Java整合Jackson实现反序列化器流程

 更新时间:2023年01月14日 09:18:47   作者:何忆清风  
Jackson是一个开源的Java序列化和反序列化工具,可以将Java对象序列化为XML或JSON格式的字符串,以及将XML或JSON格式的字符串反序列化为Java对象。由于其使用简单,速度较快,且不依靠除JDK外的其他库,被众多用户所使用

在使用 Jackson 时很多时候在自定义的数据结构时通过本身自带的序列化器可能实现不了自定义的结构转换,比如:需要从Json 字符串中读取某个字段,根据某个字段转换为对应的实体类,例如下面的结构,我需要根据 Json 字符串中的 messageType 字段转换为对应的实体,这时候我们可以通过自定义序列化器来进行转换

{
    "messageId": "1665709790068",
    "timestamp": 1665709790068,
    "point": "123.33, 123.555",
    "messageType": "MODEL",
    "payload": {
        "method": "POST",
        "modelType": "EVENT",
        "output": {
            "ouaaa": "name"
        },
        "timestamp": 1665709790068
    }
}

1. 实体类

这里 payload 可以使用成泛型通过指定对应的类型来转换为对应的实体类型

public class UniversalMessage<T extends Payload> {
    /** PAYLOAD */
    public static final String PAYLOAD = "payload";
    /** MESSAGE_ID */
    public static final String MESSAGE_ID = "messageId";
    /** TIMESTAMP */
    public static final String TIMESTAMP = "timestamp";
    /** POINT */
    public static final String POINT = "point";
    /** MESSAGETYPE */
    public static final String MESSAGE_TYPE = "messageType";
    private static final long serialVersionUID = -3703724430631400996L;
    protected String messageId;
    protected Long timestamp;
    protected String point;
    protected MessageType messageType;
    private T payload;
}

下面是定义好的 Payload 实现类,这里我们定义一个就行了 ModelType 定义的枚举类型,随便写就行了,只要跟后面获取序列化器对应就好

@Type(value = "MODEL") //填写消息类型
public class ModelEventPayload implements Payload {
    /** serialVersionUID */
    private static final long serialVersionUID = -4371712921890795815L;
    private Map<String, Object> output;
    private Long timestamp;
    protected ModelType modelType;
    protected String method;
}

2. 反序列化器

反序列化器的整体结构

PayloadDeserialize 实现了 JacksonStdDeserializer 序列化器

  • 其中用到了 parser 里面的 ObjectCodec 对象编码,用于将对应的 Json 格式转换为实体
  • Jackon会将 Json 字符串中每个结构都会转换对应的类型添加到树结构中,例如:字符串就会转换为 TextNode,对象就会转换为 ObjectNode

其中根据每个转换类型,我这里封装了一个 DeserializeForType<Payload> 接口类型,用于根据自定的类型获取序列化器,用于替换写多个 If else 代码不美观

  • 接口里面定义一个 type() 方法,实现类用于实现,当前类用于什么类型的转换
  • 上面 Json 接口中,通过 messageType 转换了之后可能还需要通过实体中的 modelType 来进行转换,这里我又通过实现 DeserializeForType<Payload> 创建了两个匿名类 event()other() 方法返回,先根据 messageType 转换了,然后再根据 modelType 获取对应的反序列化器进行转换
