2014-07-22 2 views
10

Ich habe folgende JSON:Deserialisieren nicht für eine Klasse Umsetzung Kollektion mit Jackson

{ 
    "item": [ 
    { "foo": 1 }, 
    { "foo": 2 } 
    ] 
} 

Dies ist im Grunde ein Objekt, das eine Auflistung von Elementen enthält.

Also machte ich eine Klasse deserialisieren, dass:

public class ItemList { 
    @JsonProperty("item") 
    List<Item> items; 

    // Getters, setters & co. 
    // ... 
} 

Alles schön bis zu diesem Punkt arbeitet.

Nun, um mein Leben woanders einfacher zu machen, entschied ich, dass es schön wäre, auf dem ItemList-Objekt zu iterieren und es die Collection-Schnittstelle zu implementieren.

Also im Grunde wurde meine Klasse:

public class ItemList implements Collection<Item>, Iterable<Item> { 
    @JsonProperty("item") 
    List<Item> items; 

    // Getters, setters & co. 

    // Generated all method delegates to items. For instance: 
    public Item get(int position) { 
    return items.get(position); 
    } 
} 

Die Umsetzung funktioniert und schön. Die Deserialisierung schlägt jedoch fehl.

Sieht aus wie Jackson ist verwirrt:

com.fasterxml.jackson.databind.JsonMappingException: Kann nicht Instanz com.example.ItemList aus START_OBJECT deserialisieren Token

Ich habe versucht, @JsonDeserialize(as=ItemList.class) hinzuzufügen, aber es hat nicht den Trick gemacht.

Was ist der Weg zu gehen?

+0

Ich bin ziemlich sicher, dass http://stackoverflow.com/users/22656/jon-skeet Lage sein wird, diese zu beantworten. Darüber hinaus können Sie unter http://stackoverflow.com/a/21279016/1382251 nützliche Informationen finden. –

+1

Während dies eine gute Frage ist, wie es ist, wenn alles, was Sie wollen, iterieren, warum machen Sie Ihre Klasse eine Sammlung statt nur iterierbar? – chrylis

+0

@chrylis: so dass ich 'für (Artikel t: itemList)' –

Antwort

4

Offensichtlich funktioniert es nicht, weil Jackson die Standard-Sammlung Deserialiser für Java Sammlungstypen verwendet, die nichts über ItemList Eigenschaften weiß.

Es ist möglich, es funktioniert, aber nicht auf eine sehr elegante Art und Weise. Sie müssen ObjectMapper konfigurieren, um den Standard-Deserialiser für die Sammlung eines Bean-Deserialisers zu ersetzen, der manuell für den entsprechenden Typ erstellt wurde. Ich habe ein Beispiel geschrieben, das dies in BeanDeserializerModifier für alle Klassen tut, die mit einer benutzerdefinierten Anmerkung kommentiert werden.

Bitte beachte, dass ich ObjectMapper außer Kraft zu setzen Zugang zu einem geschützten Verfahren createDeserializationContext von ObjectMapper bekommt einen richtigen Deserialisation Kontext zu schaffen, da die Bohne Modifikator keinen Zugriff darauf hat. Hier

ist der Code:

public class JacksonCustomList { 
    public static final String JSON = "{\n" + 
      " \"item\": [\n" + 
      " { \"foo\": 1 },\n" + 
      " { \"foo\": 2 }\n" + 
      " ]\n" + 
      "} "; 

    @Retention(RetentionPolicy.RUNTIME) 
    public static @interface PreferBeanDeserializer { 

    } 

    public static class Item { 
     public int foo; 

     @Override 
     public String toString() { 
      return String.valueOf(foo); 
     } 
    } 

    @PreferBeanDeserializer 
    public static class ItemList extends ArrayList<Item> { 
     @JsonProperty("item") 
     public List<Item> items; 

     @Override 
     public String toString() { 
      return items.toString(); 
     } 
    } 

    public static class Modifier extends BeanDeserializerModifier { 
     private final MyObjectMapper mapper; 

     public Modifier(final MyObjectMapper mapper) { 
      this.mapper = mapper; 
     } 

