web-dev-qa-db-fra.com

Jackson - Désérialiser à l'aide d'une classe générique

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?

90
gnjago

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>>() {});
150
Eser Aygün

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

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.

22
CharlieQ

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);
9
AZ_

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)
7
Devesh

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.

6
monkjack

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.

1
Sanjeev Sachdev

À 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);
1
davidxxx

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)
0
nathan g