如何配置Jackson以默认键入反序列化命名类型? [英] How to configure Jackson to deserialize named types with default typing?

查看:123
本文介绍了如何配置Jackson以默认键入反序列化命名类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下示例:

package com.example;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;

public class JacksonDeserializationOfNamedTypes {
    public static void main(String[] args) throws Exception {
        ObjectMapper jackson = new ObjectMapper();
        jackson.enableDefaultTypingAsProperty(DefaultTyping.JAVA_LANG_OBJECT, "@type");

        Balloon redBalloon = new Balloon("red");

        String json = jackson.writeValueAsString(redBalloon); //{"@type":"Balloon","color":"red"}
        //assume the JSON could be anything
        Object deserialized = jackson.readValue(json, Object.class);

        assert deserialized instanceof Balloon;
        assert redBalloon.equals(deserialized);
    }

    @JsonTypeName("Balloon")
    @JsonTypeInfo(use = Id.NAME)
    public static final class Balloon {
        private final String color;

        //for deserialization
        private Balloon() {
            this.color = null;
        }

        public Balloon(final String color) {
            this.color = color;
        }

        public String getColor() {
            return color;
        }

        @Override
        public boolean equals(final Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            final Balloon other = (Balloon) obj;
            return this.color.equals(other.color);
        }

        @Override
        public int hashCode() {
            int result = color.hashCode();
            result = 31 * result + color.hashCode();
            return result;
        }

        @Override
        public String toString() {
            return color + " balloon";
        }
    }
}

反序列化在运行时失败以下异常:
线程main中的异常java.lang.IllegalArgumentException:无效的类型ID'Balloon'(对于id类型'Id.class'):找不到这样的类

The deserialization fails at runtime with the following exception: Exception in thread "main" java.lang.IllegalArgumentException: Invalid type id 'Balloon' (for id type 'Id.class'): no such class found

生成的JSON肯定具有Jackson正确确定类型所需的所有信息,因此如何配置ObjectMapper以正确映射气球 com.example.JacksonDeserializationOfNamedTypes $ Balloon

The produced JSON certainly has all the information Jackson needs to determine the type correctly, so how can I configure the ObjectMapper to properly map "Balloon" to com.example.JacksonDeserializationOfNamedTypes$Balloon?

推荐答案

我目前的解决方案是将自定义反序列化器和手动形成的类型名称映射组合到Java类型中:

My current solution involves a combination of a custom deserializer and a manually formed map of type names to Java types:

package com.example.jackson;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class JacksonDeserializerOfNamedTypes extends StdDeserializer<Object> {
    private final Map<String, Class<?>> typesByName;
    private final String typeProperty;

    private JacksonDeserializerOfNamedTypes(final Map<String, Class<?>> typesByName, final String typeProperty) {
        super(Object.class);

        this.typesByName = typesByName;
        this.typeProperty = typeProperty;
    }

    @Override
    public Object deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException {
        final ObjectCodec codec = parser.getCodec();
        final JsonNode root = parser.readValueAsTree();
        final JsonNode typeNameNodeOrNull = root.get(typeProperty);
        if (typeNameNodeOrNull == null) {
            throw new JsonMappingException(parser, "Unable to determine Java type of JSON: " + root);
        } else {
            final String typeName = typeNameNodeOrNull.asText();
            return Optional
                .ofNullable(typesByName.get(typeName))
                .map(type -> parseOrNull(root, type, codec))
                .orElseThrow(() ->
                    new JsonMappingException(parser, String.format(
                        "Unsupported type name '%s' in JSON: %s", typeName, root)));
        }
    }

    private <T> T parseOrNull(final JsonNode root, final Class<T> type, final ObjectCodec codec) {
        try {
            return root.traverse(codec).readValueAs(type);
        } catch (IOException e) {
            return null;
        }
    }

    public static void main(String[] args) throws Exception {
        final Map<String, Class<?>> typesByName = scanForNamedTypes();

        final SimpleModule namedTypesModule = new SimpleModule("my-named-types-module");
        namedTypesModule.addDeserializer(Object.class, new JacksonDeserializerOfNamedTypes(typesByName, JsonTypeInfo.Id.NAME.getDefaultPropertyName()));

        final Car pinto = new Car("Ford", "Pinto", 1971);
        final Balloon sharik = new Balloon("blue");
        final ObjectMapper mapper = new ObjectMapper().registerModule(namedTypesModule);
        System.out.println(mapper.readValue(mapper.writeValueAsString(pinto), Object.class).getClass());
        System.out.println(mapper.readValue(mapper.writeValueAsString(sharik), Object.class).getClass());
    }

    @JsonTypeName("Balloon")
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
    public static final class Balloon {
        public String color;

        private Balloon() {}

        public Balloon(final String color) {
            this.color = color;
        }
    }

    @JsonTypeName("Car")
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
    public static final class Car {
        public String make;
        public String model;
        public int year;

        private Car() {}

        public Car(final String make, final String model, final int year) {
            this.make = make;
            this.model = model;
            this.year = year;
        }
    }

    static Map<String, Class<?>> scanForNamedTypes() {
        //in reality, i'd be using a framework (e.g. Reflections) to scan the classpath
        //for classes tagged with @JsonTypeName to avoid maintaining manual mappings
        final Map<String, Class<?>> typesByName = new HashMap<>();
        typesByName.put("Balloon", Balloon.class);
        typesByName.put("Car", Car.class);
        return Collections.unmodifiableMap(typesByName);
    }
}

这篇关于如何配置Jackson以默认键入反序列化命名类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