美文网首页
SpringBoot Jackson 复杂模型 嵌套模型的反序列

SpringBoot Jackson 复杂模型 嵌套模型的反序列

作者: Yellowtail | 来源:发表于2019-10-09 15:14 被阅读0次

[TOC]

前言

最近写了一个Controller , 接收了一个复杂模型
在这个过程中遇到了很多的问题,最近梳理出来了,在这里分享一下

首先看一下 Controller 代码

@RequestMapping(value = "/", method = RequestMethod.POST)
public String test(@RequestBody BaseWriteOperation data) {
}

看下 BaseWriteOperation 长什么样子

package com.mongodb.operation;

public abstract class BaseWriteOperation implements AsyncWriteOperation<WriteConcernResult>, WriteOperation<WriteConcernResult> {

    private final WriteConcern writeConcern;
    private final MongoNamespace namespace;
    private final boolean ordered;
    private Boolean bypassDocumentValidation;
}

这个对象是 MongoDB 驱动代码里的一个 抽象类

列出一个子类

public class InsertOperation extends BaseWriteOperation {
    private final List<InsertRequest> insertRequests;
}

我的场景基本介绍完了,可以看到有挺多东西需要注意的,包括

  1. 得到 spring bootobject mapper
  2. spring bootobject mapper 注册 Module (也就是注册自定义序列化、反序列化逻辑)
  3. 实现一个自定义反序列化逻辑
  4. 实现复杂模型的嵌套反序列化

SpringBoot 相关

得到 object mapper

这个知道了就很简单,不知道的时候会无从下手

import com.fasterxml.jackson.databind.ObjectMapper;

@Autowired
 private ObjectMapper bootJacksonObjectMapper;

注册自定义 Module

首先我介绍一下,我们自己实现一个 Jacksonjson 工具类的时候,是怎么注册的
直接贴一下完整的工具类代码


import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTimeZone;

import com.fanggeek.mmm.common.serialize.BaseWriteOperationDeserializer;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.joda.JodaModule;

/**
 * <br>json 帮助类 
 * <br>对象和JSON互相转换
 *
 * @author YellowTail
 * @since 2019-10-09
 */
public class JSON2Helper {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    static {
        OBJECT_MAPPER.registerModule(new JodaModule());
        
        OBJECT_MAPPER.registerModule(BaseWriteOperationDeserializer.getModule());

        
        OBJECT_MAPPER.setTimeZone(DateTimeZone.getDefault().toTimeZone());
        OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        OBJECT_MAPPER.setSerializationInclusion(Include.NON_NULL);
    }

    /**
     * <br>得到实例
     * @return
     * @author YellowTail
     * @since 2019-04-13
     */
    public static ObjectMapper getInstance() {
        return OBJECT_MAPPER;
    }
    /**
     * 将Object对象转为JSON字符串
     * 
     * @param object
     * @return
     */
    public static String toJson(Object object) {
        String json = null;
        try {
            json = OBJECT_MAPPER.writeValueAsString(object);
        } catch (Exception e) {
            throw new RuntimeException("To json error, object is " + object + ";exception:" + e);
        }
        return json;
    }

    /**
     * 将一个JSON字符串转换为Object对象
     * 
     * @param <T>
     * @param json
     * @param clazz
     * @return
     */
    public static <T> T toObject(String json, Class<T> clazz) {
        T o = null;
        if (StringUtils.isNotBlank(json)) {
            try {
                o = OBJECT_MAPPER.readValue(json, clazz);
            } catch (Exception e) {
                throw new RuntimeException("Json string To object error, json is " + json + ";exception:" + e);
            }
        }
        return o;
    }

    /**
     * 将一个JSON字符串转换为T对象
     * 
     * @param json
     * @param typeReference
     * @return
     */
    public static <T> T toObject(String json, TypeReference<T> typeReference) {
        try {
            return OBJECT_MAPPER.readValue(json, typeReference);
        } catch (Exception e) {
            throw new RuntimeException("failed to convert string " + json + " to object", e);
        }
    }
}

注册逻辑在 static 方法块里的 registerModule
有一个自定义的方法,贴一下代码

BaseWriteOperationDeserializer.getModule();

public static SimpleModule getModule() {
        SimpleModule simpleModule = new SimpleModule();
        
        simpleModule.addDeserializer(BaseWriteOperation.class, new BaseWriteOperationDeserializer());
        
        return simpleModule;
    }

代码的意思后面会介绍,这里只介绍注册思路:就是向 Object Mapper 对象 register Module

对于 spring boot, 我们可以先拿到对象,再自己调用 registerModule,这种是可以的,不过比较 low b
spring boot 提供了一个注解

import org.springframework.boot.jackson.JsonComponent;

@JsonComponent

我们写一个类,加上这个注解,继承抽象类 JsonDeserializer 或者 StdDeserializer 就可以了
下面介绍一下,如何去实现

