jackson:基于BeanDeserializer实现自定义的Java bean 解析器

19 篇文章 1 订阅

JsonDeserializer

关于jackson实现自定义的对象解析器,最常用的方式就是继承顶级抽象类(com.fasterxml.jackson.databind.JsonDeserializer) 来实现,比如下面的代码实现

// 自定义的JavaBean
class Person {
    private String name;
    private int age;
 
    // 标准的getter和setter
    // ...
}
class PersonDeserializer extends JsonDeserializer<Person> {
    @Override
    public Person deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        // 假设JSON格式为 {"name": "John", "age": 30}
        String name = p.getValueAsString(); // 获取name的值
        int age = p.nextFieldName() ? p.getIntValue() : 0; // 获取age的值
 
        Person person = new Person();
        person.setName(name);
        person.setAge(age);
        return person;
    }
}

BeanDeserializer

但上面这种方式只适合已经知解析类型的场景,在一些特殊场景,比如不知道特定解析类型的场景,这个方法会有局限性。
其实jackson已经提供了一个可以反序列化任意java bean对象实例的反序列化器类com.fasterxml.jackson.databind.deser.BeanDeserializer,如果能基于这个类实现自定义反序列化器,那么就可以利用它已有的成熟逻辑,代码实现上会简化,并且稳定和适应性也更好。
但是查看了BeanDeserializer的构造方法,发现问题不简单。我原本以为BeanDeserializer能有以Class为参数的构造方法,调用者直接提供Java Bean的类,就可以实现构造方法。但现实是BeanDeserializer的构造方法需要的是BeanDeserializerBase对象

BeanDeserializerBase

现在如何从Java Bean Class构造一个BeanDeserializerBase对象成了解决问题的关键。
通过跟踪DeserializationContext.findRootValueDeserializer方法的实现源码(过程曲折),终于找到了这条路径,以下是实现代码:

	/**
	 * 创建{@code beanClass}对应的{@link BeanDeserializerBase}实例用于父类构造方法的参数,
	 * 将{@code beanClass}的序列化参数注入到当前实例中
	 * @param beanClass
	 */
	private static BeanDeserializer createBeanDeserializer(Class<?> beanClass){
		try {
			ObjectMapper mapper = new ObjectMapper();
			DefaultDeserializationContext defctx = (DefaultDeserializationContext) mapper.getDeserializationContext();
			DefaultDeserializationContext ctxt = defctx.createInstance(mapper.getDeserializationConfig(),	null,null);
			JavaType type = ctxt.constructType(beanClass);
			BasicBeanDescription beanDesc = (BasicBeanDescription)ctxt.getConfig().introspect(type);
			BeanDeserializerFactory factory = (BeanDeserializerFactory) ctxt.getFactory();
			BeanDeserializer beanDeserializer = (BeanDeserializer) factory.buildBeanDeserializer(ctxt, type, beanDesc);
			beanDeserializer.resolve(ctxt);
			return beanDeserializer;
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}

BeanDeserializer 实现示例

有了上面的createBeanDeserializer方法,就可以写继承BeanDeserializer 的子类了,
下面这个类用于实现以我自定义的BaseBean接口的所有JavaBean实现子类的解析器。
因为BaseBeaninitialized,modified字段的值受其他字段setter方法影响,所以下面的实现中先解析出initialized,modified字段,待所有字段都解析完成后,再对这两个字段赋值,在确保字段值正确。

import static gu.sql2java.utils.CaseSupport.isSnakecase;
import static gu.sql2java.utils.CaseSupport.toCamelcase;

import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerFactory;
import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.introspect.BasicBeanDescription;
import gu.sql2java.BaseBean;
import gu.sql2java.Constant;

public class JacksonDeserializer extends BeanDeserializer implements Constant  {
	private static final long serialVersionUID = 7410414787512241455L;
	public JacksonDeserializer(Class<? extends BaseBean> beanClass) {
		super(createBeanDeserializer(beanClass));
	}

	@Override
	public BaseBean deserializeFromObject(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
		// see also BeanDeserializer.vanillaDeserialize
		BaseBean bean = (BaseBean) _valueInstantiator.createUsingDefault(ctxt);
		Boolean _new = null;
		Integer initialized = null,modified = null;
        for(String propName = jp.nextFieldName();propName != null;propName = jp.nextFieldName()) {
        	jp.nextToken();
        	SettableBeanProperty prop = findProperty(this,propName);
        	if (prop != null) { // normal case
        		try {
        			switch(propName) {
        			case FIELD_NEW:
        				_new = (Boolean) prop.deserialize(jp,ctxt);
        				break;
        			case FIELD_INITIALIZED:
        				initialized = (Integer) prop.deserialize(jp,ctxt);
        				break;
        			case FIELD_MODIFIED:
        				modified = (Integer) prop.deserialize(jp,ctxt);
        				break;
        			default:
        				prop.deserializeAndSet(jp, ctxt, bean);
        			}
        		} catch (Exception e) {
        			wrapAndThrow(e, bean, propName, ctxt);
        		}
        		continue;
        	}
            handleUnknownVanilla(jp, ctxt, bean, propName);
        }
        if(null != _new) {
        	bean.setNew(_new);
        }
		if(null != initialized){
			bean.setInitialized(initialized);
		}
		if(null != modified){
			bean.setModified(modified);
		}
		return bean;
	}
	/**
	 * 查找字段名对应的{@link SettableBeanProperty}对象如果没找到且字段名为snake-case尝试转为camel-case查找。
	 * @param beanDeserializer
	 * @param propName
	 * @see BeanDeserializer#findProperty(String)
	 */
	private SettableBeanProperty findProperty(BeanDeserializer beanDeserializer,String propName) {
		SettableBeanProperty prop = beanDeserializer.findProperty(propName);
		if(null == prop && isSnakecase(propName)) {
			prop = beanDeserializer.findProperty(toCamelcase(propName));
		}
		return prop;
	}
	/**
	 * 创建{@code beanClass}对应的{@link BeanDeserializerBase}实例用于父类构造方法的参数,
	 * 将{@code beanClass}的序列化参数注入到当前实例中
	 * @param beanClass
	 */
	private static BeanDeserializer createBeanDeserializer(Class<?> beanClass){
		try {
			ObjectMapper mapper = new ObjectMapper();
			DefaultDeserializationContext defctx = (DefaultDeserializationContext) mapper.getDeserializationContext();
			DefaultDeserializationContext ctxt = defctx.createInstance(mapper.getDeserializationConfig(),	null,null);
			JavaType type = ctxt.constructType(beanClass);
			BasicBeanDescription beanDesc = (BasicBeanDescription)ctxt.getConfig().introspect(type);
			BeanDeserializerFactory factory = (BeanDeserializerFactory) ctxt.getFactory();
			BeanDeserializer beanDeserializer = (BeanDeserializer) factory.buildBeanDeserializer(ctxt, type, beanDesc);
			beanDeserializer.resolve(ctxt);
			return beanDeserializer;
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}
}

以上完整代码参见码云仓库:
https://gitee.com/l0km/sql2java/blob/dev/sql2java-base/src/main/java/gu/sql2java/json/JacksonDeserializer.java

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值