2015-12-10 9 views
8

Jackson robi coś naprawdę dziwnego i nie mogę znaleźć żadnego wyjaśnienia. Robię serializację polimorficzną i działa doskonale, gdy obiekt jest sam. Ale jeśli umieścisz ten sam obiekt na liście i zamiast niego zmienisz katalog, usunie on informacje o typie.Dlaczego serializacja polimorficzna Jacksona nie działa na listach?

Fakt, że traci informacje o typie, może spowodować podejrzenie, że typ został usunięty. Ale dzieje się to podczas serializacji zawartości listy; wszystko, co Jackson musi zrobić, to sprawdzenie aktualnego obiektu, który serializuje, aby określić jego typ.

Utworzyłem przykład używając Jackson 2.5.1:

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

Oto wynik:

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

Jak widać, Jackson nie jest dodanie informacji o typie gdy obiekt jest na liście. Czy ktoś wie, dlaczego tak się dzieje?

Odpowiedz

13

Różne powody, dla których tak się dzieje, są omówione here i here. Niekoniecznie zgadzam się z powodami, ale Jackson, ze względu na typ wymazania, nie ma od nietoperza znać typ elementów zawartych w List (lub Collection lub Map). Decyduje się na użycie prostego serializera, który nie interpretuje adnotacji.

Masz dwie opcje zasugerowane w tych linków:

Po pierwsze, można utworzyć klasę, która implementuje List<Cat>, oznacz ją odpowiednio i serializacji instancji.

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

Ogólny argument typu Cat nie został utracony. Jackson ma do niego dostęp i używa go.

Po drugie, można utworzyć instancję i użyć ObjectWriter dla typu List<Cat>. Na przykład

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

wypisze

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

Również można użyć jak 'nowej ObjectMapper() writerFor (mapper.getTypeFactory() constructCollectionType (List.class, zwierząt.. klasa)). writeValueAsString (lista) ' –

+0

To jest naprawdę dziwne i nie mogę sobie wyobrazić, dlaczego mieliby dokonać takiego wyboru. Niezależnie od rodzaju listy, można by sądzić, że obiekty, które są w niej zawarte, będą serializowane w ten sam sposób przez cały czas. – monitorjbl

+0

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

11

Odpowiedź Sotirios Delimanolis dał jest prawidłowa. Jednak pomyślałem, że byłoby miło opublikować to obejście jako osobną odpowiedź. jeśli jesteś w środowisku, w którym nie możesz zmienić ObjectMappera dla każdego typu rzeczy, którą powinieneś zwrócić (jak aplikacja webowa Jersey/SpringMVC), istnieje alternatywa.

Możesz po prostu dołączyć prywatne ostatnie pole do klasy zawierającej typ. To pole nie będzie widoczne dla niczego poza klasą, ale jeśli dodasz adnotację do niego z @JsonProperty("@type") (lub "@class" lub cokolwiek innego, jakie ma twoje pole typu), Jackson dokona serializacji, niezależnie od tego, gdzie znajduje się obiekt.

@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

Używam interfejsów zamiast klas (Spring Projection). To nie działa dla mnie. Wszelkie sugestie proszę? –

+0

To działa dla mnie, ale kiedy serializowałem jeden element, pole @typu jest duplikowane. –

0

jako tablice nie używaj typu skasowaniu można rozwiązać to nadpisanie ObjectMapper.writeValueAsString przekształcić kolekcja do tablicy.

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

Użyj go do testu.Głównym:

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

wyjściowa (koty i psy lista).

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