Perché Gson di Johnson lancia una JsonSyntaxException: Si aspettava un certo tipo ma era un altro tipo?

(Questo post è pensato per essere una domanda canonica con una risposta di esempio fornita di seguito.)


Sto provando a deserializzare alcuni contenuti JSON in un tipo POJO personalizzato con Gson#fromJson(String, Class) .

Questo pezzo di codice

 import com.google.gson.Gson; public class Sample { public static void main(String[] args) { String json = "{\"nestedPojo\":[{\"name\":null, \"value\":42}]}"; Gson gson = new Gson(); gson.fromJson(json, Pojo.class); } } class Pojo { NestedPojo nestedPojo; } class NestedPojo { String name; int value; } 

lancia l’eccezione seguente

 Exception in thread "main" com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 16 path $.nestedPojo at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:200) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:103) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:196) at com.google.gson.Gson.fromJson(Gson.java:810) at com.google.gson.Gson.fromJson(Gson.java:775) at com.google.gson.Gson.fromJson(Gson.java:724) at com.google.gson.Gson.fromJson(Gson.java:696) at com.example.Sample.main(Sample.java:23) Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 16 path $.nestedPojo at com.google.gson.stream.JsonReader.beginObject(JsonReader.java:387) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:189) ... 7 more 

Perché Gson non può convertire correttamente il mio testo JSON nel mio tipo POJO?

Come afferma il messaggio di eccezione

 Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 16 path $.nestedPojo 

durante la deserializzazione, Gson si aspettava un object JSON, ma ha trovato un array JSON. Dal momento che non poteva convertire da uno all’altro, ha lanciato questa eccezione.

Il formato JSON è descritto qui . In breve, definisce i seguenti tipi: oggetti, matrici, stringhe, numeri, null e valori booleani true e false .

In Gson (e nella maggior parte dei parser JSON), esistono i seguenti mapping: una stringa JSON viene mappata su una String Java; un numero JSON si associa a un tipo Number Java; un array JSON esegue il mapping a un tipo di Collection o un tipo di matrice; un object JSON si associa a un tipo di Map Java o, in genere, un tipo POJO personalizzato (non menzionato in precedenza); mappe null di Java, e valori booleani mappati su Java true e false .

Gson itera attraverso il contenuto JSON che fornisci e prova a deserializzare il tipo corrispondente che hai richiesto. Se il contenuto non corrisponde o non può essere convertito nel tipo previsto, genererà un’eccezione corrispondente.

Nel tuo caso, hai fornito il seguente JSON

 { "nestedPojo": [ { "name": null, "value": 42 } ] } 

Alla radice, questo è un object JSON che contiene un membro chiamato nestedPojo che è un array JSON. Quella matrice JSON contiene un singolo elemento, un altro object JSON con due membri. Considerando i mapping definiti in precedenza, ci si aspetterebbe che questo JSON nestedPojo mapping su un object Java che ha un campo denominato nestedPojo di alcuni tipi Collection o array, dove quei tipi definiscono rispettivamente due campi denominati name e value .

Tuttavia, hai definito il tuo tipo Pojo come un campo

 NestedPojo nestedPojo; 

non è né un tipo di array, né un tipo di Collection . Gson non può deserializzare il JSON corrispondente per questo campo.

Invece, hai 3 opzioni:

  • Cambia il tuo JSON in modo che corrisponda al tipo atteso

     { "nestedPojo": { "name": null, "value": 42 } } 
  • Cambia il tuo tipo di Pojo per aspettarti una Collection o un tipo di array

     List nestedPojo; // consider changing the name and using @SerializedName NestedPojo[] nestedPojo; 
  • Scrivi e registra un deserializzatore personalizzato per NestedPojo con le tue regole di analisi. Per esempio

     class Custom implements JsonDeserializer { @Override public NestedPojo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { NestedPojo nestedPojo = new NestedPojo(); JsonArray jsonArray = json.getAsJsonArray(); if (jsonArray.size() != 1) { throw new IllegalStateException("unexpected json"); } JsonObject jsonObject = jsonArray.get(0).getAsJsonObject(); // get only element JsonElement jsonElement = jsonObject.get("name"); if (!jsonElement.isJsonNull()) { nestedPojo.name = jsonElement.getAsString(); } nestedPojo.value = jsonObject.get("value").getAsInt(); return nestedPojo; } } Gson gson = new GsonBuilder().registerTypeAdapter(NestedPojo.class, new Custom()).create(); 
 class Pojo { NestedPojo nestedPojo; } 

nel tuo json hai una matrice di nestedPojo così puoi cambiare il codice

  NestedPojo[] nestedPojo; 

o cambi la stringa JSON

 String json = "{\"nestedPojo\":{\"name\":null, \"value\":42}}";