jackson BeanDeserializer 获取解析的json字段所在对象错误问题及原因分析

问题:

用spring自定义Deserializer并且需要获取到当前解析字段所在对象实例时,某种情况会导出获取到的对象实例并非当前字段所在对象。

版本:

spring boot 2.1.4
jackson-databind 2.9.8

场景:

首先先看看定义的实体结构

public class Dept{
    private User user;
    private String deptName
}

public class User{
    private String userName;
}

然后是自定义 Deserializer 的代码,重点关注图中的 currentValue

public class CustomDeserializer extends JsonDeserializer<IDictBean> {

    @Override
    public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonNode node = p.getCodec().readTree(p);
        //当前解析的json字段名称
        String currentName = p.currentName();
        //当前字段所在对象的实体
        Object currentValue = p.getCurrentValue();
        return node.asText();
    }
}

下面是 Controller 的方法代码


@RequestMapping(value = "", method = RequestMethod.POST)
public void cabinMapCreate(@RequestBody Dept dept) {
}

接下来是针对三种不同的 json 转换成 dept 并在绑定deptName字段时 currentValue 的区别

第一种 json : currentValue 为 Dept 的实例 ,此处无论 user 里的字段是 abc 或是 userName 都一样

{

        "user":{

                "abc":""

        },

        "deptName":"技术部"

}

第二种 json : currentValue 为 Dept 的实例 

{

        "user": null

        "deptName":"技术部"

}

3.第三种 json : 解析 deptName 时则 currentValue 为 User 的实例,这也是问题所在,deptName是Dept的字段,currentValue却是User。

{

        "user":{},

        "deptName":"技术部"

}

 原因分析:

首先,json解析的顺序是从上往下,如下面的json会先解析 user 字段,然后解析 user 里的 abc ,接着在解析deptName.

{

        "user":{

                "abc":""

        },

        "deptName":"技术部"

}

接下来会通过 BeanDeserializer 源码中的 deserialize 和 deserializeFromObject 方法分析上面三种情况的区别:(BeanDeserializer 是用于反序列化对象用的)

源码中的注释讲的都是解析 user 字段的过程

第一种json

@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException{
	//在解析 user 字段时, currentValue 是 Dept 的实例
    //判断 json 中 user: 后面是不是 "{" 开头,是的话表示user是一个对象.则此处为ture
    //此处p.isExpectedStartObjectToken() 为 true,走if里的逻辑
	if (p.isExpectedStartObjectToken()) {
		if (_vanillaProcessing) {
			return vanillaDeserialize(p, ctxt, p.nextToken());
		} 
		p.nextToken();
		if (_objectIdReader != null) {
			return deserializeWithObjectId(p, ctxt);
		}
        //此deserializeFromObject方法里会创建一个user实例,并会修改 currentValue 的值
		return deserializeFromObject(p, ctxt);
	}
	return _deserializeOther(p, ctxt, p.getCurrentToken());
}

@Override
public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException{
    .......省略若干代码..........

    //这里是会创建一个 User 对象,即 bean=user 对象
	final Object bean = _valueInstantiator.createUsingDefault(ctxt);
	//此处会让 currentValue由 dept 改成 user 实例.因此如果后续没有地方把currentValue改成 dept,则 currentValue 就会一直都是 user 的实例
	p.setCurrentValue(bean);

	if (p.canReadObjectId()) {
		Object id = p.getObjectId();
		if (id != null) {
			_handleTypedObjectId(p, ctxt, bean, id);
		}
	}
	if (_injectables != null) {
		injectValues(ctxt, bean);
	}
	if (_needViewProcesing) {
		Class<?> view = ctxt.getActiveView();
		if (view != null) {
			return deserializeWithView(p, ctxt, bean, view);
		}
	}
    //此处会判断当前标识是不是字段名称,此处是 abc 字段,所以p.hasTokenId(JsonTokenId.ID_FIELD_NAME) 为 true
	if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
        //此处拿到的 propName 是 abc
        // 此处执行了 do while,而 while 里的p.nextFieldName()会在找不到下一个字段(此处指的是user内的下一个字段)的时候把 currentValue 设为dept的实例,因此 currentValue 又被改回dept了,所以解析下一个字段deptName的时候 currentValue=dept
		String propName = p.getCurrentName();
		do {
			p.nextToken();
			SettableBeanProperty prop = _beanProperties.find(propName);
			if (prop != null) { // normal case
				try {
					prop.deserializeAndSet(p, ctxt, bean);
				} catch (Exception e) {
					wrapAndThrow(e, bean, propName, ctxt);
				}
				continue;
			}
			handleUnknownVanilla(p, ctxt, bean, propName);
		} while ((propName = p.nextFieldName()) != null);
	}
	return bean;
}

