2017-05-05 1 views
0

Bonjour à tous. J'ai un peu de mal à comprendre cela. J'ai un JSON qui ressemble à ceci:Comment analyser un tableau d'objets JSON imbriqué avec un désérialiseur Gson personnalisé?

{ 
     "data": [ 
     { 
      "id": "43", 
      "type": "position", 
      "attributes": { 
      "address-id": "1", 
      "employer-id": "11" 
      } 
     } 
     ], 
     "included": [ 
     { 
      "id": "1", 
      "type": "address", 
      "attributes": { 
      "line-1": "21 london london", 
      "line-2": "", 
      "line-3": "", 
      "locality": "", 
      "region": "London", 
      "post-code": "", 
      "country": "UK", 
      "latitude": "", 
      "longitude": "" 
      } 
     }, 
     { 
      "id": "11", 
      "type": "employer", 
      "attributes": { 
      "title": "Mr", 
      "first-name": "S", 
      "last-name": "T" 
      } 
     } 
     ] 
    } 

Et mon appel Rénovation est:

@GET("/api/positions") 
Single<PositionResponse> getPosition(); 

Et ma PositionResponse classe:

public class PositionResponse { 

     @SerializedName("data") 
     @Expose 
     private List<DataResponse> data; 
     @SerializedName("included") 
     @Expose 
     private List<IncludedModel> included; 

     public List<DataResponse> getData() { 
      return data; 
     } 

     public void setData(List<DataResponse> data) { 
      this.data = data; 
     } 

     public List<IncludedModel> getIncluded() { 
      return included; 
     } 

     public void setIncluded(List<IncludedModel> included) { 
      this.included = included; 
     } 

     } 
    } 

Maintenant, imaginez qu'il a beaucoup plus de données. Comment puis-je créer un TypeAdapter personnalisé ou JsonDeserializer pour analyser le List<IncludedModel>? Pour une raison quelconque, je peux créer un JsonDeserializer ou TypeAdapter personnalisé pour Object, mais quand il s'agit d'un List, je ne semble pas être en mesure de faire fonctionner cela.

Mon TypeAdapter est la suivante:

public class IncludedTypeAdapter extends TypeAdapter<ArrayList<IncludedModel>> { 

     @Override 
     public void write(JsonWriter out, ArrayList<IncludedModel> value) throws IOException { 

     } 

     @Override 
     public ArrayList<IncludedModel> read(JsonReader in) throws IOException { 
      ArrayList<IncludedModel> list = new ArrayList<>(); 
      IncludedModel model = new IncludedModel(); 
      Gson gson = new Gson(); 
      in.beginArray(); 
      String id = null; 
      //in.beginObject(); 
      while(in.hasNext()){ 
       JsonToken nextToken = in.peek(); 

       if(JsonToken.BEGIN_OBJECT.equals(nextToken)){ 
        in.beginObject(); 

       } else if(JsonToken.NAME.equals(nextToken)){ 

        if(JsonToken.NAME.name().equals("id")){ 
         id = in.nextString(); 
         model.setId(id); 

        } else if(JsonToken.NAME.name().equals("type")){ 
         String type = in.nextString(); 
         model.setMytype(type); 

         switch (type) { 
          case BaseModelType.Employer: 
           EmployerResponse employer = gson.fromJson(in, EmployerResponse.class); 
           model.setEmployer(employer); 
           break; 
         } 
        } 
       } 
      } 
      list.add(model); 

      return list; 
     } 

Et je vous inscrire à mon Gson:

GsonBuilder gsonBuilder = new GsonBuilder(); 
     gsonBuilder.registerTypeAdapter(IncludeModel.class, new IncludedTypeAdapter()); 
    //gsonBuilder.registerTypeAdapter(new IncludedTypeAdapter()); 
     gsonBuilder.serializeNulls(); 
     Gson gson = gsonBuilder.create(); 

     return gson; 

Ce qui me inscrire sur la modernisation par GsonConverterFactory.

Je reçois:

BEGIN_ARRAY attendu, mais était chemin BEGIN_OBJECT à la ligne 1 de la colonne 6292 $ .included [0]

que je pense parce que ma réponse Retrofit est <PositionResponse> qui est JsonObject.

Pour résumer ma question: comment désérialiser l'objet List<IncludeModel> avec mon propre adaptateur de type personnalisé en gardant à l'esprit le type de réponse de mon service Retrofit est PositionResponse? Un grand merci pour vos patients et vos réponses.

+1

Pourquoi avez-vous besoin d'un adaptateur personnalisé du tout? Si votre modèle correspond au json, vous n'en avez généralement pas besoin. – nasch

+1

@nasch car si vous regardez dans l'objet include, le type change dynamiquement et le serveur renvoie d'autres objets imbriqués, comme la balise "relationship" au format JSONAPI, j'aurais beaucoup de classes de modèles difficiles à maintenir et il serait donc préférable de les cartographier dans un modèle personnalisé. Aussi à des fins d'apprentissage, si c'est possible de le faire. – irobotxxx

Répondre

0

C'est facile si vous utilisez des modèles d'arbre JSON en utilisant JsonDeserializer. Les adaptateurs de type pur sont quelque peu exagérés (et RuntimeTypeAdapterFactory est, je pense, car il est toujours orienté arbre), et dans le cas le plus simple pour votre document JSON, vous pouvez utiliser quelque chose comme ça (vous pouvez trouver une approche similaire dans my yesterday answer avoir quelques explications supplémentaires, mais votre cas diffère légèrement).

Je suppose que vous voudrais avoir les correspondances comme celles-ci:

abstract class Element { 

