JsonDeserializer
关于jackson实现自定义的对象解析器,最常用的方式就是继承顶级抽象类(com.fasterxml.jackson.databind.JsonDeserializer
) 来实现,比如下面的代码实现
// 自定义的JavaBean
class Person {private String name;private int age;// 标准的getter和setter// ...
}
class PersonDeserializer extends JsonDeserializer<Person> {@Overridepublic 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实现子类的解析器。
因为BaseBean
的initialized,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));}@Overridepublic BaseBean deserializeFromObject(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {// see also BeanDeserializer.vanillaDeserializeBaseBean 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 casetry {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