【问题标题】:Json deserialization into other class hierarchy using Jackson使用 Jackson 将 Json 反序列化为其他类层次结构
【发布时间】:2012-05-06 23:11:48
【问题描述】:

现在我正在与 Jackson 合作,我对此有一些疑问。

首先。我有两个服务,第一个是数据收集和发送服务,第二个是接收这些数据,例如,将其记录到文件中。

所以,第一个服务的类层次结构如下:

         +----ConcreteC
         |
Base ----+----ConcreteA
         |
         +----ConcreteB

第二个服务的类层次结构如下:

ConcreteAAdapter extends ConcreteA implements Adapter {}
ConcreteBAdapter extends ConcreteB implements Adapter {}
ConcreteCAdapter extends ConcreteC implements Adapter {}

第一个服务对ConcreteXAdapter一无所知。

我在第一个服务上发送数据的方式:

Collection<Base> data = new LinkedBlockingQueue<Base>()
JacksonUtils utils = new JacksonUtils();
data.add(new ConcreteA());
data.add(new ConcreteB());
data.add(new ConcreteC());
...
send(utils.marshall(data));
...

public class JacksonUtils {

    public byte[] marshall(Collection<Base> data) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream() {
            @Override
            public byte[] toByteArray() {
                return buf;
            }
        };

        getObjectMapper().writeValue(out, data);
        return out.toByteArray();
    }
    protected ObjectMapper getObjectMapper() {
        return new ObjectMapper();
    }

    public Object unmarshall(byte[] json) throws IOException {
        return getObjectMapper().readValue(json, Object.class);
    }

    public <T> T unmarshall(InputStream source, TypeReference<T> typeReference) throws IOException {
        return getObjectMapper().readValue(source, typeReference);
    }

    public <T> T unmarshall(byte[] json, TypeReference<T> typeReference) throws IOException {
        return getObjectMapper().readValue(json, typeReference);
    }
}

所以,我想将 json 反序列化为 ConcreteXAdapter 的集合,而不是 ConcreteX (ConcreteA -&gt; ConcreteAAdapter, ConcreteB -&gt; ConcreteBAdapter, ConcreteC -&gt; ConcreteCAdapter) 的集合。在我描述的情况下,我想得到:

Collection [ConcreteAAdapter, ConcreteBAdapter, ConcreteCAdapter]

我该怎么做?

