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