Вопрос-ответ

Why does Gson fromJson throw a JsonSyntaxException: Expected BEGIN_OBJECT but was BEGIN_ARRAY?

Почему Gson из Json выдает исключение JsonSyntaxException: ожидаемый BEGIN_OBJECT, но был BEGIN_ARRAY?

(Этот пост задуман как канонический вопрос с примером ответа, приведенным ниже.)


Я пытаюсь десериализовать некоторое содержимое JSON в пользовательский тип POJO с помощью Gson#fromJson(String, Class).

Этот фрагмент кода

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

выдает следующее исключение

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

Почему Gson не может правильно преобразовать мой текст JSON в мой тип POJO?

Переведено автоматически
Ответ 1

Как указано в сообщении об исключении

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

во время десериализации Gson ожидал объект JSON, но обнаружил массив JSON. Поскольку он не смог преобразовать из одного в другой, он выдал это исключение.

Формат JSON описан здесь. Короче говоря, он определяет следующие типы: объекты, массивы, строки, числа, null и логические значения true и false.

В Gson (и большинстве парсеров JSON) существуют следующие сопоставления: строка JSON сопоставляется с Java String; число JSON сопоставляется с типом Java Number; массив JSON сопоставляется с Collection типом или типом массива; объект JSON сопоставляется с типом Java Map или, как правило, пользовательским типом POJO (не упоминалось ранее); null сопоставляется с типом Java null, а логические значения сопоставляются с типом Java true и false.

Gson выполняет итерацию по предоставляемому вами контенту JSON и пытается десериализовать его до соответствующего запрошенного вами типа. Если содержимое не соответствует или не может быть преобразовано в ожидаемый тип, это вызовет соответствующее исключение.

В вашем случае вы предоставили следующий JSON

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

В корне это объект JSON, который содержит элемент с именем nestedPojo который является массивом JSON. Этот массив JSON содержит один элемент, другой объект JSON с двумя элементами. Учитывая сопоставления, определенные ранее, можно было бы ожидать, что этот JSON будет отображаться в объект Java, у которого есть поле с именем nestedPojo некоторого Collection типа или массива, где этот тип определяет два поля с именами name и value соответственно.

Однако вы определили свой Pojo тип как имеющий поле

NestedPojo nestedPojo;

это ни тип массива, ни Collection тип. Gson не может десериализовать соответствующий JSON для этого поля.

Вместо этого у вас есть 3 варианта:


  • Измените свой JSON, чтобы он соответствовал ожидаемому типу


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

  • Измените свой Pojo тип, чтобы ожидать Collection или тип массива


    List<NestedPojo> nestedPojo; // consider changing the name and using @SerializedName
    NestedPojo[] nestedPojo;

  • Напишите и зарегистрируйте пользовательский десериализатор для NestedPojo с вашими собственными правилами синтаксического анализа. Например


    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();

Ответ 2
class Pojo {
NestedPojo nestedPojo;
}

в вашем json у вас есть массив nestedPojo
так что либо вы меняете код

  NestedPojo[] nestedPojo;

или вы меняете строку json

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