J'ai une chaîne json que je devrais désérialiser à la classe suivante
class Data <T> {
int found;
Class<T> hits
}
Comment puis-je le faire? C'est la manière habituelle
mapper.readValue(jsonString, Data.class);
Mais comment mentionner ce que T représente?
Vous devez créer un objet TypeReference
pour chaque type générique que vous utilisez et l'utiliser pour la désérialisation. Par exemple,
mapper.readValue(jsonString, new TypeReference<Data<String>>() {});
Vous ne pouvez pas faire cela: vous devez spécifier un type entièrement résolu, tel que Data<MyType>
. T
est juste une variable et n'a aucun sens.
Mais si vous voulez dire que T
sera connu, mais pas statiquement, vous devez créer l’équivalent de TypeReference
de manière dynamique. D'autres questions référencées peuvent déjà en faire mention, mais cela devrait ressembler à quelque chose comme:
public Data<T> read(InputStream json, Class<T> contentClass) {
JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, contentClass.class);
return mapper.readValue(json, type);
}
La première chose à faire est de sérialiser, puis de désérialiser .
Ainsi, lorsque vous effectuez une sérialisation, vous devez utiliser @JsonTypeInfo
pour permettre à Jackson d’écrire les informations de classe dans vos données json. Voici ce que vous pouvez faire:
Class Data <T> {
int found;
@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
Class<T> hits
}
Ensuite, lors de la désérialisation, vous constaterez que Jackson a désérialisé vos données dans une classe où votre variable est en cours d’exécution.
Il suffit d'écrire une méthode statique dans la classe Util. Je lis un Json d'un fichier. vous pouvez aussi donner String à 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())));
}
Usage:
List<TaskBean> list = Util.<List<TaskBean>>convertJsonToPOJO("E:/J2eeWorkspaces/az_workspace_svn/az-client-service/dir1/dir2/filename.json", TaskBean.class);
Pour la classe de données <>
ObjectMapper mapper = new ObjectMapper();
JavaType type = mapper.getTypeFactory().constructParametrizedType(Data.class, Data.class, Parameter.class);
Data<Parameter> dataParam = mapper.readValue(jsonString,type)
Vous pouvez l'envelopper dans une autre classe qui connaît le type de votre type générique.
Par exemple,
class Wrapper {
private Data<Something> data;
}
mapper.readValue(jsonString, Wrapper.class);
Ici, quelque chose est un type concret. Vous avez besoin d'un wrapper par type réifié. Sinon, Jackson ne sait pas quels objets créer.
La chaîne JSON qui doit être désérialisée devra contenir les informations de type relatives au paramètre T
.
Vous devrez mettre des annotations de Jackson sur chaque classe pouvant être passée en tant que paramètre T
à class Data
afin que les informations de type relatives au paramètre type T
puissent être lues/écrites dans la chaîne JSON par Jackson.
Supposons que T
puisse être n'importe quelle classe qui étend la classe abstraite 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 {
}
Une fois que chaque classe (ou son supertype commun) pouvant être passée en tant que paramètre T
est annotée, Jackson inclura des informations sur le paramètre T
dans le JSON. Ce JSON peut ensuite être désérialisé sans connaître le paramètre T
au moment de la compilation.
Ce lien dans la documentation Jackson parle de la désérialisation polymorphe, mais il est utile de s'y référer également.
À partir de Jackson 2.5, une méthode élégante de résolution utilisant la méthode TypeFactory.constructParametricType (Classe paramétrée, Classe ... paramètreClasses) qui permet de définir directement un Jackson JavaType
en spécifiant le paramètre classe et ses types paramétrés.
Si vous voulez désérialiser en Data<String>
, vous pouvez faire:
// 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);
Notez que si la classe déclarait plusieurs types paramétrés, ce ne serait pas vraiment plus difficile:
class Data <T, U> {
int found;
Class<T> hits;
List<U> list;
}
Nous pourrions faire :
JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, String.class, Integer);
Data<String, Integer> data = mapper.readValue(json, type);
si vous utilisez scala et connaissez le type générique au moment de la compilation, mais que vous ne voulez pas passer TypeReference partout dans toutes vos applications, vous pouvez utiliser le code suivant (avec 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)
}
qui peut être utilisé comme ceci:
read[List[Map[Int, SomethingToSerialize]]](inputStream)