2012-04-26 1 views
18

Теперь я работаю с Джексоном, и у меня есть некоторые вопросы об этом.Дессериализация Json в другую иерархию классов с использованием Jackson

Прежде всего. У меня есть две службы, сначала это сбор данных и отправка службы, а во-вторых, получение этих данных и, например, запись в файл.

Таким образом, первый сервис имеет иерархию классов, как это:

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

И второй сервис имеет иерархию классов, как это:

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

Первая служба ничего не знает о ConcreteXAdapter.

Путь я шлю данные на первый сервис:

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

Итак, я хочу desirialize в JSON Коллекция ConcreteXAdapter, а не в коллекции ConcreteX (ConcreteA -> ConcreteAAdapter, ConcreteB -> ConcreteBAdapter, ConcreteC -> ConcreteCAdapter). В случае, который я описал, я хочу получить:

Collection [ConcreteAAdapter, ConcreteBAdapter, ConcreteCAdapter] 

Как я могу это сделать?

ответ

15

Как я решил эту проблему. Вот диаграмма классов для примера проекта: class diagram

Так я хочу, чтобы получить ConcreteAAdapter форму ConcreteA после десериализации.

Мое решение заключается в расширении ClassNameIdResolver, чтобы добавить функциональность десериализации объектов базового класса в объекты класса подтипа (классы подтипов не добавили дополнительной функциональности и дополнительных полей).

Вот код, который создает ObjectMapper для десериализации:

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

А вот код, который создают ObjectMapper для сериализации:

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

Код испытания:

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

Полный код 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

Для этого вам необходимо пройти дополнительные данные в формате JSON:

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

Тогда сериализацию это добавит @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", 
     ... 
    } 

на десериализации будет:

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

Подробнее: http://wiki.fasterxml.com/JacksonPolymorphicDeserialization

+0

Спасибо за быстрый ответ! Я обновил вопрос. Вкратце, первая служба ничего не знает о «ConcreteXAdapter». Поэтому вопрос заключается в том, как сказать Джексону создать «ConcreteXAdapter», если он найдет «ConcreteX» в json. – pbespechnyi

+1

В этом случае я бы предложил передать тип вручную (например, «@type»: «ConcreteA»), а затем десериализовать с другой стороны на основе свойства. То есть для этого вам необходимо реализовать собственный сериализатор. –

+0

Таким образом, нет возможности сделать это из коробки. Хорошо, спасибо за помощь! – pbespechnyi

9

Я считаю, что подход programmerbruce является самым ясным и легким в работе (пример ниже). я получил информацию от своего ответа на соответствующий вопрос: https://stackoverflow.com/a/6339600/1148030 и связанный с ним блоге: http://programmerbruce.blogspot.fi/2011/05/deserialize-json-with-jackson-into.html

также проверить эту дружественную вики-страницу (также упоминается в ответ Евгений Retunsky в): http://wiki.fasterxml.com/JacksonPolymorphicDeserialization

Еще одна приятная вики-страница: http://wiki.fasterxml.com/JacksonMixInAnnotations

Вот небольшой пример, чтобы дать вам идею:

Настройка ObjectMapper как это: (. Легко определить как внутренний класс)

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

Пример 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 { 
} 

На второй службы можно определить BaseMixin так:

@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

Ваше решение более чистое и правильное для первого взгляда. Теперь я не могу сказать, что это подходит для меня, так как это было давно. Но в любом случае спасибо за ответ! – pbespechnyi

Смежные вопросы