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

147

J'ai une chaîne json, que je devrais désérialiser dans la classe suivante

class Data <T> {
    int found;
    Class<T> hits
}

Comment fait-on ça? C'est la manière habituelle

mapper.readValue(jsonString, Data.class);

Mais comment mentionner ce que signifie T?

gnjago
la source

Réponses:

238

Vous devez créer un TypeReferenceobjet 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>>() {});
Eser Aygün
la source
Je dois l'utiliser comme TypeReference <Data <T>> () {} ... Mais j'obtiens l'erreur suivante - impossible d'accéder à java.lang.class.Class () privé depuis java.lang.class. Échec de la définition de l'accès. Impossible de rendre un constructeur java.lang.Class accessible
gnjago
Non, non Data<T>, ce n'est PAS un type. Vous devez spécifier la classe réelle; sinon c'est la même chose que Data<Object>.
StaxMan
18
Et si je ne sais pas de quelle classe il s'agit avant l'exécution? J'obtiendrai la classe en tant que paramètre pendant l'exécution. Comme ce public <T> void deSerialize (Class <T> clazz {ObjectMapper mapper = new ObjectMapper (); mapper.readValue (jsonString, new TypeReference <Json <T>> () {});}
gnjago
1
J'ai posé correctement la question complète ici stackoverflow.com/questions/11659844/…
gnjago
quel est le nom complet du package TypeReference? c'est ça com.fasterxml.jackson.core.type?
Lei Yang
88

Vous ne pouvez pas faire cela: vous devez spécifier un type entièrement résolu, comme Data<MyType>. Test juste une variable, et comme cela n'a pas de sens.

Mais si vous voulez dire que Tcela sera connu, mais pas de manière statique, vous devez créer l'équivalent de TypeReferencedynamiquement. D'autres questions référencées peuvent déjà le mentionner, mais cela devrait ressembler à quelque chose comme:

public Data<T> read(InputStream json, Class<T> contentClass) {
   JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, contentClass);
   return mapper.readValue(json, type);
}
StaxMan
la source
2
Et si je ne sais pas de quelle classe il s'agit avant l'exécution? J'obtiendrai la classe en tant que paramètre pendant l'exécution. Comme ce public <T> void deSerialize (Class <T> clazz {ObjectMapper mapper = new ObjectMapper (); mapper.readValue (jsonString, new TypeReference <Json <T>> () {});}
gnjago
1
J'ai posé la question complète ici stackoverflow.com/questions/11659844/…
gnjago
2
Ensuite, passez le cours tel quel , pas besoin de TypeReference: return mapper.readValue(json, clazz);Quel est exactement le problème ici?
StaxMan
2
Le problème est que "Data" est une classe générique. J'ai besoin de spécifier quel type T est au moment de l'exécution. Le paramètre clazz est ce que nous avons à l'exécution. Alors, comment appeler readValue? l'appeler avec le nouveau TypeReference> Json <T>> ne fonctionne pas La question complète est ici stackoverflow.com/questions/11659844/…
gnjago
2
D'accord. Ensuite, vous devez utiliser TypeFactory.. Je modifierai ma réponse.
StaxMan
30

La première chose à faire est de sérialiser, puis de désérialiser.
Ainsi, lorsque vous sérialisez, vous devez utiliser @JsonTypeInfopour laisser jackson é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, lorsque vous désérialisez, vous constaterez que jackson a désérialisé vos données dans une classe que votre variable atteint réellement lors de l'exécution.

CharlieQ
la source
ne fonctionne pas, obtenant ci-dessous l'erreur com.fasterxml.jackson.databind.exc.InvalidTypeIdException: ID de type manquant lors de la tentative de résolution du sous-type de [type simple, classe java.lang.Object]: propriété d'identifiant de type manquante '@class' (pour POJO property 'data')
gaurav9620
15

Pour les données de classe <>

ObjectMapper mapper = new ObjectMapper();
JavaType type = mapper.getTypeFactory().constructParametrizedType(Data.class, Data.class, Parameter.class);
Data<Parameter> dataParam = mapper.readValue(jsonString,type)
Devesh
la source
Ceci est désormais obsolète.
Evan Gertis le
13

Écrivez simplement une méthode statique dans la classe Util. Je lis un Json à partir d'un fichier. vous pouvez également 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);
AZ_
la source
7

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.

sksamuel
la source
6

La chaîne JSON qui doit être désérialisée devra contenir les informations de type sur le paramètre T.
Vous devrez mettre des annotations Jackson sur chaque classe qui peut être transmise en tant que paramètre Tà la classe Dataafin que les informations de type sur le type de paramètre Tpuissent être lues / écrites dans la chaîne JSON par Jackson.

Supposons que cela Tpuisse être n'importe quelle classe qui étend une 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 chacune des classes (ou leur supertype commun) qui peut être transmise en tant que paramètre T est annotée, Jackson inclura des informations sur le paramètre Tdans le JSON. Un tel JSON peut ensuite être désérialisé sans connaître le paramètre Tau moment de la compilation.
Ce lien de documentation Jackson parle de la désérialisation polymorphe, mais il est également utile de s'y référer pour cette question.

Sanjeev Sachdev
la source
et comment gérer cela si je veux avoir une liste? Comme disons List <ImageResult>
Luca Archidiacono
5

Depuis Jackson 2.5, une manière élégante de résoudre cela utilise la méthode TypeFactory.constructParametricType (Class parametrized, Class ... parameterClasses) qui permet de définir directement un Jackson JavaTypeen spécifiant la classe paramétrée et ses types paramétrés.

En supposant que vous souhaitiez désérialiser vers 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);
davidxxx
la source
Génial, merci, cela a fonctionné pour moi. Avec la référence de type, j'ai obtenu une exception de diffusion de classe de la carte à l'objet spécifique mais cela fait vraiment le travail.
Tacsiazuma le
1
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;
    }
    ...
 }
mikka22
la source
0

si vous utilisez scala et connaissez le type générique au moment de la compilation, mais que vous ne voulez pas passer manuellement TypeReference partout dans tous vos api l ayers, 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)
nathan g
la source
0

Pour désérialiser une chaîne JSON générique en objet Java avec Jackson, vous avez besoin de:

  1. Pour définir une classe JSON.

  2. Effectuez un mappage d'attributs.

Code final, testé et prêt à être utilisé:

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();
}

Important: l'
@JsonAnySetter annotation est obligatoire, elle garantit une analyse et un peuplement JSON génériques.

Pour les cas plus compliqués avec des tableaux imbriqués, veuillez consulter la référence Baeldung: https://www.baeldung.com/jackson-mapping-dynamic-object

Mike B.
la source
0

Exemple de décision pas très bonne, mais simple (pas seulement pour Jackson, également pour Spring RestTemplate, etc.):

Set<MyClass> res = new HashSet<>();
objectMapper.readValue(json, res.getClass());
Demel
la source