2012-04-26 36 views
18

Ahora estoy trabajando con Jackson y tengo algunas preguntas al respecto.Json deserialización en otra jerarquía de clases utilizando Jackson

Primero de todos. Tengo dos servicios, el primero es el servicio de recopilación y envío de datos y el segundo recibir estos datos y, por ejemplo, registrarlo en un archivo.

Así, el primer servicio tiene jerarquía de clases como esto:

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

Y segundo servicio tiene jerarquía de clases como esto:

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

Por lo tanto, quiero desirialize JSON en 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?

Respuesta

15

Cómo he resuelto este problema. Aquí es un diagrama de clases para un proyecto de ejemplo: class diagram

así que quiero conseguir el ConcreteAAdapter forma ConcreteA después de deserialización.

Mi solución es ampliar ClassNameIdResolver para agregar la funcionalidad para deserializar los objetos de la clase base en los objetos de clase de subtipo (las clases de subtipo no agrega funcionalidad adicional y campos adicionales).

Aquí es un código que crea ObjectMapper de 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í es un código que crean 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 ensayo:

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

completa código 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); 
    } 
} 
27

Para ello se necesita pasar información adicional en JSON:

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

A continuación, en la serialización que se sumará @type campo:

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

+0

¡Gracias por su rápida respuesta! Actualicé la pregunta. En resumen, el primer servicio no sabe nada sobre 'ConcreteXAdapter'. Entonces la pregunta es cómo decirle a Jackson que cree 'ConcreteXAdapter' si encuentra' ConcreteX' en json. – pbespechnyi

+1

En este caso, sugeriría pasar el tipo manualmente (como "@type": "ConcreteA") y luego deserializar en el otro lado en función de la propiedad. Es decir. necesita implementar un serializador personalizado para esto. –

+0

Así que no hay forma de hacerlo de la caja. ¡De acuerdo, gracias por la ayuda! – pbespechnyi

9

Encuentro el enfoque de programmerbruce como el más claro y fácil de trabajar (ejemplo a continuación). Tengo la información de su respuesta a una pregunta relacionada: https://stackoverflow.com/a/6339600/1148030 y la entrada de blog relacionada: http://programmerbruce.blogspot.fi/2011/05/deserialize-json-with-jackson-into.html

También puedes ver esta página wiki amigable (también mencionado en la respuesta de Eugene Retunsky): http://wiki.fasterxml.com/JacksonPolymorphicDeserialization

otra página wiki agradable: http://wiki.fasterxml.com/JacksonMixInAnnotations

Aquí está un ejemplo corto para darle la idea:

Configurar el ObjectMapper como esto: (. Fácil de definir como una clase interna)

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

clase Ejemplo 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 { 
} 

En segundo servicio se 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 { 
} 
+0

Su solución es más limpia y correcta para el primer vistazo. Ahora no puedo decir si es adecuado para mí, ya que fue hace mucho tiempo. Pero de todos modos gracias por la respuesta! – pbespechnyi

Cuestiones relacionadas