it-swarm.com.de

Ist Jackson wirklich nicht in der Lage, Json zu einem generischen Typ zu deserialisieren?

Dies ist eine doppelte Frage, da die folgenden Fragen entweder unordentlich sind oder überhaupt nicht beantwortet werden:

Deserialisierung eines generischen Typs mit Jackson

jackson-deserialize-in-laufzeitspezifizierte Klasse

jackson-deserialize-using-generic-class

jackson-deserialize-generische-Klassenvariable

Ich hoffe, dass diese Frage endlich eine Antwort findet, die dies für immer klar macht.

Ein Modell haben:

public class AgentResponse<T> {

    private T result;

    public AgentResponse(T result) {
        this.result = result;
    }
    public T getResult() {
        return result;
    }
}

JSON-Eingabe:

{"result":{"first-client-id":3,"test-mail-module":3,"third-client-id":3,"second-client-id":3}}

und zwei empfohlene Methoden zur Deserialisierung generischer Typen:

mapper.readValue(out, new TypeReference<AgentResponse<Map<String, Integer>>>() {}); 

oder

JavaType javaType = mapper.getTypeFactory().constructParametricType(AgentResponse.class, Map.class);
mapper.readValue(out, javaType);

Jackson ist nie in der Lage, mit dem generischen Typ T umzugehen, er geht davon aus, dass es sich um eine Map aus JavaType handelt, er findet jedoch ein Argument für den Objekttypkonstruktor aufgrund der Typlöschung und löst einen Fehler aus. Ist das also ein Jackson-Bug oder mache ich etwas falsch? Wofür ist explizite Angabe von TypeReference oder JavaType?

com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class com.fg.mail.smtp.AgentResponse<Java.util.Map<Java.lang.String,Java.lang.Integer>>]: can not instantiate from JSON object (need to add/enable type information?)
at [Source: [email protected]; line: 1, column: 2]
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.Java:164)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.Java:984)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.Java:276)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.Java:121)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.Java:2888)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.Java:2064)
41
lisak

Sie müssen dem Konstruktor einige Anmerkungen hinzufügen, um Jackson mitzuteilen, wie das Objekt erstellt werden soll. Folgendes hat für mich gearbeitet:

public class AgentResponse<T> {

    private T result;

    @JsonCreator
    public AgentResponse(@JsonProperty("result") T result) {
        this.result = result;
    }
    public T getResult() {
        return result;
    }
}

Ohne die @JsonCreator-Anmerkung kann Jackson diesen Konstruktor nicht nennen. Und ohne die @JsonProperty-Annotation weiß Jackson nicht, dass das erste Argument des Konstruktors der result-Eigenschaft zugeordnet ist.

53
cambecc

Ich habe es mit dem gleichen Ansatz versucht, aber ich habe meine Modellklasse nicht kommentiert. Es hat gut für mich funktioniert.

Dies ist meine Modellklasse

public class BasicMessage<T extends Serializable> implements Message<T> {
    private MessageHeader messageHeader = new MessageHeader();
    private T payload;
    public MessageHeader getHeaders() {
        return messageHeader;
    }

    public Object getHeader(String key) {
        return messageHeader.get(key);
    }

    public Object addHeader(String key, Object header) {
        return messageHeader.put(key, header);
    }

    public T getPayload() {
        return payload;
    }

    public void setPayload(T messageBody) {
        this.payload = messageBody;
    }
}

Und ich habe die folgende Methode benutzt, um die Nutzlast zu deserialisieren

public static <T extends Serializable> BasicMessage<T> getConcreteMessageType(String jsonString, Class<T> classType) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            JavaType javaType = mapper.getTypeFactory().constructParametricType(BasicMessage.class, classType);
            return mapper.readValue(jsonString, javaType);
        } catch (IOException e) {

        }
 }

dabei enthält jsonString das BasicMessageObject in einer Zeichenfolge.

6
yaswanth

Die JSON-Zeichenfolge, die deserialisiert werden muss, muss die Typinformationen zum Parameter T enthalten.
Sie müssen Jackson-Anmerkungen zu jeder Klasse setzen, die als Parameter T an die Klasse AgentResponse übergeben werden kann, damit die Typinformationen über den Parametertyp T von Jackson aus dem JSON-String gelesen bzw. in diesen geschrieben werden können.

Nehmen wir an, dass T jede Klasse sein kann, die die abstrakte Klasse Result erweitert.

public class AgentResponse<T extends Result> {
    public Hits<T> hits;
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
@JsonSubTypes({
        @JsonSubTypes.Type(value = ImageResult.class, name = "ImageResult"),
        @JsonSubTypes.Type(value = NewsResult.class, name = "NewsResult")})
public abstract class Result {

}

public class ImageResult extends Result {

}

public class NewsResult extends Result {

}

Sobald alle Klassen (oder deren allgemeiner Supertyp), die als Parameter T übergeben werden können, mit Anmerkungen versehen sind, fügt Jackson Informationen über den Parameter T in der JSON ein. Ein solches JSON kann dann deserialisiert werden, ohne den Parameter T zur Kompilierzeit zu kennen.
Dieser Jackson-Dokumentationslink spricht über polymorphe Deserialisierung, kann aber auch für diese Frage verwendet werden.

1
Sanjeev Sachdev

Wenn Sie programmgesteuert die Java.lang.reflect.Type von beispielsweise einer Methode Rückgabetyp oder ein Feld, dann ist es am einfachsten zu verwenden

Type type = ...;
ObjectMapper mapper = new ObjectMapper();
JavaType javaType = mapper.getTypeFactory().constructType( type );
Object value = mapper.readValue( json, javaType );

Ein vollständig verschachtelter JavaType wird erstellt, also Controller<PID<Temperature,Double>>> wird korrekt deserialisiert.

0
Niclas Hedhman
public class AgentResponse<T> {

private T result;

public AgentResponse(T result) {
    this.result = result;
}
public T getResult() {
    return result;
}

}

Also für die obige Klassenstruktur und T können vom Typ T1, T2 Klasse sein

Um für AgentResponse zu deserialisieren, verwenden Sie den folgenden Code 

JavaType javaType = objectMapper.getTypeFactory.constructParametricType(AgentResponse.class,T1.class)
AgentResponse<T1> agentResponseT1 = objectMapper.readValue(inputJson,javaType);
0
jily