objeto - json parser java jackson
Deserialización de Json en otra jerarquÃa de clases usando Jackson (3)
Cómo resolví este problema. Aquí hay un diagrama de clase para un proyecto de ejemplo:
Así que quiero obtener el formulario ConcreteA
desde ConcreteA
después de la deserialización.
Mi solución es extender ClassNameIdResolver
para agregar funcionalidad para deserializar objetos de clase base en objetos de clase de subtipo (las clases de subtipo no agregan funcionalidad adicional y campos adicionales).
Aquí hay un código que crea ObjectMapper
para la deserialización:
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;
}
Y aquí hay un código que crea ObjectMapper
para la serialización:
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;
}
Código de prueba:
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());
}
}
Código completo de la clase 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);
}
}
Ahora estoy trabajando con Jackson y tengo algunas preguntas al respecto.
Ante todo. Tengo dos servicios, el primero es el servicio de recopilación y envío de datos y el segundo lo recibo y, por ejemplo, lo registro en un archivo.
Entonces, el primer servicio tiene una jerarquía de clases como esta:
+----ConcreteC
|
Base ----+----ConcreteA
|
+----ConcreteB
Y el segundo servicio tiene una jerarquía de clases como esta:
ConcreteAAdapter extends ConcreteA implements Adapter {}
ConcreteBAdapter extends ConcreteB implements Adapter {}
ConcreteCAdapter extends ConcreteC implements Adapter {}
El primer servicio no sabe nada sobre ConcreteXAdapter
.
La forma en que estoy enviando los datos en el primer servicio:
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);
}
}
Entonces, quiero desirializar json en la Colección de ConcreteXAdapter
, no en la Colección de ConcreteX
( ConcreteA -> ConcreteAAdapter, ConcreteB -> ConcreteBAdapter, ConcreteC -> ConcreteCAdapter
). En el caso que describí quiero obtener:
Collection [ConcreteAAdapter, ConcreteBAdapter, ConcreteCAdapter]
¿Cómo puedo hacer esto?
Me parece que el enfoque de programmerbruce es el más claro y fácil de trabajar (ejemplo a continuación). Obtuve la información de su respuesta a una pregunta relacionada: https://.com/a/6339600/1148030 y la publicación del blog relacionado: http://programmerbruce.blogspot.fi/2011/05/deserialize-json-with-jackson-into.html
También puedes ver esta página wiki amigable (también mencionada en la respuesta de Eugene Retunsky): http://wiki.fasterxml.com/JacksonPolymorphicDeserialization
Otra bonita página wiki: http://wiki.fasterxml.com/JacksonMixInAnnotations
Aquí hay un breve ejemplo para darle la idea:
Configure el ObjectMapper de esta manera:
mapper.getDeserializationConfig().addMixInAnnotations(Base.class, BaseMixin.class);
mapper.getSerializationConfig().addMixInAnnotations(Base.class, BaseMixin.class);
Ejemplo de clase BaseMixin (fácil de definir como una clase interna).
@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 {
}
En el segundo servicio podría definir la BaseMixin así:
@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 {
}
Para este propósito necesitas pasar información adicional en JSON:
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME,
include=JsonTypeInfo.As.PROPERTY, property="@type")
class Base {
...
}
Luego en la serialización agregará el campo @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",
...
}
en deserialización será:
objectMapper.registerSubtypes(
new NamedType(ConcreteA.class, "ConcreteA"),
new NamedType(ConcreteB.class, "ConcreteB"),
new NamedType(ConcreteC.class, "ConcreteC")
);
objectMapper.readValue(....)
Más aquí: http://wiki.fasterxml.com/JacksonPolymorphicDeserialization