public class PayloadDeserialize extends StdDeserializer<UniversalMessage<Payload>> {
    /** serialVersionUID */
    private static final long serialVersionUID = 7922419965896101563L;
    /** MESSAGE_TYPE */
    public static final String MESSAGE_TYPE = "messageType";
    /** PAYLOAD */
    public static final String PAYLOAD = "payload";
    /**
     * Payload deserialize
     */
    protected PayloadDeserialize() {
        this(null);
    }
    /**
     * Payload deserialize
     */
    protected PayloadDeserialize(Class<?> vc) {
        super(vc);
    }
    /**
     * 实现jackson序列化器的方法
     */
    @Override
    public UniversalMessage<Payload> deserialize(JsonParser jsonParser,
    DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
    	//获取到对象编码对象,用当前对应可以对字符串进行转换
        ObjectCodec codec = jsonParser.getCodec();
        //读取出整个消息体的树型结构
        TreeNode treeNode = codec.readTree(jsonParser);
        //解析出的节点树结构,消息字段是处于第一层结构,所以这里可以直接通过get进行获取,jackson会将字符串不同的结构解析为不同的 TreeNode类型,例如字符串就会解析成 TextNode
        TreeNode messageTypeNode = Objects.requireNonNull(treeNode.get(MESSAGE_TYPE), "消息类型不能为空");
        Class<? extends TreeNode> messageTypeNodeClass = messageTypeNode.getClass();
        boolean assignableFrom = TextNode.class.isAssignableFrom(messageTypeNodeClass);
        Assertions.isTrue(assignableFrom, "消息类型字段类型错误:" + messageTypeNodeClass + "需要 TextNode");
        String messageTypeStr = ((TextNode) messageTypeNode).asText();
        //构建外层实体消息
        UniversalMessage<Payload> universalMessage = getPayloadUniversalMessage(treeNode, codec);
        //获取到json字符串中的 payload
        TreeNode payloadNode = treeNode.get(PAYLOAD);
        DeserializeForType<Payload> deserializeForType = DeserializeTypeContext.get(messageTypeStr);
        Objects.requireNonNull(deserializeForType, "不支持当前消息类型:" + messageTypeStr);
        //对 payload进行解析
        Payload payload = deserializeForType.deserialize(codec, payloadNode);
        universalMessage.setPayload(payload);
        return universalMessage;
    }
    /**
     * 创建最外层的消息体
     */
    private UniversalMessage<Payload> getPayloadUniversalMessage(TreeNode treeNode, ObjectCodec codec) throws IOException {
        ObjectNode node = (ObjectNode) treeNode;
        String messageId = Objects.requireNonNull(node.get(UniversalMessage.MESSAGE_ID), "消息ID不能为空").asText();
        String messageType = Objects.requireNonNull(node.get(UniversalMessage.MESSAGE_TYPE), "消息类型不能为空").asText();
        long timestamp = Objects.requireNonNull(node.get(UniversalMessage.TIMESTAMP), "时间戳不能为空").asLong();
        String point = Objects.requireNonNull(node.get(UniversalMessage.POINT), "Point不能为空").asText();
        return UniversalMessage.builder()
            .messageId(messageId)
            //枚举转换的工具,这里可以自己封装一个,根据名称进行转换
            .messageType(EnumUtils.nameOf(MessageType.class, messageType.toUpperCase()))
            .timestamp(timestamp)
            .point(point).build();
    }
    /**
     * 保存序列化器的上下文,为防止反复的创建序列化器,这里我们提前加载上对类型的序列化器
     */
    static class DeserializeTypeContext {
        /** Deserialize for type map */
        private static Map<String, DeserializeForType<Payload>> deserializeForTypeMap = new HashMap<>(4);
        static {
            add(new ModelDeserialize());
            //如果有新的类型,直接在这里添加,这里也可以做成 spring容器管理进行注入
        }
        /**
         * Add
         */
        public static void add(DeserializeForType deserializeForType) {
            Assertions.notNull(deserializeForType, "序列化器不能为空");
            deserializeForTypeMap.putIfAbsent(deserializeForType.getType(), deserializeForType);
        }
        /**
         * Get
         */
        public static DeserializeForType<Payload> get(String type) {
            Assertions.notBlank(type, "消息类型不能为空");
            return deserializeForTypeMap.get(type);
        }
    }
    /**
     * 定义的序列化器顶级接口
     */
    interface DeserializeForType<T extends Payload> {
        /**
         * 反序列化方法,这里的 ObjectCodec对象是上面最外层的方法传递进来的,TreeNode则是对应的消息结构体
         */
        T deserialize(ObjectCodec codec,  TreeNode node) throws IOException;
        /**
         * Gets type
         */
        String getType();
    }
    /**
     * 封装一个抽象的父级类,主要用于实现接口的 Type 方法,获取当前类是什么类型的
     */
    public abstract static class AbstractDeserialize<T extends Payload> implements DeserializeForType<T> {
        /** Type */
        private final String type;
        /**
         * Abstract deserialize
         */
        public AbstractDeserialize(String type) {
            this.type = type;
        }
        /**
         * Gets type *
         */
        @Override
        public String getType() {
            return this.type;
        }
    }
    /**
     * 自定义model类型的序列化器
     */
    public static class ModelDeserialize extends AbstractDeserialize<Payload> {
        /** TYPE */
        public static final String TYPE = "MODEL";
        /** MODEL_TYPE */
        public static final String MODEL_TYPE = "modelType";
        /** MODEL_TYPE_MAP */
        private static final Map<String, DeserializeForType<Payload>> MODEL_TYPE_MAP = new HashMap<>(4);
        static {
        	//这里根据 MODEL 类型又封装了几个子类型的方法进行转换
            add(other());
            add(event());
        }
        /**
         * Model deserialize
         */
        public ModelDeserialize() {
            super(TYPE);
        }
        /**
         * Deserialize
         */
        @Override
        public Payload deserialize(ObjectCodec codec, TreeNode node) throws IOException {
            TreeNode modelTypeNode = Objects.requireNonNull(node.get(MODEL_TYPE), "消息类型不能为空");
            Class<? extends TreeNode> modelTypeNodeClass = modelTypeNode.getClass();
            boolean assignableFrom = TextNode.class.isAssignableFrom(modelTypeNodeClass);
            Assertions.isTrue(assignableFrom, "模型消息类型字段类型错误:" + modelTypeNodeClass + "需要 TextNode");
            //string 类型的字段会被转换成 TextNode的节点
            String messageTypeStr = ((TextNode) modelTypeNode).asText();
            DeserializeForType<Payload> deserializeForType = MODEL_TYPE_MAP.get(messageTypeStr);
            Assertions.notNull(deserializeForType, "模型:" + messageTypeStr + ",序列化器为空");
            return deserializeForType.deserialize(codec, node);
        }
        /**
         * Add
         */
        public static void add(DeserializeForType<Payload> deserializeForType) {
            Assertions.notNull(deserializeForType, "对应类型的序列化器不能为空");
            MODEL_TYPE_MAP.putIfAbsent(deserializeForType.getType(), deserializeForType);
        }
        /**
         * OTHER
         */
        private static DeserializeForType<Payload> other() {
            return new DeserializeForType<Payload>() {
                @Override
                public Payload deserialize(ObjectCodec codec, TreeNode node) throws IOException {
                    return node.traverse(codec).readValueAs(ModelOtherPayload.class);
                }
                @Override
                public String getType() {
                    return "OTHER";
                }
            };
        }
        /**
         * Event
         */
        private static DeserializeForType<Payload> event() {
            return new DeserializeForType<Payload>() {
                @Override
                public Payload deserialize(ObjectCodec codec, TreeNode node) throws IOException {
                    return node.traverse(codec).readValueAs(ModelEventPayload.class);
                }
                @Override
                public String getType() {
                    return "EVENT";
                }
            };
        }
    }
}

