[Java] Nested JSON-Objekt mit GSON mit Retrofit erhalten


Answers

@ BrianRoachs Lösung ist die richtige Lösung. Beachten Sie, dass Sie in dem speziellen Fall, in dem Sie benutzerdefinierte Objekte verschachtelt haben, die beide ein benutzerdefiniertes TypeAdapter benötigen, das TypeAdapter mit der neuen Instanz von GSON registrieren müssen, andernfalls wird das zweite TypeAdapter nie aufgerufen. Dies liegt daran, dass wir in unserem benutzerdefinierten Deserializer eine neue Gson Instanz Gson .

Zum Beispiel, wenn du folgendes json hast:

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

Und Sie wollten, dass dieser JSON folgenden Objekten zugeordnet wird:

class MainContent
{
    public int foo;
    public String bar;
    public SubContent subcontent;
}

class SubContent
{
    public String baz;
}

Sie müssten den SubContent des SubContent TypeAdapter . Um robuster zu sein, könnten Sie Folgendes tun:

public class MyDeserializer<T> implements JsonDeserializer<T> {
    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);

    }
}

und dann erstelle es so:

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

Dies könnte leicht auch für den verschachtelten "Inhalt" -Fall verwendet werden, indem einfach eine neue Instanz von MyDeserializer mit Null-Werten übergeben wird.

Question

Ich verwende eine API von meiner Android-App und alle JSON-Antworten lauten wie folgt:

{
    'status': 'OK',
    'reason': 'Everything was fine',
    'content': {
         < some data here >
}

Das Problem ist, dass alle meine POJOs einen status , reason und innerhalb des content ist das POJO, das ich will.

Gibt es eine Möglichkeit, einen benutzerdefinierten Konverter von Gson zu erstellen, um immer das Inhaltsfeld zu extrahieren, so dass Retrofit das passende POJO zurückgibt?




In meinem Fall würde sich der Schlüssel "Inhalt" für jede Antwort ändern. Beispiel:

// 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
    }
}

In solchen Fällen habe ich eine ähnliche Lösung wie oben erwähnt verwendet, musste sie aber anpassen. Sie können das Wesentliche here . Es ist ein bisschen zu groß, um es hier auf SOF zu posten.

Die Anmerkung @InnerKey("content") wird verwendet und der Rest des Codes soll die Verwendung mit Gson erleichtern.




Dies ist die gleiche Lösung wie @AYarulin, aber der Klassenname ist der JSON-Schlüsselname. Auf diese Weise müssen Sie nur den Klassennamen übergeben.

 class RestDeserializer<T> implements JsonDeserializer<T> {

    private Class<T> mClass;
    private String mKey;

    public RestDeserializer(Class<T> 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);

    }
}

Um die Beispielnutzlast von oben zu analysieren, können wir GSON Deserializer registrieren. Dies ist problematisch, da der Schlüssel die Groß- / Kleinschreibung unterscheidet. Daher muss der Fall des Klassennamens mit dem Fall des JSON-Schlüssels übereinstimmen.

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



Brians Idee wird weitergeführt, da wir fast immer viele REST-Ressourcen mit jeweils eigenem Root haben, könnte es sinnvoll sein, die Deserialisierung zu verallgemeinern:

 class RestDeserializer<T> implements JsonDeserializer<T> {

    private Class<T> mClass;
    private String mKey;

    public RestDeserializer(Class<T> targetClass, String 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);

    }
}

Um die Beispielnutzlast von oben zu analysieren, können wir GSON Deserializer registrieren:

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



Hier ist eine Kotlin-Version basierend auf den Antworten von Brian Roach und AYarulin.

class RestDeserializer<T>(targetClass: Class<T>, key: String?) : JsonDeserializer<T> {
    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)
    }
}