问题描述
json数据:{“pTargetId”:“123”}
javaBean:
@Datapublic static class Test {private String pTargetId;}
运行下面代码:
public static void main(String[] args) throws JsonProcessingException {String json = "{\"pTargetId\":\"123\"}";ObjectMapper objectMapper=new ObjectMapper();Test test = objectMapper.readValue(json, Test.class);}
报错:
Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "pTargetId"
这个报错的意思很明显,从json中没有解析出对象的pTargetId
字段,但明显我们知道json中是存在这个属性字段的。
通过设置
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//可以将jackson设置为不需要json中包含javaBean中的属性字段。
这里报错倒是解决了,但是本质问题还是没有解决也就是为什么json中有pTargetId
,但是转换成对象后属性值确实空的。
Gson,fastjson,hutool的不存在这个问题
导致原因
jackson在识别对象的属性name时,会通过两种方式,第一通过field
的name,第二种通过javaBean规范获取getXxx()
方法解析属性名。
它出问题的地方是方法二,通过getXxx()解析属性名。
源码位置:
public class DefaultAccessorNamingStrategy extends AccessorNamingStrategy {/*** Method called to figure out name of the property, given corresponding suggested name based on a method or field name.* Params:* basename – Name of accessor/mutator method, not including prefix ("get"/"is"/"set")** 这个方法就是用来解析getXxx方法,basename:就是getXxx字符串 offset对于getXxx就是3,对于isXxx就是2* 下面的说明都针对 getPTargetId() 这个方法**/protected String legacyManglePropertyName(final String basename, final int offset){final int end = basename.length();if (end == offset) { // empty name, nopereturn null;}//获取方法的首字母 也就是P char c = basename.charAt(offset);// 12-Oct-2020, tatu: Additional configurability; allow checking that// base name is acceptable (currently just by checking first character)if (_baseNameValidator != null) {if (!_baseNameValidator.accept(c, basename, offset)) {return null;}}// next check: is the first character upper case? If not, return as is//将首字母变更为小写char d = Character.toLowerCase(c);//如果首字母是小写,直接返回get后的字符串 (getaaa() 返回的就是aaa)if (c == d) {return basename.substring(offset);}// otherwise, lower case initial chars. Common case first, just one charStringBuilder sb = new StringBuilder(end - offset);//将小写的首字母放到要输出的字符串中sb.append(d);//从首字母后的字母开始int i = offset+1;for (; i < end; ++i) {c = basename.charAt(i);d = Character.toLowerCase(c);//如果字母为小写,则将当前以及后面的字符全部加入到输出的字符串中if (c == d) {sb.append(basename, i, end);break;}//如果字母为大小,则调整为小写然后加入到字符串中sb.append(d);}/*** 针对于 getPTargetId()方法,则返回的字符串为 ptargetId* pTargetId 属性字段,生成get方法的规范就是get后首字母大写* 所以其get方法为getPTargetId()**/return sb.toString();}}
那么上述方法在哪调用的呢?
public class POJOPropertiesCollector
{protected void collectAll(){LinkedHashMap<String, POJOPropertyBuilder> props = new LinkedHashMap<String, POJOPropertyBuilder>();// First: gather basic data//通过属性直接获取其名称_addFields(props); // note: populates _fieldRenameMappings//通过上面我们讲解的方法获取名(层级比较深)_addMethods(props);// 25-Jan-2016, tatu: Avoid introspecting (constructor-)creators for non-static// inner classes, see [databind#1502]if (!_classDef.isNonStaticInnerClass()) {_addCreators(props);}// Remove ignored properties, first; this MUST precede annotation merging// since logic relies on knowing exactly which accessor has which annotation_removeUnwantedProperties(props);// and then remove unneeded accessors (wrt read-only, read-write)_removeUnwantedAccessor(props);.....忽略的代码}}
上面源码的思路是,最后我们会生成一个LinkedHashMap<String, POJOPropertyBuilder> props
,其中key为名称,value为对应的属性构建对象。
什么意思呢,比如我们json数据{"pTargetId":"123"}
,那么最后只有在map中含有key:pTargetId
的builider才能进行写入。
而这个map的生成可以通过_addFields(props)
方法,去解析类的属性名称来进行获取到,也可以通过_addMethods(props)
,刚才说的通过解析getXxx()
方法来获取到。
所以到这里我们的Test类
的map应该有两个值,一个是通过属性解析出来的pTargetId
和通过方法解析出来的ptargetId
。
那么虽然通过方法获取的key错误了(ptargetId
),但不是还有通过属性正确获取的吗(pTargetId
),为什么最后还是没赋值进去?这是因为下面这个方法:
_removeUnwantedProperties(props);
这个方法将会把某些不合规的属性值给移除掉,针对于属性类型获取的key,如果你的属性范围为private
,那么就会将你过滤掉(过滤逻辑比较简单,可以自己追下去看看)
所以最后只有错误的ptargetId
存在。
解决方案
原因知道了,方案就很简单了,就是想办法将它的key变更对(非常不符合直觉,满足json规范,满足javaBean规范,最后却不能正确转成功,因该是Bug范畴了吧)
- 换工具,Gson,fastjson符合直觉,能填充成功(Gson这块代码非常清晰,思路符合直觉)
- 将
private pTargetId
修改为public pTargetId
- 自己实现get方法
public String getpTargetId() {return pTargetId;}
- 使用
@JsonProperty(value = "pTargetId")
注解
等等