2017-05-12 3 views
0

Ich arbeite an einer Android-App, die Retrofit + OkHttp verwendet, um eine Verbindung zu einer REST-API herzustellen und JSON-Daten zu verwenden. Ich bin ziemlich neu in Retrofit, also lerne ich immer noch, wie es funktioniert, aber bisher war es ziemlich glatt. Ich stieß jedoch auf ein ungewöhnliches Problem.Retrofit: Wie analysiert man ein JSON-Array, das ein Array und ein Objekt kombiniert?

Einer der Endpunkte gibt Daten, die ein bisschen wie folgt aussieht:

{ 
    "success": true, 
    "data": [ 
     [ 
      { 
       "field": "value1", 
       ... 
      }, 
      { 
       "field": "value2", 
       ... 
      }, 
      ... 
     ], 
     { 
      "info": "value", 
      "info2": "value", 
      "info3": "value", 
     } 
    ], 
    "message": "This is the message." 
} 

Zum größten Teil ist dies ein ziemlich Standard JSON-Antwort. Wir haben einen "Status" -Wert, eine "Nachricht" und einen "Daten" -Wert, der die wichtigen Daten enthält, die zurückgegeben werden. Es gibt jedoch ein Problem mit der Struktur von "Daten". Wie Sie sehen, ist es ein Array, aber es ist nicht nur ein Array von Objekten. Sein erstes Element ist das Array von Werten und sein zweites Element ist ein Objekt.

Gson mag dies nicht. Wenn ich eine POJO zu analysieren diese in mit Gson erstellen möchten, würde ich erwarten, so etwas zu tun:

public class JsonResponse { 

    @SerializedName("success") 
    @Expose 
    boolean success; 

    @SerializedName("message") 
    @Expose 
    String message; 

    @SerializedName("data") 
    @Expose 
    private ArrayList<MyObject> data = new ArrayList<>(); 

    public ArrayList<MyObject> getData() { 
     return data; 
    } 

    public void setData(ArrayList<MyObject> data) { 
     this.data = data; 
    } 
} 

Wo „MyObject“ ist ein Parcel wie folgt aus:

public class MyObject implements Parcelable { 
     @SerializedName("field") 
     @Expose 
     boolean field; 

     ... 
} 

Aber das doesn Es funktioniert nicht, weil "Daten" nicht nur ein Array von Objekten sind; Es ist ein Array, das das Array von Objekten und ein anderes Top-Level-Objekt enthält.

Ich kann "Daten" als "Array" definieren, und es scheint die Werte zu analysieren. Aber dann kommen sie als generische LinkedTreeMap-Objekte heraus, so dass ich die Vorteile von Gson's JSON-> POJO-Parsing verliere.

Gibt es eine elegante Möglichkeit, ein gemischtes Array in Retrofit/Gson zu behandeln? Ich bin nicht für die Daten verantwortlich, die von der API kommen, daher denke ich nicht, dass Änderungen eine Option darstellen.

Antwort

1

Es gibt einen Weg.

Fügen Sie eine benutzerdefinierte hinzu und behandeln Sie dieses Feld manuell.

Beispiele

public static class Deserializer implements JsonDeserializer<JsonResponse> { 

    private final Gson gson = new GsonBuilder().create(); 

    @Override 
    public JsonResponse deserialize(JsonElement json, Type typeOfT, 
             JsonDeserializationContext context) 
      throws JsonParseException { 
     JsonResponse response = gson.fromJson(json, typeOfT); 

     JsonObject jObject = json.getAsJsonObject(); 
     //Handle jObject here, and parse each object of data array 
     //accordingly to its type - JsonObject/JsonArray 
     return response; 
    } 
} 

Sie müssen es in Ihrem globalen Gson Instanz registrieren, wie folgt aus:

new GsonBuilder() 
      .registerTypeAdapter(JsonResponse.class, new JsonResponse.Deserializer()) 
      .create(); 
+0

Aha! Ich wusste, dass es dafür eine Lösung geben musste. Vielen Dank; Ich werde es versuchen. –

+0

hinzugefügt auch mehr Code für die Initialisierung – LukeJanyga

1

Sie scheinen nicht List<MyObject> haben zu verwenden, da Sie mindestens zwei Arten von schaffen haben Objekte: "single" und "multiple", wobei letzteres List<E> implementieren muss, um die Array-ähnliche Schnittstelle zu erfüllen.

Zum Beispiel. JsonResponse ist einfacher als das, was jsonschema2pojo erzeugt:

final class JsonResponse { 

    final boolean success = Boolean.valueOf(false); 
    final String message = null; 
    final List<MyObject> data = null; 

} 

Nun MyObject kann wie folgt aussehen:

