问题描述
json数据:{“pTargetId”:“123”}
javaBean:
@Data
public 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, nope
return 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 char
StringBuilder 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")
注解
等等