2012-05-15 13 views
19

aufrufen Ich bekomme ein JSON, das Feld "Feld" hat.
Wenn das "Feld" Daten enthält, gibt es ein OBJEKT, das viele (etwa 20) andere Felder enthält, die ebenfalls Objekte sind. Ich kann sie ohne Probleme deserialisieren.
Aber wenn "Feld" keine Daten hat, ist es ein leeres ARRAY (ich weiß, es ist verrückt, aber das ist die Antwort vom Server und ich kann es nicht ändern). Etwas wie folgt aus:Wie Standard-Deserialisierung mit Gson

Wenn leer:

"extras":[ 

] 

hat einige Daten:

"extras": { 
    "22":{ "name":"some name" }, 
    "59":{ "name":"some other name" }, 
    and so on... 
} 

Also, wenn es, wenn keine Daten (leeres Array), bekomme ich natürlich die Ausnahme

Caused by: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: 
Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 4319 

Ich habe versucht, benutzerdefinierte JavaDeserializer verwenden:

public class ExtrasAdapter implements JsonDeserializer<Extras> { 
    @Override 
    public Extras deserialize(JsonElement json, Type typeOf, 
     JsonDeserializationContext context) throws JsonParseException { 
     try { 
      JsonObject jsonObject = json.getAsJsonObject(); 
      // deserialize normally 

      // the following does not work, as it makes recursive calls 
      // to the same function 
      //return context.deserialize(jsonObject, 
      //      new TypeToken<Object>(){}.getType()); 
     } catch (IllegalStateException e) { 
      return null; 
     } 
    } 
} 

Ich las die json die folgende Art und Weise

Gson gsonDecoder = new GsonBuilder().registerTypeAdapter(Extras.class, new ExtrasAdapter(); 
// httpResponse contains json with extras filed. 
Reader reader = new InputStreamReader(httpResponse.getEntity().getContent()); 
Extras response = gsonDecoder.fromJson(reader, Extras.class); 

Ich will nicht manuell alle 20 Felder deserialisiert (Ich weiß, das ist eine Option), ich möchte nur context.defaultDeserialize nennen (), oder etwas ähnliches.
Noch einmal: Ich habe keine Probleme deserializing normalen json, Erstellen von benutzerdefinierten Objekten, Registrierung von benutzerdefinierten TypeAdapters, benutzerdefinierte JavaDeserializers. Es funktioniert alles schon. Ich brauche nur eine Lösung für den Umgang mit Daten, die sowohl ARRAY als auch OBJECT sein können.
Danke für jede Hilfe.

======================


Die Antwort des Joey funktioniert perfekt. Genau das Richtige, nach dem ich gesucht habe. Ich poste meinen Code hier.

public class SafeTypeAdapterFactory implements TypeAdapterFactory { 
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { 
     final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type); 
     return new TypeAdapter<T>() { 
      public void write(JsonWriter out, T value) throws IOException { 
       try { 
        delegate.write(out, value); 
       } catch (IOException e) { 
        delegate.write(out, null); 
       } 
      } 
      public T read(JsonReader in) throws IOException { 
       try { 
        return delegate.read(in); 
       } catch (IOException e) { 
        Log.w("Adapter Factory", "IOException. Value skipped"); 
        in.skipValue(); 
        return null; 
       } catch (IllegalStateException e) { 
        Log.w("Adapter Factory", "IllegalStateException. Value skipped"); 
        in.skipValue(); 
        return null; 
       } catch (JsonSyntaxException e) { 
        Log.w("Adapter Factory", "JsonSyntaxException. Value skipped"); 
        in.skipValue(); 
        return null; 
       } 
      } 
     }; 
    } 
} 
+0

Fügen Sie den Code ein, in dem Sie Daten deserialisieren, wenn Array verfügbar ist – waqaslam

+0

Array kann nicht verfügbar sein. Es kann nur leer sein. Wenn ich das Array deserialisieren müsste, würde ich es im catch-Block machen. –

Antwort

14

Versuchen Gson> = 2.2.1 und suchen die TypeAdapterFactory Klasse.

Dies gibt Ihnen die Möglichkeit, das Objekt zu überprüfen, bevor Sie es deserialisieren und benutzerdefinierten Code anwenden, während Rekursionen vermieden werden.

Hier ist ein Beispiel für die getDelegateAdapter, die Sie verwenden können.

+1

+1 für getDelegateAdapter() Dies ist die gute Lösung für die Implementierung einer gemeinsamen Funktionalität unabhängig von der Art des Objekts. Zum Beispiel Aufruf einer Post-Deserialisierungsfunktion für das Objekt. – nidheeshdas

6
public class ExtrasAdapter implements JsonDeserializer<Extras> { 
@Override 
public Extras deserialize(JsonElement json, Type typeOf, 
       JsonDeserializationContext context) throws JsonParseException { 
    try { 
     JsonObject jsonObject = json.getAsJsonObject(); 
     return new Gson().fromJson(jsonObject , Extras.class); // default deserialization 

    } catch (IllegalStateException e) { 
     return null; 
    } 
} 
2

Für jedermann Ende kommen, brauchen Sie keinen TypeAdapter zu implementieren, dieses Problem zu lösen, obwohl eine absolut gültige Lösung so tut.

Die Antwort auf dieses Problem ist eigentlich in der ursprünglichen Frage:

public class ExtrasAdapter implements JsonDeserializer<Extras> { 

@Override 
public Extras deserialize(JsonElement json, Type typeOf, 
      JsonDeserializationContext context) throws JsonParseException { 
    try { 
     JsonObject jsonObject = json.getAsJsonObject(); 
     // deserialize normally 

     // the following does not work, as it makes recursive calls 
     // to the same function 
     //return context.deserialize(jsonObject, new TypeToken<Object>(){}.getType()); 
    } catch (IllegalStateException e) { 
     return null; 
    } 
} 

Die

kommentiert out
return context.deserialize(jsonObject, new TypeToken<Object>(){}.getType()); 

ist fast die Lösung. Das Problem ist zweifach. Erstens ist jsonObject das genaue Objekt, das ursprünglich in diese Funktion übergeben wurde.

JsonObject jsonObject = json.getAsJsonObject(); 

es in context.deserialize So vorbei() wird Rekursion und schließlich OOM erstellen. Die Lösung besteht darin, die Objekte innerhalb von jsonObject zu analysieren.

Dies führt uns zu dem zweiten Problem, das darin besteht, dass hier zwei Dinge gemischt werden. "Extras" ist ein Objekttyp, vermutlich mit einer konkreten Klasse dahinter (und möglicherweise einem leeren Array). "Extra" ist eine Karte. Der Versuch, ein "Extra" als "Extras" zu parsen, wird nicht funktionieren. Zu diesem Zweck würde ich die folgende Definition von „Extras“ vorschlagen:

public class Extras { 
    Map<String, Map<String, String>> extras; 
    // you could also create a concrete class for "Extra" 
    //and have this be a Map<String, Extra> 
} 

In diesem Fall ist das Problem für die Lösung mit context.deserialize trivial wird.

Wie oben erwähnt, ist ein TypeAdatper eine vollkommen gültige Lösung für dieses Problem. Ich glaube nur, dass es mehr ist, als du brauchst.