Jackson进行Json反序列化对于一个小写字母开头后跟大写字母无法识别反序列成功问题

问题描述

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中的属性字段。

Jackson进行Json反序列化对于一个小写字母开头后跟大写字母无法识别反序列成功问题_第1张图片
这里报错倒是解决了,但是本质问题还是没有解决也就是为什么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 props,其中key为名称,value为对应的属性构建对象。
什么意思呢,比如我们json数据{"pTargetId":"123"},那么最后只有在map中含有key:pTargetId的builider才能进行写入。

而这个map的生成可以通过_addFields(props)方法,去解析类的属性名称来进行获取到,也可以通过_addMethods(props),刚才说的通过解析getXxx()方法来获取到。

所以到这里我们的Test类的map应该有两个值,一个是通过属性解析出来的pTargetId和通过方法解析出来的ptargetId
Jackson进行Json反序列化对于一个小写字母开头后跟大写字母无法识别反序列成功问题_第2张图片
那么虽然通过方法获取的key错误了(ptargetId),但不是还有通过属性正确获取的吗(pTargetId),为什么最后还是没赋值进去?这是因为下面这个方法:

        _removeUnwantedProperties(props);

这个方法将会把某些不合规的属性值给移除掉,针对于属性类型获取的key,如果你的属性范围为private,那么就会将你过滤掉(过滤逻辑比较简单,可以自己追下去看看)
Jackson进行Json反序列化对于一个小写字母开头后跟大写字母无法识别反序列成功问题_第3张图片
所以最后只有错误的ptargetId存在。

解决方案

原因知道了,方案就很简单了,就是想办法将它的key变更对(非常不符合直觉,满足json规范,满足javaBean规范,最后却不能正确转成功,因该是Bug范畴了吧)

  1. 换工具,Gson,fastjson符合直觉,能填充成功(Gson这块代码非常清晰,思路符合直觉)
  2. private pTargetId修改为public pTargetId
  3. 自己实现get方法
        public String getpTargetId() {
           return pTargetId;
       }
  1. 使用@JsonProperty(value = "pTargetId")注解
    等等

你可能感兴趣的:(java,json,java)