【问题讨论】:

    标签: java json serialization jackson deserialization


    【解决方案1】:

    为此,您需要在 JSON 中传递附加信息:

    @JsonTypeInfo(use=JsonTypeInfo.Id.NAME, 
          include=JsonTypeInfo.As.PROPERTY, property="@type")
    class Base {
    ...
    }
    

    然后在序列化时会添加@type 字段:

    objectMapper.registerSubtypes(
                new NamedType(ConcreteAAdapter.class, "ConcreteA"),
                new NamedType(ConcreteBAdapter.class, "ConcreteB"),
                new NamedType(ConcreteCAdapter.class, "ConcreteC")
                );
    
    // note, that for lists you need to pass TypeReference explicitly
    objectMapper.writerWithType(new TypeReference<List<Base>>() {})
         .writeValueAsString(someList);
    
    
        {
          "@type" : "ConcreteA",
          ...
        }
    

    在反序列化时它将是:

        objectMapper.registerSubtypes(
                new NamedType(ConcreteA.class, "ConcreteA"),
                new NamedType(ConcreteB.class, "ConcreteB"),
                new NamedType(ConcreteC.class, "ConcreteC")
                );
        objectMapper.readValue(....)
    

    More info here

    【讨论】:

    • 感谢您的快速回复!我更新了问题。简而言之,第一个服务对ConcreteXAdapter 一无所知。所以问题是如果在json中找到ConcreteX,如何告诉Jackson创建ConcreteXAdapter
    • 在这种情况下,我建议手动传递类型(如“@type”:“ConcreteA”),然后根据属性在另一侧反序列化。 IE。您需要为此实现一个自定义序列化程序。
    • 所以没有办法开箱即用。好的,谢谢你的帮助!
    • @Eugene 有支持:只需使用 @JsonTypeName 和 'As.NAME' -- 不是类名,如果它们不同的话。然后您需要注册子类型(通过调用ObjectMapper.registerSubtypes(),或使用@JsonSubTypes 注释)。
    • @StaxMan - 感谢您的提示。它完美地工作。我更新了我的答案。
    【解决方案2】:

    我是如何解决这个问题的。这是示例项目的类图:

    所以我想在反序列化后得到ConcreteAAdapter 表单ConcreteA

    我的解决方案是扩展ClassNameIdResolver 以添加将基类对象反序列化为子类型类对象的功能(子类型类不添加额外功能和额外字段)。

    这是一个创建 ObjectMapper 用于反序列化的代码:

    protected ObjectMapper getObjectMapperForDeserialization() {
            ObjectMapper mapper = new ObjectMapper();
    
            StdTypeResolverBuilder typeResolverBuilder = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
            typeResolverBuilder = typeResolverBuilder.inclusion(JsonTypeInfo.As.PROPERTY);
            typeResolverBuilder.init(JsonTypeInfo.Id.CLASS, new ClassNameIdResolver(SimpleType.construct(Base.class), TypeFactory.defaultInstance()) {
                private HashMap<Class, Class> classes = new HashMap<Class, Class>() {
                    {
                        put(ConcreteA.class, ConcreteAAdapter.class);
                        put(ConcreteB.class, ConcreteBAdapter.class);
                        put(ConcreteC.class, ConcreteCAdapter.class);
                    }
                };
    
                @Override
                public String idFromValue(Object value) {
                    return (classes.containsKey(value.getClass())) ? value.getClass().getName() : null;
                }
    
                @Override
                public JavaType typeFromId(String id) {
                    try {
                        return classes.get(Class.forName(id)) == null ? super.typeFromId(id) : _typeFactory.constructSpecializedType(_baseType, classes.get(Class.forName(id)));
                    } catch (ClassNotFoundException e) {
                        // todo catch the e
                    }
                    return super.typeFromId(id);
                }
            });
            mapper.setDefaultTyping(typeResolverBuilder);
            return mapper;
        }
    

    这是一个创建ObjectMapper 用于序列化的代码:

    protected ObjectMapper getObjectMapperForSerialization() {
        ObjectMapper mapper = new ObjectMapper();
    
        StdTypeResolverBuilder typeResolverBuilder = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
        typeResolverBuilder = typeResolverBuilder.inclusion(JsonTypeInfo.As.PROPERTY);
        typeResolverBuilder.init(JsonTypeInfo.Id.CLASS, new ClassNameIdResolver(SimpleType.construct(Base.class), TypeFactory.defaultInstance()));
        mapper.setDefaultTyping(typeResolverBuilder);
    
        return mapper;
    }
    

    测试代码:

    public static void main(String[] args) throws IOException {
        JacksonUtils JacksonUtils = new JacksonUtilsImpl();
    
        Collection<Base> data = new LinkedBlockingQueue<Base>();
        data.add(new ConcreteA());
        data.add(new ConcreteB());
        data.add(new ConcreteC());
    
        String json = JacksonUtils.marshallIntoString(data);
    
        System.out.println(json);
    
        Collection<? extends Adapter> adapters = JacksonUtils.unmarshall(json, new TypeReference<ArrayList<Adapter>>() {});
    
        for (Adapter adapter : adapters) {
            System.out.println(adapter.getClass().getName());
        }
    }
    

    JacksonUtils 类的完整代码:

    public class JacksonUtilsImpl implements JacksonUtils {
    
        @Override
        public byte[] marshall(Collection<Base> data) throws IOException {
            ByteArrayOutputStream out = new ByteArrayOutputStream() {
                @Override
                public byte[] toByteArray() {
                    return buf;
                }
            };
    
            getObjectMapperForSerialization().writerWithType(new TypeReference<Collection<Base>>() {}).writeValue(out, data);
            return out.toByteArray();
        }
    
        @Override
        public String marshallIntoString(Collection<Base> data) throws IOException {
            return getObjectMapperForSerialization().writeValueAsString(data);
        }
    
        protected ObjectMapper getObjectMapperForSerialization() {
            ObjectMapper mapper = new ObjectMapper();
    
            StdTypeResolverBuilder typeResolverBuilder = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
            typeResolverBuilder = typeResolverBuilder.inclusion(JsonTypeInfo.As.PROPERTY);
            typeResolverBuilder.init(JsonTypeInfo.Id.CLASS, new ClassNameIdResolver(SimpleType.construct(Base.class), TypeFactory.defaultInstance()));
            mapper.setDefaultTyping(typeResolverBuilder);
    
            return mapper;
        }
    
        protected ObjectMapper getObjectMapperForDeserialization() {
            ObjectMapper mapper = new ObjectMapper();
    
            StdTypeResolverBuilder typeResolverBuilder = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
            typeResolverBuilder = typeResolverBuilder.inclusion(JsonTypeInfo.As.PROPERTY);
            typeResolverBuilder.init(JsonTypeInfo.Id.CLASS, new ClassNameIdResolver(SimpleType.construct(Base.class), TypeFactory.defaultInstance()) {
                private HashMap<Class, Class> classes = new HashMap<Class, Class>() {
                    {
                        put(ConcreteA.class, ConcreteAAdapter.class);
                        put(ConcreteB.class, ConcreteBAdapter.class);
                        put(ConcreteC.class, ConcreteCAdapter.class);
                    }
                };
    
                @Override
                public String idFromValue(Object value) {
                    return (classes.containsKey(value.getClass())) ? value.getClass().getName() : null;
                }
    
                @Override
                public JavaType typeFromId(String id) {
                    try {
                        return classes.get(Class.forName(id)) == null ? super.typeFromId(id) : _typeFactory.constructSpecializedType(_baseType, classes.get(Class.forName(id)));
                    } catch (ClassNotFoundException e) {
                        // todo catch the e
                    }
                    return super.typeFromId(id);
                }
            });
            mapper.setDefaultTyping(typeResolverBuilder);
            return mapper;
        }
    
        @Override
        public Object unmarshall(byte[] json) throws IOException {
            return getObjectMapperForDeserialization().readValue(json, Object.class);
        }
    
        @Override
        public <T> T unmarshall(InputStream source, TypeReference<T> typeReference) throws IOException {
            return getObjectMapperForDeserialization().readValue(source, typeReference);
        }
    
        @Override
        public <T> T unmarshall(byte[] json, TypeReference<T> typeReference) throws IOException {
            return getObjectMapperForDeserialization().readValue(json, typeReference);
        }
    
        @Override
        public <T> Collection<? extends T> unmarshall(String json, Class<? extends Collection<? extends T>> klass) throws IOException {
            return getObjectMapperForDeserialization().readValue(json, klass);
        }
    
    
        @Override
        public <T> Collection<? extends T> unmarshall(String json, TypeReference typeReference) throws IOException {
            return getObjectMapperForDeserialization().readValue(json, typeReference);
        }
    }
    

    【讨论】:

    • 这是一个更简洁的解决方案,有时无法向 json 消息添加额外信息,例如对于客户已经在使用的现有 REST API 或自动生成类时,例如开放API
    【解决方案3】:

    我发现programmerbruce 的方法是最清晰和最容易上手的(示例如下)。 我从他对相关问题的回答中得到了信息: https://stackoverflow.com/a/6339600/1148030 和相关的博客文章: http://programmerbruce.blogspot.fi/2011/05/deserialize-json-with-jackson-into.html

    还可以查看这个友好的 wiki 页面(在 Eugene Retunsky 的回答中也提到过): https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization

    另一个不错的 wiki 页面:https://github.com/FasterXML/jackson-docs/wiki/JacksonMixInAnnotations

    这里有一个简短的例子来告诉你这个想法:

    像这样配置 ObjectMapper:

        mapper.getDeserializationConfig().addMixInAnnotations(Base.class, BaseMixin.class);
        mapper.getSerializationConfig().addMixInAnnotations(Base.class, BaseMixin.class);
    

    BaseMixin 类示例(易于定义为内部类。)

    @JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
    @JsonSubTypes({
        @JsonSubTypes.Type(value=ConcreteA.class, name="ConcreteA"),
        @JsonSubTypes.Type(value=ConcreteB.class, name="ConcreteB")
    })  
    private static class BaseMixin {
    }
    

    在第二个服务中,您可以像这样定义 BaseMixin:

    @JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
    @JsonSubTypes({
        @JsonSubTypes.Type(value=ConcreteAAdapter.class, name="ConcreteA"),
        @JsonSubTypes.Type(value=ConcreteBAdapter.class, name="ConcreteB")
    })  
    private static class BaseMixin {
    }
    

    【讨论】:

    猜你喜欢
    • 2019-04-09
    • 2021-05-06
    • 2017-02-21
    • 1970-01-01
    • 1970-01-01
    • 2016-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多