Json deserialization into other class hierarchy using Jackson
deserialization, jackson, java, json, serialization
Solution
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<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;
}
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<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());
}
}
Full code of JacksonUtils class:
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);
}
}
Problem
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<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); } } ``` 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?