可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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 { }