【问题标题】:Retrofit2 - GSON - Expected BEGIN_ARRAY but was STRING or Expected BEGIN_OBJECT but was BEGIN_ARRAYRetrofit2 - GSON - 应为 BEGIN_ARRAY 但为 STRING 或应为 BEGIN_OBJECT 但为 BEGIN_ARRAY
【发布时间】:2018-07-17 21:23:54
【问题描述】:

我的 Retrofit2 调用出现以下异常:

com.google.gson.JsonSyntaxException:java.lang.IllegalStateException:应为 BEGIN_ARRAY,但在第 1 行第 22311 列路径 $[1].programme[3].credits.presenter

我可能知道它为什么会被抛出。一些“信用”对象有多个“演示者”,它们以 JSON 数组({"presenter":["Barbara Schledduytkew","Hubert Muckhutgoldwes"]})的形式给出,但是,有些其他人只有一个“presenter”,它以 JSON 对象的形式给出 ({"presenter":"Rosalynda Demstogtrojkt"})

我需要找到一种方法来解析两种情况下的演示者项目。我想我必须编写自己的自定义 TypeAdapter,但我不确定并且需要帮助。提前致谢。

改造实例:

retrofit = new Retrofit.Builder()
                .baseUrl(Constants.URL_HOST)
                .client(okHttpClient.build())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

学分类

public class Credits {

    @SerializedName("director")
    @Expose
    private List<String> director;
    @SerializedName("actor")
    @Expose
    private List<String> actor = null;
    @SerializedName("presenter")
    @Expose
    private List<String> presenter;
    @SerializedName("commentator")
    @Expose
    private String commentator;

    public List<String> getDirector() {
        return director;
    }

    public void setDirector(List<String> director) {
        this.director = director;
    }

    public List<String> getActor() {
        return actor;
    }

    public void setActor(List<String> actor) {
        this.actor = actor;
    }

    public List<String> getPresenter() {
        return presenter;
    }

    public void setPresenter(List<String> presenter) {
        this.presenter = presenter;
    }

    public String getCommentator() {
        return commentator;
    }

    public void setCommentator(String commentator) {
        this.commentator = commentator;
    }
}

【问题讨论】:

标签: android json gson retrofit2


【解决方案1】:

您对为什么会发生此错误是正确的。它试图解析一个数组但接收到一个字符串。至于您的数据,最好坚持使用相同的模型。这意味着:使后端也返回一个数组,即使它只包含一个演示者。

不过,是的:我们可以使用自定义类型适配器解决此问题。 这个自定义类型适配器将简单地检查读取部分是否获取字符串或数组。如果它得到一个字符串,我们将它重写为 List 变体。

类型适配器

类型适配器做了两件事:

  • 定义读取 json 时应该做什么 -> read() 方法。
  • 定义了编写 json 时应该做什么 -> write() 方法。

在这种情况下不使用后一种,因为我们只尝试读取数据。所以我们只为write() 方法编写数组。有点类似于 Collections 的原始类型适配器实现。

读取数据

  • 我们首先通过read()方法中的reader.peek() == JsonToken.STRING检查是否收到了json中的String。如果是这样,我们自己创建列表。
  • 如果我们得到一个数组,我们会像以前一样解析它。

结果 生成的类型适配器将如下所示:

public class ListFromStringTypeAdapter extends TypeAdapter<List<String>> {

    public List<String> read(JsonReader reader) throws IOException {
        if (reader.peek() == JsonToken.NULL) {
            reader.nextNull();
            return null;
        }
        if (reader.peek() == JsonToken.STRING) {
            // ** This is the part where we fix the issue **
            // If we receive a String, get this and put it in a list.
            // Result will be that item in a list.
            List<String> list = new ArrayList<>();
            list.add(reader.nextString());
            return list;
        } else {
            // Else we expect to receive the array.
            // Based on original collection implementation:
            // https://github.com/google/gson/blob/0636635cbffa08157bdbd558b1212e4d806474eb/gson/src/main/java/com/google/gson/internal/bind/CollectionTypeAdapterFactory.java
            List<String> list = new ArrayList<>();
            reader.beginArray();
            while (reader.hasNext()) {
                String value = reader.nextString();
                list.add(value);
            }
            reader.endArray();
            return list;
        }
    }

