web-dev-qa-db-fra.com

Pourquoi Gson fromJson lève une exception JsonSyntaxException: BEGIN_OBJECT attendu mais BEGIN_ARRAY?

(Ce message est censé être un question canonique avec un exemple de réponse fourni ci-dessous.)


J'essaie de désérialiser du contenu JSON en un type POJO personnalisé avec Gson#fromJson(String, Class) .

Ce morceau de code

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

lève l'exception de suivi

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

Pourquoi Gson ne peut-il pas convertir correctement mon texte JSON en mon type POJO?

13

Comme l'indique le message d'exception

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

lors de la désérialisation, Gson attendait un objet JSON, mais a trouvé un tableau JSON. Puisqu'il ne pouvait pas se convertir de l'un à l'autre, il a levé cette exception.

Le format JSON est décrit ici . En bref, il définit les types suivants: objets, tableaux, chaînes, nombres, null et les valeurs booléennes true et false.

Dans Gson (et la plupart des analyseurs JSON), les mappages suivants existent: une chaîne JSON correspond à un Java String; un numéro JSON correspond à un Java Number type; un tableau JSON correspond à un type Collection ou un type tableau; un objet JSON correspond à un Java Map tapez ou, typiquement, un type personnalisé [~ # ~] pojo [~ # ~] (non mentionné précédemment); null correspond au null de Java et les valeurs booléennes au true et false de Java.

Gson parcourt le contenu JSON que vous fournissez et essaie de le désérialiser au type correspondant que vous avez demandé. Si le contenu ne correspond pas ou ne peut pas être converti au type attendu, il lèvera une exception correspondante.

Dans votre cas, vous avez fourni le JSON suivant

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

À la racine, il s'agit d'un objet JSON qui contient un membre nommé nestedPojo qui est un tableau JSON. Ce tableau JSON contient un seul élément, un autre objet JSON avec deux membres. Compte tenu des mappages définis précédemment, vous vous attendriez à ce que ce JSON soit mappé à un objet Java qui a un champ nommé nestedPojo d'un certain Collection ou type de tableau, où ce type définit deux champs nommés name et value, respectivement.

Cependant, vous avez défini votre type Pojo comme ayant un champ

NestedPojo nestedPojo;

ce n'est ni un type de tableau, ni un type Collection. Gson ne peut pas désérialiser le JSON correspondant pour ce champ.

Au lieu de cela, vous avez 3 options:

  • Modifiez votre JSON pour qu'il corresponde au type attendu

    {
        "nestedPojo": {
            "name": null,
            "value": 42
        }
    }
    
  • Modifiez votre type Pojo pour vous attendre à un type Collection ou tableau

    List<NestedPojo> nestedPojo; // consider changing the name and using @SerializedName
    NestedPojo[] nestedPojo;
    
  • Écrivez et enregistrez un désérialiseur personnalisé pour NestedPojo avec vos propres règles d'analyse. Par exemple

    class Custom implements JsonDeserializer<NestedPojo> {
        @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();
    
30
class Pojo {
  NestedPojo nestedPojo;
}

dans votre json, vous avez un tableau de nestedPojo donc soit vous changez le code

  NestedPojo[] nestedPojo;

ou vous changez la chaîne json

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