2015-12-10 15 views
8

Jackson sta facendo qualcosa di veramente bizzarro e non riesco a trovare alcuna spiegazione per questo. Sto facendo serializzazione polimorfica e funziona perfettamente quando un oggetto è da solo. Ma se metti lo stesso oggetto in una lista e serializzi la lista, cancella le informazioni sul tipo.Perché la serializzazione polimorfa di Jackson non funziona negli elenchi?

Il fatto che stia perdendo informazioni sul tipo porterebbe a sospettare la cancellazione del tipo. Ma questo sta accadendo durante la serializzazione del contenuto della lista; tutto ciò che Jackson deve fare è ispezionare l'oggetto corrente che sta serializzando per determinare il suo tipo.

ho creato un esempio utilizzando 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))); 
    } 
} 

ecco l'output:

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

Come si può vedere, Jackson non aggiunge le informazioni sul tipo quando l'oggetto è in una lista. Qualcuno sa perché questo sta accadendo?

risposta

13

I vari motivi per cui questo accade sono discussi here e here. Non sono necessariamente d'accordo con le ragioni, ma Jackson, a causa della cancellazione del tipo, non dal pipistrello conosce il tipo di elementi contiene List (o Collection o Map). Sceglie di utilizzare un serializzatore semplice che non interpreti le annotazioni.

avete due opzioni suggerite in questi link:

In primo luogo, è possibile creare una classe che implementa List<Cat>, un'istanza in modo appropriato e serializzare l'istanza.

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

Il tipo di argomento generico Cat non tutto è perduto. Jackson ha accesso ad esso e lo usa.

In secondo luogo, è possibile creare un'istanza e utilizzare un ObjectWriter per il tipo List<Cat>. Per esempio

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

stamperà

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

Inoltre è possibile utilizzare come 'nuova ObjectMapper() writerFor (mapper.getTypeFactory() constructCollectionType (List.class, Animale.. class)). writeValueAsString (list) ' –

+0

Questo è davvero strano, e non riesco a immaginare perché avrebbero fatto quella scelta. Indipendentemente dal tipo di elenco, penseresti che gli oggetti al suo interno vengano serializzati nello stesso modo in ogni momento. – monitorjbl

+0

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

11

La risposta Sotirios Delimanolis ha dato è quella corretta. Tuttavia, ho pensato sarebbe stato carino postare questa soluzione alternativa come risposta separata. se ci si trova in un ambiente in cui non è possibile modificare l'ObjectMapper per ogni tipo di cosa che è necessario restituire (ad esempio un'applicazione web Jersey/SpringMVC), esiste un'alternativa.

È possibile includere semplicemente un campo finale privato nella classe che contiene il tipo. Il campo non sarà visibile a nulla al di fuori della classe, ma se lo annoti con @JsonProperty("@type") (o "@class" o qualunque sia il campo del tuo nome) Jackson lo serializzerà indipendentemente da dove si trova l'oggetto.

@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

Uso interfacce anziché classi (Spring Projection). Questo non ha funzionato per me. Qualche suggerimento per favore? –

+0

Funziona per me, ma quando serializzo un elemento, il campo @type è duplicato. –

0

come array non usano la cancellazione di tipo si può risolvere l'override del ObjectMapper.writeValueAsString di trasformare la Collezione in un array.

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

Utilizzarlo nel test.principale:

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

uscita (cani e gatti liste):.

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