    public void write(JsonWriter writer, List<String> list) throws IOException {
        // Simply writing the array, we don't need to modify anything here.
        // Based on original collection type adapter implementation:
        // https://github.com/google/gson/blob/0636635cbffa08157bdbd558b1212e4d806474eb/gson/src/main/java/com/google/gson/internal/bind/CollectionTypeAdapterFactory.java
        if (list == null) {
            writer.nullValue();
            return;
        }
        writer.beginArray();
        for (String string : list) {
            writer.value(string);
        }
        writer.endArray();
    }
}

注册类型适配器 最后,我们需要告诉 Gson 为 List 类使用这个类型适配器,所以修改你创建 Gson 对象的位置:

// Get the type token for gson
Type collectionStringType = new TypeToken<List<String>>() {}.getType();
// Create gson object
Gson gson = new GsonBuilder()
        .registerTypeAdapter(collectionStringType, new ListFromStringTypeAdapter())
        .create();

【讨论】:

  • 非常感谢您的详细解答。它解决了问题。
【解决方案2】:

您可以通过自定义 JsonDeserializer 来完成此操作,该 JsonDeserializer 检查 presenter 键是字符串还是数组:

public final class CreditsJsonDeserializer implements JsonDeserializer<Credits> {

    private CreditsJsonDeserializer() {}

    private static final CreditsJsonDeserializer INSTANCE = new CreditsJsonDeserializer();

    public static CreditsJsonDeserializer instance() {
        return INSTANCE;
    }

    @Override
    public Credits deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        if (json.isJsonObject()) {
            // Set up an object to return
            Credits newCredits = new Credits();
            // Loop through the keys in our JSON object
            for (Map.Entry<String, JsonElement> kvp : json.getAsJsonObject().entrySet()) {
                // Get key
                String key = kvp.getKey();
                // Get value
                JsonElement value = kvp.getValue();
                // If we have a null value, just go to the next key
                if (value.isJsonNull()) continue;
                // Check our key to see which field we need to deserialize
                switch (key) {
                    // Deserialize our List of Directors
                    case "director":
                        newCredits.setDirector(context.deserialize(value, new TypeToken<ArrayList<String>>(){}.getType()));
                        break;
                    // Deserialize our List of Actors
                    case "actor":
                        newCredits.setActor(context.deserialize(value, new TypeToken<ArrayList<String>>(){}.getType()));
                        break;
                    // Deserialize our Presenter name or List of Presenters
                    case "presenter":
                        // Check if it's a singular name
                        if (value.isJsonPrimitive() && value.getAsJsonPrimitive().isString()) {
                            ArrayList<String> presenters = new ArrayList<>();
                            presenters.add(value.getAsString());
                            newCredits.setPresenter(presenters);
                        }
                        // Else, it's an Array of names
                        else {
                          newCredits.setPresenter(context.deserialize(value, new TypeToken<ArrayList<String>>(){}.getType()));
                        }
                        break;
                    // Deserialize our Commentator name
                    case "commentator":
                        newCredits.setCommentator(value.getAsString());
                        break;
                    default:
                        break;
                }
            }
            return newCredits;
        }
        else {
            return null;
        }
    }
}

然后,更改您的 Retrofit 实例代码以使用新的反序列化器:

Gson gson = new GsonBuilder()
                .registerTypeAdapter(Credits.class, CreditsJsonDeserializer.instance())
                .create();

retrofit = new Retrofit.Builder()
                .baseUrl(Constants.URL_HOST)
                .client(okHttpClient.build())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

【讨论】:

    猜你喜欢
    • 2013-05-15
    • 2015-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-24
    • 2015-11-30
    • 1970-01-01
    • 2016-06-20
    相关资源
    最近更新 更多