Obtient un object JSON nested avec GSON en utilisant la conversion

Je consum une API de mon application Android, et toutes les réponses JSON sont comme ceci:

{ 'status': 'OK', 'reason': 'Everything was fine', 'content': {  } 

Le problème est que tous mes POJO ont un status , des champs de reason , et à l’intérieur du champ de content trouve le véritable POJO que je veux.

Existe-t-il un moyen de créer un convertisseur personnalisé de Gson pour extraire toujours le champ de content , de sorte que la conversion renvoie le POJO approprié?

Vous devez écrire un désérialiseur personnalisé qui renvoie l’object incorporé.

Disons que votre JSON est:

 { "status":"OK", "reason":"some reason", "content" : { "foo": 123, "bar": "some value" } } 

Vous auriez alors un Content POJO:

 class Content { public int foo; public Ssortingng bar; } 

Ensuite, vous écrivez un désérialiseur:

 class MyDeserializer implements JsonDeserializer { @Override public Content deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException { // Get the "content" element from the parsed JSON JsonElement content = je.getAsJsonObject().get("content"); // Deserialize it. You use a new instance of Gson to avoid infinite recursion // to this deserializer return new Gson().fromJson(content, Content.class); } } 

Maintenant, si vous construisez un Gson avec GsonBuilder et enregistrez le désérialiseur:

 Gson gson = new GsonBuilder() .registerTypeAdapter(Content.class, new MyDeserializer()) .create(); 

Vous pouvez désérialiser votre JSON directement sur votre Content :

 Content c = gson.fromJson(myJson, Content.class); 

Modifier pour append des commentaires:

Si vous avez différents types de messages mais qu’ils ont tous le champ “content”, vous pouvez rendre le Deserializer générique en procédant comme suit:

 class MyDeserializer implements JsonDeserializer { @Override public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException { // Get the "content" element from the parsed JSON JsonElement content = je.getAsJsonObject().get("content"); // Deserialize it. You use a new instance of Gson to avoid infinite recursion // to this deserializer return new Gson().fromJson(content, type); } } 

Vous devez simplement enregistrer une instance pour chacun de vos types:

 Gson gson = new GsonBuilder() .registerTypeAdapter(Content.class, new MyDeserializer()) .registerTypeAdapter(DiffContent.class, new MyDeserializer()) .create(); 

Lorsque vous appelez .fromJson() le type est .fromJson() dans le désérialiseur, il devrait donc fonctionner pour tous vos types.

Et enfin, lors de la création d’une instance Retrofit:

 Retrofit retrofit = new Retrofit.Builder() .baseUrl(url) .addConverterFactory(GsonConverterFactory.create(gson)) .build(); 

La solution @ BrianRoach est la solution correcte. Il est à noter que dans le cas particulier où vous avez des objects personnalisés nesteds nécessitant tous deux un TypeAdapter personnalisé, vous devez enregistrer TypeAdapter avec la nouvelle instance de GSON , sinon le second TypeAdapter ne sera jamais appelé. C’est parce que nous créons une nouvelle instance de Gson dans notre désérialiseur personnalisé.

Par exemple, si vous aviez le json suivant:

 { "status": "OK", "reason": "some reason", "content": { "foo": 123, "bar": "some value", "subcontent": { "useless": "field", "data": { "baz": "values" } } } } 

Et vous vouliez que ce JSON soit mappé sur les objects suivants:

 class MainContent { public int foo; public Ssortingng bar; public SubContent subcontent; } class SubContent { public Ssortingng baz; } 

Vous devez enregistrer le SubContent du TypeAdapter . Pour être plus robuste, vous pouvez effectuer les opérations suivantes:

 public class MyDeserializer implements JsonDeserializer { private final Class mNestedClazz; private final Object mNestedDeserializer; public MyDeserializer(Class nestedClazz, Object nestedDeserializer) { mNestedClazz = nestedClazz; mNestedDeserializer = nestedDeserializer; } @Override public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException { // Get the "content" element from the parsed JSON JsonElement content = je.getAsJsonObject().get("content"); // Deserialize it. You use a new instance of Gson to avoid infinite recursion // to this deserializer GsonBuilder builder = new GsonBuilder(); if (mNestedClazz != null && mNestedDeserializer != null) { builder.registerTypeAdapter(mNestedClazz, mNestedDeserializer); } return builder.create().fromJson(content, type); } } 

et ensuite le créer ainsi:

 MyDeserializer myDeserializer = new MyDeserializer(SubContent.class, new SubContentDeserializer()); Gson gson = new GsonBuilder().registerTypeAdapter(Content.class, myDeserializer).create(); 

Cela pourrait facilement être utilisé pour le cas “content” nested en transmettant simplement une nouvelle instance de MyDeserializer avec des valeurs NULL.

Un peu tard mais j’espère que cela aidera quelqu’un.

Il suffit de créer en suivant TypeAdapterFactory.

  public class ItemTypeAdapterFactory implements TypeAdapterFactory { public  TypeAdapter create(Gson gson, final TypeToken type) { final TypeAdapter delegate = gson.getDelegateAdapter(this, type); final TypeAdapter elementAdapter = gson.getAdapter(JsonElement.class); return new TypeAdapter() { public void write(JsonWriter out, T value) throws IOException { delegate.write(out, value); } public T read(JsonReader in) throws IOException { JsonElement jsonElement = elementAdapter.read(in); if (jsonElement.isJsonObject()) { JsonObject jsonObject = jsonElement.getAsJsonObject(); if (jsonObject.has("content")) { jsonElement = jsonObject.get("content"); } } return delegate.fromJsonTree(jsonElement); } }.nullSafe(); } } 

et l’append dans votre générateur GSON:

 .registerTypeAdapterFactory(new ItemTypeAdapterFactory()); 

ou

  yourGsonBuilder.registerTypeAdapterFactory(new ItemTypeAdapterFactory()); 

Poursuivant l’idée de Brian, comme nous avons presque toujours beaucoup de ressources REST ayant chacune sa propre racine, il pourrait être utile de généraliser la désérialisation:

  class RestDeserializer implements JsonDeserializer { private Class mClass; private Ssortingng mKey; public RestDeserializer(Class targetClass, Ssortingng key) { mClass = targetClass; mKey = key; } @Override public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException { JsonElement content = je.getAsJsonObject().get(mKey); return new Gson().fromJson(content, mClass); } } 

Ensuite, pour parsingr les données utiles des échantillons ci-dessus, nous pouvons enregistrer le désérialiseur GSON:

 Gson gson = new GsonBuilder() .registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class, "content")) .build(); 

Il y a eu le même problème il y a quelques jours. J’ai résolu ce problème en utilisant la classe wrapper de réponse et le transformateur RxJava, ce qui, à mon avis, est une solution assez flexible:

Emballage:

 public class ApiResponse { public Ssortingng status; public Ssortingng reason; public T content; } 

Exception personnalisée à lancer, lorsque le statut n’est pas correct:

 public class ApiException extends RuntimeException { private final Ssortingng reason; public ApiException(Ssortingng reason) { this.reason = reason; } public Ssortingng getReason() { return apiError; } } 

Transformateur Rx:

 protected  Observable.Transformer, T> applySchedulersAndExtractData() { return observable -> observable .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .map(tApiResponse -> { if (!tApiResponse.status.equals("OK")) throw new ApiException(tApiResponse.reason); else return tApiResponse.content; }); } 

Exemple d’utilisation:

 // Call definition: @GET("/api/getMyPojo") Observable> getConfig(); // Call invoke: webservice.getMyPojo() .compose(applySchedulersAndExtractData()) .subscribe(this::handleSuccess, this::handleError); private void handleSuccess(MyPojo mypojo) { // handle success } private void handleError(Throwable t) { getView().showSnackbar( ((ApiException) throwable).getReason() ); } 

Mon sujet: Retrofit 2 RxJava – Gson – Désérialisation “globale”, changement de type de réponse

C’est la même solution que @AYarulin mais supposons que le nom de la classe est le nom de la clé JSON. De cette façon, il vous suffit de passer le nom de la classe.

  class RestDeserializer implements JsonDeserializer { private Class mClass; private Ssortingng mKey; public RestDeserializer(Class targetClass) { mClass = targetClass; mKey = mClass.getSimpleName(); } @Override public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException { JsonElement content = je.getAsJsonObject().get(mKey); return new Gson().fromJson(content, mClass); } } 

Ensuite, pour parsingr l’échantillon de données utiles ci-dessus, nous pouvons enregistrer le désérialiseur GSON. Ceci est problématique car la clé est sensible à la casse, donc la casse du nom de la classe doit correspondre à celle de la clé JSON.

 Gson gson = new GsonBuilder() .registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class)) .build(); 

Une meilleure solution pourrait être ceci.

 public class ApiResponse { public T data; public Ssortingng status; public Ssortingng reason; } 

Ensuite, définissez votre service comme celui-ci.

 Observable> updateDevice(..); 

Voici une version de Kotlin basée sur les réponses de Brian Roach et AYarulin.

 class RestDeserializer(targetClass: Class, key: Ssortingng?) : JsonDeserializer { val targetClass = targetClass val key = key override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): T { val data = json!!.asJsonObject.get(key ?: "") return Gson().fromJson(data, targetClass) } } 

Selon la réponse de @Brian Roach et @rafakob, je l’ai fait de la manière suivante

Réponse Json du serveur

 { "status": true, "code": 200, "message": "Success", "data": { "fullname": "Rohan", "role": 1 } } 

Classe commune de gestionnaire de données

 public class ApiResponse { @SerializedName("status") public boolean status; @SerializedName("code") public int code; @SerializedName("message") public Ssortingng reason; @SerializedName("data") public T content; } 

Sérialiseur personnalisé

 static class MyDeserializer implements JsonDeserializer { @Override public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException { JsonElement content = je.getAsJsonObject(); // Deserialize it. You use a new instance of Gson to avoid infinite recursion // to this deserializer return new Gson().fromJson(content, type); } } 

Objet Gson

 Gson gson = new GsonBuilder() .registerTypeAdapter(ApiResponse.class, new MyDeserializer()) .create(); 

Appel api

  @FormUrlEncoded @POST("/loginUser") Observable> signIn(@Field("email") Ssortingng username, @Field("password") Ssortingng password); restService.signIn(username, password) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe(new Observer>() { @Override public void onCompleted() { Log.i("login", "On complete"); } @Override public void onError(Throwable e) { Log.i("login", e.toSsortingng()); } @Override public void onNext(ApiResponse response) { Profile profile= response.content; Log.i("login", profile.getFullname()); } }); 

Dans mon cas, la clé “content” changerait pour chaque réponse. Exemple:

 // Root is hotel { status : "ok", statusCode : 200, hotels : [{ name : "Taj Palace", location : { lat : 12 lng : 77 } }, { name : "Plaza", location : { lat : 12 lng : 77 } }] } //Root is city { status : "ok", statusCode : 200, city : { name : "Vegas", location : { lat : 12 lng : 77 } } 

Dans de tels cas, j’ai utilisé une solution similaire à celle indiquée ci-dessus, mais j’ai dû la modifier. Vous pouvez voir l’essentiel ici . C’est un peu trop gros pour le poster ici sur SOF.

L’annotation @InnerKey("content") est utilisée et le rest du code est destiné à faciliter son utilisation avec Gson.

Salam. N’oubliez pas les annotations @SerializedName et @Expose pour tous les membres de classe et membres de classe interne les plus désérialisés de JSON par GSON.

Regardez https://stackoverflow.com/a/40239512/1676736