2015-12-10 5 views
8

Jackson macht etwas wirklich Bizarres und ich kann keine Erklärung dafür finden. Ich mache polymorphe Serialisierung und es funktioniert perfekt, wenn ein Objekt für sich allein ist. Wenn Sie jedoch dasselbe Objekt in eine Liste einfügen und stattdessen die Liste serialisieren, wird die Typinformation gelöscht.Warum funktioniert die polymorphe Jackson-Serialisierung nicht in Listen?

Die Tatsache, dass es Typinfo verliert, würde dazu führen, dass man Typauslöschung vermutet. Dies geschieht jedoch während der Serialisierung des Inhalts der Liste; Alles, was Jackson tun muss, ist das aktuelle Objekt, das serialisiert wird, zu inspizieren, um seinen Typ zu bestimmen.

Ich habe ein Beispiel unter Verwendung von Jackson 2.5.1 erstellt:

import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 
import com.fasterxml.jackson.annotation.JsonSubTypes; 
import com.fasterxml.jackson.annotation.JsonSubTypes.Type; 
import com.fasterxml.jackson.annotation.JsonTypeInfo; 
import com.fasterxml.jackson.annotation.JsonTypeName; 
import com.fasterxml.jackson.core.JsonProcessingException; 
import com.fasterxml.jackson.databind.ObjectMapper; 

import java.util.ArrayList; 
import java.util.List; 

public class Test { 

    @JsonIgnoreProperties(ignoreUnknown = true) 
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY) 
    @JsonSubTypes({ 
    @Type(value = Dog.class, name = "dog"), 
    @Type(value = Cat.class, name = "cat")}) 
    public interface Animal {} 

    @JsonTypeName("dog") 
    public static class Dog implements Animal { 
    private String name; 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 
    } 

    @JsonTypeName("cat") 
    public static class Cat implements Animal { 
    private String name; 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 
    } 

    public static void main(String[] args) throws JsonProcessingException { 
    List<Cat> list = new ArrayList<>(); 
    list.add(new Cat()); 
    System.out.println(new ObjectMapper().writeValueAsString(list)); 
    System.out.println(new ObjectMapper().writeValueAsString(list.get(0))); 
    } 
} 

Hier ist der Ausgang:

[{"name":null}] 
{"@type":"cat","name":null} 

Wie Sie sehen können, Jackson ist das Hinzufügen nicht die Typinformationen, wenn das Objekt in einer Liste. Weiß jemand, warum das passiert?

Antwort

13

Die verschiedenen Gründe, warum dies passiert, werden diskutiert here und here. Ich stimme nicht unbedingt mit den Gründen überein, aber Jackson, wegen der Art Löschung, nicht aus der Fledermaus wissen die Art der Elemente List (oder Collection oder) enthält. Es wählt einen einfachen Serializer, der Ihre Anmerkungen nicht interpretiert.

Sie haben zwei in dieser Links vorgeschlagen Optionen:

Erstens können Sie eine Klasse erstellen, die List<Cat> implementiert, instanziiert es die Instanz in geeigneter Weise und serialisiert werden.

class CatList implements List<Cat> {...} 

Der generische Typ Argument Cat nicht verloren. Jackson hat Zugriff darauf und benutzt es.

Zweitens können Sie einen ObjectWriter für den Typ List<Cat> instanziieren und verwenden. Zum Beispiel

System.out.println(new ObjectMapper().writerFor(new TypeReference<List<Cat>>() {}).writeValueAsString(list)); 

druckt

[{"@type":"cat","name":"heyo"}] 
+0

Sie wie 'neue ObjectMapper können auch() writerFor (mapper.getTypeFactory() constructCollectionType (List.class, Tier.. Klasse)). writeValueAsString (Liste) ' –

+0

Das ist wirklich seltsam, und ich kann mir nicht vorstellen, warum sie diese Wahl treffen würden. Unabhängig vom Typ der Liste würden Sie denken, dass die Objekte, die darin enthalten sind, auf die gleiche Weise serialisiert werden. – monitorjbl

+0

mapper.writerWithType (mapper.getTypeFactory(). ConstructCollectionType (List.class, Animal.class)). WriteValueAsString (list) – Youness

11

Die Antwort Sotirios Delimanolis gab die richtige ist. Ich dachte jedoch, es wäre nett, diese Problemumgehung als separate Antwort zu veröffentlichen. Wenn Sie sich in einer Umgebung befinden, in der Sie den ObjectMapper nicht für jede Art von Sache ändern können, die Sie zurückgeben müssen (wie eine Jersey/SpringMVC-Webanwendung), gibt es eine Alternative.

Sie können einfach ein privates abschließendes Feld in die Klasse einfügen, die den Typ enthält. Das Feld ist für nichts außerhalb der Klasse sichtbar, aber wenn Sie es mit @JsonProperty("@type") (oder "@class" oder was auch immer Ihr Typ-Feld genannt wird) annotieren, wird Jackson es serialisieren, unabhängig davon, wo sich das Objekt befindet.

@JsonTypeName("dog") 
public static class Dog implements Animal { 
    @JsonProperty("@type") 
    private final String type = "dog"; 
    private String name; 

    public String getName() { 
    return name; 
    } 

    public void setName(String name) { 
    this.name = name; 
    } 
} 
+0

Ich benutze Interfaces anstelle von Klassen (Spring Projection). Das hat nicht für mich funktioniert. Irgendwelche Vorschläge bitte? –

+0

Es funktioniert für mich, aber wenn ich ein Element serialisierte, ist @Type Feld dupliziert. –

0

Als Arrays Sie keine Typ Löschung verwenden können lösen sie die ObjectMapper.writeValueAsString Überschreiben der Sammlung in ein Array zu verwandeln.

public class CustomObjectMapper extends ObjectMapper {  
    @Override 
     public String writeValueAsString(Object value) throws JsonProcessingException { 
       //Transform Collection to Array to include type info 
       if(value instanceof Collection){ 
        return super.writeValueAsString(((Collection)value).toArray()); 
       } 
       else 
        return super.writeValueAsString(value); 
      } 
} 

Verwenden Sie es in Ihrem Test.Haupt:

System.out.println(new CustomObjectMapper().writeValueAsString(list)); 

Ausgang (Katzen und Hunde Liste):.

[{"@type":"cat","name":"Gardfield"},{"@type":"dog","name":"Snoopy"}] 
Verwandte Themen