Jackson 自定义序列化

反序列化

反序列化 就是把 一个 json 字符串 转换成 bean 或者 对象的一个方法/过程

首先看一下抽象方法,也就是我们要实现的方法

@JsonComponent
public class BaseWriteOperationDeserializer extends JsonDeserializer<BaseWriteOperation> {
    
    public static SimpleModule getModule() {
        SimpleModule simpleModule = new SimpleModule();
        
        simpleModule.addDeserializer(BaseWriteOperation.class, new BaseWriteOperationDeserializer());
        
        return simpleModule;
    }

    @SuppressWarnings("unused")
    public BaseWriteOperation deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        
        //result, 也就是我们将要返回的对象
        BaseWriteOperation result = null;
        
        //得到 codec, 通过调试可以知道,ta是一个 object mapper 对象
        ObjectCodec codec = p.getCodec();
        
        //读出所有的字段,得到 TreeNode 对象
        TreeNode readValueAsTree = p.readValueAsTree();
        
        //得到某个属性 的 node
        TreeNode treeNode = readValueAsTree.get("writeConcern");
        
        
        //treeToValue 是一个 嵌套的反序列化操作,如果对象符合 jackson 反序列化规则(如有空构造方法, 有 get/set 方法等),那么调用之后,jackson自动进行反序列化
        // 在这里,WriteConcern 是一个复杂对象,jackson 无法帮助我们自动反序列化,
        //所以我们需要自己实现,也即是再写一个 public class WriteConcernDeserializer extends JsonDeserializer<WriteConcern>  即可
        
        WriteConcern writeConcern = codec.treeToValue(treeNode, WriteConcern.class);
        
        //这是一个复杂模型,也需要我们自己实现,此处不再赘述
        TreeNode namespace = readValueAsTree.get("namespace");
        
        MongoNamespace mongoNamespace = codec.treeToValue(namespace, MongoNamespace.class);
        
        //这是一个 boolean 字段,可以直接得到对象
        BooleanNode treeNode2 = (BooleanNode) readValueAsTree.get("ordered");
        
        boolean ordered = treeNode2.asBoolean();
        
        TreeNode treeNode3 = readValueAsTree.get("insertRequests");
        
        ...
        代码省略
        ...
        
        return result;
    }
}

说一下一般流程和逻辑

  1. 首先得到一个 codec对象,p.getCodec() 可以把该对象里面的复杂对象反序列化出来
  2. 得到TreeNode 对象, p.readValueAsTree()
  3. 通过readValueAsTree.get("xxx") 得到不同属性的 node 对象,再根据不同的类型,进行不同的操作,如果是简单类型,可以调用asXXX 得到值,如果是复杂对象,通过 codec.treeToValue(xxNode, XXX.class) 得到复杂模型,如果这个嵌套的复杂模型不能被自动反序列化出来,那么就需要另外实现一下,实现完成之后,这里的treeToValue 调用之后,会自动进入嵌套的对象的反序列化代码里

序列化

序列化 就是把一个对象/bean 转成 json 字符串的一个 过程/方法

这里列两个例子
第一个,是普通对象 {"key": "value"}

@JsonComponent
public class WriteConcernSerializer extends JsonSerializer<WriteConcern> {

    @Override
    public void serialize(WriteConcern value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        
        gen.writeStartObject();
        
        Object wObject = value.getWObject();
        if (null == wObject) {
            gen.writeNullField("w");
        }
        
        gen.writeBooleanField("fsync", value.getFsync());
        gen.writeBooleanField("journal", value.getJ());
        
        gen.writeNumberField("wTimeoutMS", value.getWtimeout());
        
        
        gen.writeEndObject();
    }
    
}

gen.writeStartObject() 就是写入 {
gen.writeEndObject(); 就是写入 }

中间的属性,根据不同的类型,调用不同的方法即可

第二个例子,我们有其他手段,或者方法可以得到 json 字符串的时候

public class BsonDocumentSerializer extends StdSerializer<BsonDocument> {

    private static final long serialVersionUID = -3916419648325416357L;

    protected BsonDocumentSerializer(Class<BsonDocument> t) {
        super(t);
    }

    @Override
    public void serialize(BsonDocument value, JsonGenerator gen, SerializerProvider provider) throws IOException {
                    
        String string = value.toJson();
        
        gen.writeString(string);
        
    }
    
}

如上所示,我们的 value 提供了一个 toJson 方法,可以直接得到 json 字符串,那么我们直接调用 writeString 写入就可以了
注意:此时不需要 writeStartObjectwriteEndObject 了,因为这个字符串已经有这两个字符了

相关文章

网友评论

      本文标题:SpringBoot Jackson 复杂模型 嵌套模型的反序列

      本文链接:https://www.haomeiwen.com/subject/ielgpctx.html