为什么来自Json的Gson抛出了JsonSyntaxException:Expected BEGIN_OBJECT但BEGIN_ARRAY?

2022-09-02 19:42:26

(这篇文章是一个规范的问题,下面提供了一个示例答案。


我正在尝试使用Gson#fromJson(String,Class)将一些JSON内容反序列化为自定义POJO类型。

这段代码

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 格式。简而言之,它定义了以下类型:对象、数组、字符串、数字以及布尔值和 。nulltruefalse

在Gson(和大多数JSON解析器)中,存在以下映射:JSON字符串映射到Java ;JSON 编号映射到 Java 类型;JSON数组映射到类型或数组类型;JSON对象映射到Java类型,或者通常映射到自定义POJO类型(前面未提及); 映射到 Java 的 ,布尔值映射到 Java 的 和 。StringNumberCollectionMapnullnulltruefalse

Gson 循环访问您提供的 JSON 内容,并尝试将其反序列化为您请求的相应类型。如果内容不匹配或无法转换为预期类型,则会引发相应的异常。

在您的例子中,您提供了以下 JSON

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

在根目录中,这是一个 JSON 对象,其中包含一个名为 JSON 数组的成员。该 JSON 数组包含单个元素,即另一个具有两个成员的 JSON 对象。考虑到前面定义的映射,您希望此 JSON 映射到一个 Java 对象,该对象具有一个名为 some 或数组类型的字段,其中该类型分别定义两个名为 和 的字段。nestedPojonestedPojoCollectionnamevalue

但是,您已将类型定义为具有字段Pojo

NestedPojo nestedPojo;

既不是数组类型,也不是类型。Gson 无法反序列化此字段的相应 JSON。Collection

相反,您有 3 个选项:

  • 更改 JSON 以匹配预期类型

    {
        "nestedPojo": {
            "name": null,
            "value": 42
        }
    }
    
  • 将类型更改为需要 或 数组类型PojoCollection

    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中,你有一个嵌套Pojo的数组,所以你要么改变代码

  NestedPojo[] nestedPojo;

或者您更改 json 字符串

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