Ho una stringa JSON, che dovrei deserializzare alla seguente classe
class Data <T> {
int found;
Class<T> hits
}
Come lo faccio? Questo è il solito modo
mapper.readValue(jsonString, Data.class);
Ma come posso dire cosa rappresenta T?
Ho una stringa JSON, che dovrei deserializzare alla seguente classe
class Data <T> {
int found;
Class<T> hits
}
Come lo faccio? Questo è il solito modo
mapper.readValue(jsonString, Data.class);
Ma come posso dire cosa rappresenta T?
Risposte:
È necessario creare un TypeReference
oggetto per ogni tipo generico che si utilizza e utilizzarlo per la deserializzazione. Per esempio -
mapper.readValue(jsonString, new TypeReference<Data<String>>() {});
Data<T>
NON è un tipo. È necessario specificare la classe effettiva; altrimenti è lo stesso di Data<Object>
.
TypeReference
? è com.fasterxml.jackson.core.type
?
Non puoi farlo: devi specificare il tipo completamente risolto, come Data<MyType>
. T
è solo una variabile e come è insignificante.
Ma se vuoi dire che T
sarà noto, ma non staticamente, devi creare un equivalente di TypeReference
dinamicamente. Altre domande citate potrebbero già menzionarlo, ma dovrebbe assomigliare a:
public Data<T> read(InputStream json, Class<T> contentClass) {
JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, contentClass);
return mapper.readValue(json, type);
}
TypeReference
: return mapper.readValue(json, clazz);
qual è esattamente il problema qui?
TypeFactory
.. Modificherò la mia risposta.
La prima cosa che fai è serializzare, quindi puoi deserializzare.
quindi quando serializzi, dovresti usare @JsonTypeInfo
per lasciare che jackson scriva le informazioni sulla classe nei tuoi dati json. Quello che puoi fare è così:
Class Data <T> {
int found;
@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
Class<T> hits
}
Quindi quando deserializzi, troverai che Jackson ha deserializzato i tuoi dati in una classe in cui i tuoi hit variabili sono effettivamente in fase di esecuzione.
Per dati di classe <>
ObjectMapper mapper = new ObjectMapper();
JavaType type = mapper.getTypeFactory().constructParametrizedType(Data.class, Data.class, Parameter.class);
Data<Parameter> dataParam = mapper.readValue(jsonString,type)
Basta scrivere un metodo statico nella classe Util. Sto leggendo un Json da un file. puoi dare String anche a readValue
public static <T> T convertJsonToPOJO(String filePath, Class<?> target) throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(new File(filePath), objectMapper .getTypeFactory().constructCollectionType(List.class, Class.forName(target.getName())));
}
Uso:
List<TaskBean> list = Util.<List<TaskBean>>convertJsonToPOJO("E:/J2eeWorkspaces/az_workspace_svn/az-client-service/dir1/dir2/filename.json", TaskBean.class);
Puoi avvolgerlo in un'altra classe che conosce il tipo del tuo tipo generico.
Per esempio,
class Wrapper {
private Data<Something> data;
}
mapper.readValue(jsonString, Wrapper.class);
Qui Something è un tipo concreto. È necessario un wrapper per tipo reificato. Altrimenti Jackson non sa quali oggetti creare.
La stringa JSON che deve essere deserializzata dovrà contenere le informazioni sul tipo relative al parametro T
.
Dovrai mettere le annotazioni di Jackson su ogni classe che può essere passata come parametro T
a classe in Data
modo che le informazioni sul tipo sul tipo di parametro T
possano essere lette / scritte nella stringa JSON da Jackson.
Supponiamo che T
possa essere qualsiasi classe che estende la classe astratta Result
.
class Data <T extends Result> {
int found;
Class<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 {
}
Una volta T
annotata ciascuna delle classi (o il loro supertipo comune) che può essere passato come parametro , Jackson includerà le informazioni sui parametri T
nel JSON. Tale JSON può quindi essere deserializzato senza conoscere il parametro T
al momento della compilazione.
Questo collegamento alla documentazione di Jackson parla della deserializzazione polimorfica, ma è utile fare riferimento anche per questa domanda.
Da Jackson 2.5, un modo elegante di risoluzione che sta usando il
metodo TypeFactory.constructParametricType (Class parametrized, Class ... parameterClasses) che consente di definire con precisione un Jackson JavaType
specificando la classe con parametri e i suoi tipi con parametri.
Supponendo di voler deserializzare Data<String>
, puoi fare:
// the json variable may be a String, an InputStream and so for...
JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, String.class);
Data<String> data = mapper.readValue(json, type);
Nota che se la classe dichiarasse più tipi con parametri, non sarebbe molto più difficile:
class Data <T, U> {
int found;
Class<T> hits;
List<U> list;
}
Potremmo fare:
JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, String.class, Integer);
Data<String, Integer> data = mapper.readValue(json, type);
public class Data<T> extends JsonDeserializer implements ContextualDeserializer {
private Class<T> cls;
public JsonDeserializer createContextual(DeserializationContext ctx, BeanProperty prop) throws JsonMappingException {
cls = (Class<T>) ctx.getContextualType().getRawClass();
return this;
}
...
}
se stai usando scala e conosci il tipo generico al momento della compilazione, ma non vuoi passare manualmente TypeReference ovunque in tutti i tuoi apri, puoi usare il seguente codice (con jackson 2.9.5):
def read[T](entityStream: InputStream)(implicit typeTag: WeakTypeTag[T]): T = {
//nathang: all of this *crazy* scala reflection allows us to handle List[Seq[Map[Int,Value]]]] without passing
// new TypeReference[List[Seq[Map[Int,Value]]]]](){} to the function
def recursiveFindGenericClasses(t: Type): JavaType = {
val current = typeTag.mirror.runtimeClass(t)
if (t.typeArgs.isEmpty) {
val noSubtypes = Seq.empty[Class[_]]
factory.constructParametricType(current, noSubtypes:_*)
}
else {
val genericSubtypes: Seq[JavaType] = t.typeArgs.map(recursiveFindGenericClasses)
factory.constructParametricType(current, genericSubtypes:_*)
}
}
val javaType = recursiveFindGenericClasses(typeTag.tpe)
json.readValue[T](entityStream, javaType)
}
che può essere usato in questo modo:
read[List[Map[Int, SomethingToSerialize]]](inputStream)
Per deserializzare una stringa JSON generica in un oggetto Java con Jackson è necessario:
Per definire una classe JSON.
Eseguire una mappatura degli attributi.
Codice finale, testato e pronto per essere utilizzato:
static class MyJSON {
private Map<String, Object> content = new HashMap<>();
@JsonAnySetter
public void setContent(String key, Object value) {
content.put(key, value);
}
}
String json = "{\"City\":\"Prague\"}";
try {
MyPOJO myPOJO = objectMapper.readValue(json, MyPOJO.class);
String jsonAttVal = myPOJO.content.get("City").toString();
System.out.println(jsonAttVal);
} catch (IOException e) {
e.printStackTrace();
}
Importante: l'
@JsonAnySetter
annotazione è obbligatoria, garantisce un'analisi e una popolazione JSON generiche.
Per casi più complicati con array nidificati, consultare il riferimento Baeldung: https://www.baeldung.com/jackson-mapping-dynamic-object