3. 序列化器

序列化器就比较简单了,可以先根据 payload 实体获取到对应的类型,写入一个 string类型 messageType 然后再写 payload 作为对象进行写成 Json 格式(其他几个字段我就不写了,根据对应的类型调用对应类型的 write方法即可)

public class PayloadSerialize extends StdSerializer<UniversalMessage<Payload>> {
    private static final long serialVersionUID = 7679701332948432903L;
    protected PayloadSerialize() {
        this(null);
    }
    protected PayloadSerialize(Class<Payload> t) {
        super(t);
    }
    @Override
    public void serialize(UniversalMessage<Payload> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        Payload payload = value.getPayload();
        gen.writeStartObject();
        if (payload != null) {
            Type type = payload.getClass().getAnnotation(Type.class);
            if (type != null) {
                gen.writeStringField("messageType", type.value());
            }
        }
        gen.writeObjectField("payload", payload);
        gen.writeEndObject();
    }
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Type {
    String value();
}

到此这篇关于Java整合Jackson实现反序列化器流程的文章就介绍到这了,更多相关Java Jackson反序列化器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JDK8中Optional类巧用之判空操作

    JDK8中Optional类巧用之判空操作

    善用Optional可以使我们代码中很多繁琐、丑陋的设计变得十分优雅,这篇文章主要给大家介绍了JDK8中Optional类巧用之判空的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2021-08-08
  • idea中创建jsp项目的详细实战步骤

    idea中创建jsp项目的详细实战步骤

    才学javaWeb,以防自己忘记创建项目的过程,所以浅浅的记录一下吧,下面这篇文章主要给大家介绍了关于idea中创建jsp项目的详细步骤,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-09-09
  • springboot幂等切片的实现

    springboot幂等切片的实现

    本文主要介绍了springboot幂等切片的实现,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • spring如何使用xml装配bean

    spring如何使用xml装配bean

    这篇文章主要介绍了spring如何使用xml装配bean,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • Java多线程中停止线程遇到线程阻塞的处理方法详解

    Java多线程中停止线程遇到线程阻塞的处理方法详解

    这篇文章主要介绍了Java多线程中停止线程遇到线程阻塞的处理方法详解,在阻塞状态下,线程会释放CPU资源,从而允许其他线程执行,线程阻塞是实现多线程编程中重要的概念,可以提高程序的效率和资源利用率,需要的朋友可以参考下
    2023-10-10
  • 一文学会使用sa-token解决网站权限验证

    一文学会使用sa-token解决网站权限验证

    这篇文章主要为大家介绍了使用sa-token解决网站权限验证方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • JAVA 根据Url把多文件打包成ZIP下载实例

    JAVA 根据Url把多文件打包成ZIP下载实例

    这篇文章主要介绍了JAVA 根据Url把多文件打包成ZIP下载的相关资料,需要的朋友可以参考下
    2017-08-08
  • java实现验证码类生成中文验证码

    java实现验证码类生成中文验证码

    java实现的汉字输入验证码,主要包含两个类,一个是生成验证码,一个是判断验证码输入是否正确,实现原理非常简单,将汉字和干扰线生成图片并将汉字保存到session,前台获取每次生成验证码图片并用文本框值和session值比较,功能就怎么简单
    2014-01-01
  • 使用maven工具解决jar包冲突或重复加载的问题

    使用maven工具解决jar包冲突或重复加载的问题

    这篇文章主要介绍了使用maven工具解决jar包冲突或重复加载的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Maven学习----Maven安装与环境变量配置教程

    Maven学习----Maven安装与环境变量配置教程

    这篇文章主要给大家介绍了关于如何利用Maven入手Spring Boot第一个程序的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-06-06

最新评论