     @Override 
     public JsonDeserializer<?> modifyCollectionDeserializer(
       final DeserializationConfig config, 
       final CollectionType type, 
       final BeanDescription beanDesc, 
       final JsonDeserializer<?> deserializer) { 
      if (type.getRawClass().getAnnotation(PreferBeanDeserializer.class) != null) { 
       DeserializationContext context = mapper.createContext(config); 
       try { 
        return context.getFactory().createBeanDeserializer(context, type, beanDesc); 
       } catch (JsonMappingException e) { 
        throw new IllegalStateException(e); 
       } 

      } 
      return super.modifyCollectionDeserializer(config, type, beanDesc, deserializer); 
     } 
    } 

    public static class MyObjectMapper extends ObjectMapper { 
     public DeserializationContext createContext(final DeserializationConfig cfg) { 
      return super.createDeserializationContext(getDeserializationContext().getParser(), cfg); 
     } 
    } 

    public static void main(String[] args) throws IOException { 
     final MyObjectMapper mapper = new MyObjectMapper(); 
     SimpleModule module = new SimpleModule(); 
     module.setDeserializerModifier(new Modifier(mapper)); 

     mapper.registerModule(module); 
     System.out.println(mapper.readValue(JSON, ItemList.class)); 
    } 

} 
+1

+1 Schön. Ich habe ein paar Stunden damit verbracht, eine ähnliche Lösung mit 'BeanDeserializerModifier.modifyCollectionDeserializer' zu finden, konnte aber nicht herausfinden, wie man einen' DeserializationContext' erhält. –

+0

Sieht wie eine elegante Art aus, damit umzugehen, werde ich es versuchen. –

+1

Ein Vorschlag: anstelle von Sub-Classing 'ObjectMapper' sollten Sie in der Lage sein,' ContextualDeserializer' mit einem benutzerdefinierten Collection-Deserializer zu kombinieren: Dies ermöglicht Ihnen das Finden/Ändern von Wert-Deserialisierer und garantiert, dass es richtig initialisiert wird. Es kann sinnvoll sein, einen Blick auf Standard-Collection-Serialisierer zu werfen, um einige spezielle Fälle zu behandeln (Unterstützung für polymorphe Typen). – StaxMan

2

Wenn Sie die item Eigenschaft betrachten die Wurzel Wert sein, können Sie als Ihre ItemList Klasse wie folgt ändern, die mit @JsonRootNameannotation:

@JsonRootName("item") 
public class ItemList implements Collection<Item>, Iterable<Item> { 
    private List<Item> items = new ArrayList<>(); 

    public Item get(int position) { 
     return items.get(position); 
    } 

    // implemented methods deferring to delegate 
    // ... 
} 

Wenn Sie dann die Aktivierung UNWRAP_ROOT_VALUEdeserialization feature, Dinge wie erwartet :

String json = "{\"item\": [{\"foo\": 1}, {\"foo\": 2}]}"; 
ObjectMapper mapper = new ObjectMapper(); 
ObjectReader reader = mapper.reader(ItemList.class); 

ItemList itemList = reader 
     .with(DeserializationFeature.UNWRAP_ROOT_VALUE) 
     .readValue(json); 

Serialisierung funktioniert genauso gut mit dem WRAP_ROOT_VALUEserialization feature aktiviert:

ObjectMapper mapper = new ObjectMapper(); 
ObjectWriter writer = mapper.writer(); 

Item item1 = new Item(); 
item1.setFoo(1); 

Item item2 = new Item(); 
item2.setFoo(2); 

ItemList itemList = new ItemList(); 
itemList.add(item1); 
itemList.add(item2); 

String json = writer 
     .with(SerializationFeature.WRAP_ROOT_VALUE) 
     .writeValueAsString(itemList); 

// json contains {"item":[{"foo":1},{"foo":2}]} 

Diese Lösung genügt offensichtlich nicht, wenn Ihre ItemList zusätzliche Eigenschaften (andere als die aktuelle Liste) enthält, die auch serialisiert/deserialisiert werden müssen.

+0

Schöne Abhilfe, aber ich möchte die Möglichkeit behalten, zusätzliche Eigenschaften für die Zukunft zu behandeln. –

Verwandte Themen