abstract class MyObject 
     implements Parcelable { 

    private MyObject() { 
    } 

    static <E> MyObject multiple(final List<E> list) { 
     return new MultipleObjects<>(list); 
    } 

    static final class SingleObject 
      extends MyObject { 

     private SingleObject() { 
     } 

     final String info = null; 
     final String info2 = null; 
     final String info3 = null; 

    } 

    static final class MultipleObjects<E> 
      extends MyObject 
      implements List<E> { 

     private final List<E> list; 

     private MultipleObjects(final List<E> list) { 
      this.list = list; 
     } 

     // @formatter:off 
     @Override public int size() { return list.size(); } 
     @Override public boolean isEmpty() { return list.isEmpty(); } 
     @Override public boolean contains(final Object o) { return list.contains(o); } 
     @Override public Iterator<E> iterator() { return list.iterator(); } 
     @Override public Object[] toArray() { return list.toArray(); } 
     @Override public <T> T[] toArray(final T[] a) { return list.toArray(a); } 
     @Override public boolean add(final E e) { return list.add(e); } 
     @Override public boolean remove(final Object o) { return list.remove(o); } 
     @Override public boolean containsAll(final Collection<?> c) { return list.containsAll(c); } 
     @Override public boolean addAll(final Collection<? extends E> c) { return list.addAll(c); } 
     @Override public boolean addAll(final int index, final Collection<? extends E> c) { return list.addAll(index, c); } 
     @Override public boolean removeAll(final Collection<?> c) { return list.removeAll(c); } 
     @Override public boolean retainAll(final Collection<?> c) { return list.retainAll(c); } 
     @Override public void clear() { list.clear(); } 
     @Override public E get(final int index) { return list.get(index); } 
     @Override public E set(final int index, final E element) { return list.set(index, element); } 
     @Override public void add(final int index, final E element) { list.add(index, element); } 
     @Override public E remove(final int index) { return list.remove(index); } 
     @Override public int indexOf(final Object o) { return list.indexOf(o); } 
     @Override public int lastIndexOf(final Object o) { return list.lastIndexOf(o); } 
     @Override public ListIterator<E> listIterator() { return list.listIterator(); } 
     @Override public ListIterator<E> listIterator(final int index) { return list.listIterator(index); } 
     @Override public List<E> subList(final int fromIndex, final int toIndex) { return list.subList(fromIndex, toIndex); } 
     // @formatter:on 

    } 

} 

Die Klasse oben implementiert eine abstrakte Klasse, die auf zwei Arten implementiert werden kann. Beachten Sie, dass keine öffentlichen Konstruktoren von Entwurf offen gelegt werden: SingleObject kann mit Gson sehr einfach mit der reflektierenden Strategie deserialisiert werden, während MultipleObjects ein Array-ähnliches Objekt ist, das einige manuelle Konstruktion erfordert.

Die Deserialisierung Teil:

final class MyObjectJsonDeserializer 
     implements JsonDeserializer<MyObject> { 

    private static final JsonDeserializer<MyObject> myObjectJsonDeserializer = new MyObjectJsonDeserializer(); 

    // You have to detect it more accurately yourself 
    private static final Type genericListType = new TypeToken<List<Object>>() { 
    }.getType(); 

    private MyObjectJsonDeserializer() { 
    } 

    static JsonDeserializer<MyObject> getMyObjectJsonDeserializer() { 
     return myObjectJsonDeserializer; 
    } 

    @Override 
    public MyObject deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context) 
      throws JsonParseException { 
     if (jsonElement.isJsonNull()) { 
      return null; 
     } 
     if (jsonElement.isJsonObject()) { 
      // Note that the deserialization is implemented using context, 
      // because it makes sure that you are using the Gson instance configuration 
      // Simply speaking: do not create gson instances in 99,9% cases 
      return context.deserialize(jsonElement, MyObject.SingleObject.class); 
     } 
     if (jsonElement.isJsonArray()) { 
      return multiple(context.deserialize(jsonElement, genericListType)); 
     } 
     // Or create a more sophisticated detector... Or redesign your mappigns 
     if (jsonElement.isJsonPrimitive()) { 
      throw new JsonParseException("Cannot parse primitives"); 
     } 
     throw new AssertionError(jsonElement); 
    } 

} 

Beispiel Verwendung:

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

public static void main(final String... args) 
     throws IOException { 
    try (final JsonReader jsonReader = getPackageResourceJsonReader(Q43946453.class, "polymorphic.json")) { 
     final JsonResponse response = gson.fromJson(jsonReader, JsonResponse.class); 
     for (final MyObject datum : response.data) { 
      System.out.println(datum.getClass().getSimpleName()); 
     } 
    } 
} 

Output:

MultipleObjects
Single

Verwandte Themen