第二种json

@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException{
	//在解析 user 进入这个方法的时候 currentValue 是 Dept 的实例
    //判断 json 中 user: 后面是不是 "{" 开头,是的话表示user是一个对象,则此处为true,否则为false
    //user 后面是 null,所以此处为false
	if (p.isExpectedStartObjectToken()) {
		if (_vanillaProcessing) {
			return vanillaDeserialize(p, ctxt, p.nextToken());
		} 
		p.nextToken();
		if (_objectIdReader != null) {
			return deserializeWithObjectId(p, ctxt);
		}
		return deserializeFromObject(p, ctxt);
	}
    //这里会直接返回然后继续解析下一个 deptName 字段,因此解析 deptName 时 currentValue 是 Dept 的实例
	return _deserializeOther(p, ctxt, p.getCurrentToken());
}

第三种json

@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException{
	//在解析 user 字段时, currentValue 是 Dept 的实例
    //判断 json 中 user: 后面是不是 "{" 开头,是的话表示user是一个对象.则此处为ture
    //此处p.isExpectedStartObjectToken() 为 true,走if里的逻辑
	if (p.isExpectedStartObjectToken()) {
		if (_vanillaProcessing) {
			return vanillaDeserialize(p, ctxt, p.nextToken());
		} 
		p.nextToken();
		if (_objectIdReader != null) {
			return deserializeWithObjectId(p, ctxt);
		}
        //此deserializeFromObject方法里会创建一个user实例,并会修改 currentValue 的值
		return deserializeFromObject(p, ctxt);
	}
	return _deserializeOther(p, ctxt, p.getCurrentToken());
}

@Override
public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException{
    .......省略若干代码..........

    //这里是会创建一个 User 对象,即 bean=user 对象
	final Object bean = _valueInstantiator.createUsingDefault(ctxt);
	//此处会让 currentValue由 dept 改成 user 实例.因此如果后续没有地方把currentValue改成 dept,则 currentValue 就会一直都是 user 的实例
	p.setCurrentValue(bean);

	if (p.canReadObjectId()) {
		Object id = p.getObjectId();
		if (id != null) {
			_handleTypedObjectId(p, ctxt, bean, id);
		}
	}
	if (_injectables != null) {
		injectValues(ctxt, bean);
	}
	if (_needViewProcesing) {
		Class<?> view = ctxt.getActiveView();
		if (view != null) {
			return deserializeWithView(p, ctxt, bean, view);
		}
	}
    //此处会判断当前标识是不是字段名称,user 后面是 {} ,所以p.hasTokenId(JsonTokenId.ID_FIELD_NAME) 为 false
	if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
		String propName = p.getCurrentName();
		do {
			p.nextToken();
			SettableBeanProperty prop = _beanProperties.find(propName);
			if (prop != null) { // normal case
				try {
					prop.deserializeAndSet(p, ctxt, bean);
				} catch (Exception e) {
					wrapAndThrow(e, bean, propName, ctxt);
				}
				continue;
			}
			handleUnknownVanilla(p, ctxt, bean, propName);
		} while ((propName = p.nextFieldName()) != null);
	}
    //上面 while 里的p.nextFieldName()会在找不到下一个字段(此处指的是user内的下一个字段)的时候把 currentValue 改为 dept 的实例,因为没执行到 while ,所以 currentValue 没有被改回 dept ,所以解析下一个字段deptName的时候 currentValue=user
	return bean;
}

