Json deserialization into another class hierarchy using Jackson

匿名 (未验证) 提交于 2019-12-03 02:14:01

问题:

Now i'm working with Jackson and i have some questions about it.

First of all. I have two services, first is data collecting and sending service and second receive this data and, for example, log it into a file.

So, first service has class hierarchy like this:

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

And second service has class hierarchy like this:

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

The first service knows nothing about ConcreteXAdapter.

The way i'm sending the data on the first service:

Collection data = new LinkedBlockingQueue() 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 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 unmarshall(InputStream source, TypeReference typeReference) throws IOException {         return getObjectMapper().readValue(source, typeReference);     }      public  T unmarshall(byte[] json, TypeReference typeReference) throws IOException {         return getObjectMapper().readValue(json, typeReference);     } } 

So, i want to desirialize json into Collection of ConcreteXAdapter, not into Collection of ConcreteX (ConcreteA -> ConcreteAAdapter, ConcreteB -> ConcreteBAdapter, ConcreteC -> ConcreteCAdapter). In the case i described i want to get:

Collection [ConcreteAAdapter, ConcreteBAdapter, ConcreteCAdapter] 

How can i do this?

回答1:

For this purpose you need to pass additional info in JSON:

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

Then on serialization it will add @type field:

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>() {})      .writeValueAsString(someList);       {       "@type" : "ConcreteA",       ...     } 

on deserialization it will be:

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

More here: http://wiki.fasterxml.com/JacksonPolymorphicDeserialization



回答2:

How I solved this problem. Here is a class diagram for an example project:

So i want to get the ConcreteAAdapter form ConcreteA after deserialization.

My solution is to extend ClassNameIdResolver to add functionality to deserialize base class objects into subtype class objects (subtype classes adds no extra functionality and additional fields).

Here is a code which creates ObjectMapper for deserialization:

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 classes = new HashMap() {                 {                     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;     } 

And here is a code which create ObjectMapper for serialization:

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; } 

Test code:

public static void main(String[] args) throws IOException {     JacksonUtils JacksonUtils = new JacksonUtilsImpl();      Collection data = new LinkedBlockingQueue();     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>() {});      for (Adapter adapter : adapters) {         System.out.println(adapter.getClass().getName());     } } 

Full code of JacksonUtils class:

public class JacksonUtilsImpl implements JacksonUtils {      @Override     public byte[] marshall(Collection data) throws IOException {         ByteArrayOutputStream out = new ByteArrayOutputStream() {             @Override             public byte[] toByteArray() {                 return buf;             }         };          getObjectMapperForSerialization().writerWithType(new TypeReference>() {}).writeValue(out, data);         return out.toByteArray();     }      @Override     public String marshallIntoString(Collection 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 classes = new HashMap() {                 {                     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 unmarshall(InputStream source, TypeReference typeReference) throws IOException {         return getObjectMapperForDeserialization().readValue(source, typeReference);     }      @Override     public  T unmarshall(byte[] json, TypeReference typeReference) throws IOException {         return getObjectMapperForDeserialization().readValue(json, typeReference);     }      @Override     public  Collection extends T> unmarshall(String json, Class extends Collection extends T>> klass) throws IOException {         return getObjectMapperForDeserialization().readValue(json, klass);     }       @Override     public  Collection extends T> unmarshall(String json, TypeReference typeReference) throws IOException {         return getObjectMapperForDeserialization().readValue(json, typeReference);     } } 


回答3:

I find programmerbruce's approach to be the most clear and easy to get working (example below). I got the information from his answer to a related question: https://stackoverflow.com/a/6339600/1148030 and the related blog post: http://programmerbruce.blogspot.fi/2011/05/deserialize-json-with-jackson-into.html

Also check out this friendly wiki page (also mentioned in Eugene Retunsky's answer): http://wiki.fasterxml.com/JacksonPolymorphicDeserialization

Another nice wiki page: http://wiki.fasterxml.com/JacksonMixInAnnotations

Here is a short example to give you the idea:

Configure the ObjectMapper like this:

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

Example BaseMixin class (easy to define as an inner class.)

@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 { } 

On second service you could define the BaseMixin like this:

@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 { } 


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!