    final String id = null; 

    private Element() { 
    } 

    static final class Address 
      extends Element { 

     @SerializedName("line-1") final String line1 = null; 
     @SerializedName("line-2") final String line2 = null; 
     @SerializedName("line-3") final String line3 = null; 
     @SerializedName("locality") final String locality = null; 
     @SerializedName("region") final String region = null; 
     @SerializedName("post-code") final String postCode = null; 
     @SerializedName("country") final String country = null; 
     @SerializedName("latitude") final String latitude = null; 
     @SerializedName("longitude") final String longitude = null; 

     private Address() { 
     } 

     @Override 
     public String toString() { 
      return country + " " + region; 
     } 

    } 

    static final class Employer 
      extends Element { 

     @SerializedName("title") final String title = null; 
     @SerializedName("first-name") final String firstName = null; 
     @SerializedName("last-name") final String lastName = null; 

     private Employer() { 
     } 

     @Override 
     public String toString() { 
      return title + ' ' + firstName + ' ' + lastName; 
     } 

    } 

    static final class Position 
      extends Element { 

     @SerializedName("address-id") final String addressId = null; 
     @SerializedName("employer-id") final String employerId = null; 

     private Position() { 
     } 

     @Override 
     public String toString() { 
      return '(' + addressId + ';' + employerId + ')'; 
     } 

    } 

} 

Tout ce que vous avez à faire est juste:

  • déterminer le type d'objet prévu; "Aligner" l'arborescence JSON (si cela correspond à vos besoins);
  • Il suffit de déléguer le travail de désérialisation à Gson via le contexte de désérialisation (votre exemple ne le fait pas bien: vous instanciez Gson perdant à nouveau la configuration d'origine, vous refaites tout Gson peut faire sortir de la boîte: listes et POJO par réflexion ; JsonToken sont beaucoup mieux si vérifié par (par la façon dont enum s sont singletons et il est parfaitement légal de les comparer en utilisant l'égalité de référence ==), etc).

Ainsi, il peut être mis en œuvre par quelque chose comme ceci:

final class ElementJsonDeserializer 
     implements JsonDeserializer<Element> { 

    private static final JsonDeserializer<Element> elementJsonDeserializer = new ElementJsonDeserializer(); 

    private ElementJsonDeserializer() { 
    } 

    static JsonDeserializer<Element> getElementJsonDeserializer() { 
     return elementJsonDeserializer; 
    } 

    @Override 
    public Element deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context) 
      throws JsonParseException { 
     final JsonObject jsonObject = jsonElement.getAsJsonObject(); 
     final String typeCode = jsonObject.getAsJsonPrimitive("type").getAsString(); 
     final Class<? extends Element> clazz; 
     switch (typeCode) { 
     case "address": 
      clazz = Element.Address.class; 
      break; 
     case "employer": 
      clazz = Element.Employer.class; 
      break; 
     case "position": 
      clazz = Element.Position.class; 
      break; 
     default: 
      throw new JsonParseException("Unrecognized type: " + typeCode); 
     } 
     reattach(jsonObject, "attributes"); 
     return context.deserialize(jsonElement, clazz); 
    } 

    private static void reattach(final JsonObject parent, final String property) { 
     final JsonObject child = parent.getAsJsonObject(property); 
     parent.remove(property); // remove after we're sure it's a JSON object 
     copyTo(parent, child); 
    } 

    private static void copyTo(final JsonObject to, final JsonObject from) { 
     for (final Entry<String, JsonElement> e : from.entrySet()) { 
      to.add(e.getKey(), e.getValue()); 
     } 
    } 

} 

Bien sûr, vous pouvez factoriser ci-dessus pour en extraire une stratégie pour mettre en œuvre le modèle de conception de la stratégie de le réutiliser. Mettez tout cela ensemble:

final class Response { 

    final List<Element> data = null; 
    final List<Element> included = null; 

} 

(celle ci-dessus ressemble à un Map<String, List<Element>> mais vous décidez).

private static final Gson gson = new GsonBuilder() 
     .registerTypeAdapter(Element.class, getElementJsonDeserializer()) 
     .create(); 

public static void main(final String... args) 
     throws IOException { 
    try (final JsonReader jsonReader = getPackageResourceJsonReader(Q43811168.class, "data.json")) { 
     final Response response = gson.fromJson(jsonReader, Response.class); 
     dump(response.data); 
     dump(response.included); 
    } 
} 

private static void dump(final Iterable<Element> elements) { 
    for (final Element e : elements) { 
     System.out.print(e.getClass().getSimpleName()); 
     System.out.print(" #"); 
     System.out.print(e.id); 
     System.out.print(": "); 
     System.out.println(e); 
    } 
} 

Sortie:

Position # 43: (1; 11)
Adresse # 1: UK London
Employeur # 11: M. ST

+0

Merci beaucoup! .. aussi pour l'info dans l'autre lien que vous avez envoyé. Je voudrais l'essayer et vous le faire savoir. – irobotxxx

+1

désolé pour la réponse tardive. Cela fonctionne !! .. Un merci massif pour l'aide et l'information. J'ai appris quelque chose de nouveau! – irobotxxx