总结:

综上所述,在解析user的时候
        第一种json(user:{"${any filed}":""}) 会修改currentValue=user实例,但当解析完 user 后会把currentValue 还原为 dept实例
        第二种json(user:null)不会修改 currentValue的指向,所以currentValue=dept的实例
        第三种json (user:{}) 会修改currentValue=user实例,但最后却没有一个点可以把currentValue 还原为 dept实例,导致后续解析其他字段的时候会获取到错误的currentValue.

       

@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException{
	//在解析 user 进入这个方法的时候 currentValue 是 Dept 的实例
    //判断 json 中 user: 后面是不是 "{" 开头,是的话表示user是一个对象.此处为ture,否则为false
    //第一种和第三种json 为true,走 if 里的逻辑,第二种json为false
	if (p.isExpectedStartObjectToken()) {
		if (_vanillaProcessing) {
			return vanillaDeserialize(p, ctxt, p.nextToken());
		} 
		p.nextToken();
		if (_objectIdReader != null) {
			return deserializeWithObjectId(p, ctxt);
		}
        //此方法里会创建一个user实例,并会修改currentValue的值
        //就是这方法里面逻辑导致了上述第一种和第三种 json 参数的差异
		return deserializeFromObject(p, ctxt);
	}
    //上面第二种 json 种走的是这里的逻辑,会直接返回继续解析下一个 deptName 字段,因此解析 deptName 时 currentValue 是 Dept 的实例
	return _deserializeOther(p, ctxt, p.getCurrentToken());
}

@Override
public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException{
    .......省略若干代码..........

    //这里是会创建一个 User 对象,即 bean=user 对象
	final Object bean = _valueInstantiator.createUsingDefault(ctxt);
	//此处会让 currentValue=user实例.因此如果后续没有地方把currentValue改成 dept,则 currentValue 就会一直都是 user 的实例
	p.setCurrentValue(bean);

	if (p.canReadObjectId()) {
		Object id = p.getObjectId();
		if (id != null) {
			_handleTypedObjectId(p, ctxt, bean, id);
		}
	}
	if (_injectables != null) {
		injectValues(ctxt, bean);
	}
	if (_needViewProcesing) {
		Class<?> view = ctxt.getActiveView();
		if (view != null) {
			return deserializeWithView(p, ctxt, bean, view);
		}
	}
    //此处会判断当前标识是不是字段名称,第一种json对应 abc 字段所以为true, 第三种json则为false
	if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
        //此处拿到的 propName 是 abc
        // while 里的p.nextFieldName()会在找不到下一个字段(此处指的是user里面的下一个字段)的时候会把 currentValue 设为dept的实例,然后返回继续解析下一个 deptName 的字段,因此第一种 json 拿到的 currentValue=dept实例
		String propName = p.getCurrentName();
		do {
			p.nextToken();
			SettableBeanProperty prop = _beanProperties.find(propName);
			if (prop != null) { // normal case
				try {
					prop.deserializeAndSet(p, ctxt, bean);
				} catch (Exception e) {
					wrapAndThrow(e, bean, propName, ctxt);
				}
				continue;
			}
			handleUnknownVanilla(p, ctxt, bean, propName);
		} while ((propName = p.nextFieldName()) != null);
	}
    //第三种json 跳过 if 里的逻辑,导致没有执行到 p.nextFieldName() ,于是在解析后续字段的时候拿到的 currentValue都会是 user 的实例,bug就这样产生了。报错也会报的莫名其妙。
	return bean;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值