Gson: 反序列化既可以是单个对象也可以是数组的对象

3

我正在使用Gson解析来自某个API的JSON响应。

之前一切都正常,但现在似乎响应中的某个字段可以以数组形式或单个元素形式出现,所以我得到了com.google.gson.JsonSyntaxException: java.lang.IllegalStateException:错误。

下面是导致问题的两个JSON版本的片段:

版本1

{
    "Notes": {
        "Note": [
            {
                "key": "disruption-message",
                "section": "high",
                "priority": "1",
                "message": "The battery consumption raised suddenly."
            },
            {
                "key": "disruption-message",
                "section": "low",
                "priority": "2",
                "message": "The power on the converter might be too high."
            }
        ]
    }
}

版本 2

{
    "Notes": {
        "Note": {
            "key": "medium",
            "section": "low",
            "priority": "1",
            "message": "Life time for the battery will expire soon"
        }
    }
}

为了解析版本1,我正在使用以下类:

public class Notes implements Serializable {

    @SerializedName("Note")
    @Expose
    private List<Note> note = null;

    public List<Note> getNote() {
        return note;
    }

    public void setNote(List<Note> note) {
        this.note = note;
    }

}

这个适用于版本1,但是当它找到与版本2匹配的JSON响应部分时,会出现以下情况:

com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT 

我该如何使其反序列化Notes,无论它们的格式是什么?

尝试使用此链接:https://dev59.com/EGEi5IYBdhLWcg3wBH-J - Meenal
2个回答

5

这个问题属于最著名的Gson相关问题组之一,我猜测,不当设计的JSON响应会对其造成伤害。你可以在这里找到确切的解决方案:让GSON接受期望数组的单个对象。一旦你拥有了该类型适配器工厂,你就可以像这样注释你的映射:

final class ResponseV1 {

    @SerializedName("Notes")
    final NotesWrapperV1 notes = null;

}

final class NotesWrapperV1 {

    @SerializedName("Note")
    @JsonAdapter(AlwaysListTypeAdapterFactory.class)
    final List<Note> notes = null;

}

final class Note {

    final String key = null;
    final String section = null;
    final String priority = null;
    final String message = null;

}

我认为,你甚至可以进一步移除内部包装类。

final class ResponseV2 {

    @SerializedName("Notes")
    @JsonAdapter(NestedNotesTypeAdapterFactory.class)
    final List<Note> notes = null;

}

NestedNotesTypeAdapterFactory是这样实现的:

final class NestedNotesTypeAdapterFactory
        implements TypeAdapterFactory {

    private static final TypeToken<List<Note>> noteListTypeToken = new TypeToken<List<Note>>() {
    };

    private NestedNotesTypeAdapterFactory() {
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // Just add the factory method to AlwaysListTypeAdapterFactory and let it just return the class singleton (the factory is stateless, so it can be constructed once)
        final TypeAdapter<List<Note>> noteListTypeAdapter = getAlwaysListTypeAdapterFactory().create(gson, noteListTypeToken);
        final TypeAdapter<List<Note>> nestedNotesTypeAdapter = new NestedNotesTypeAdapter(noteListTypeAdapter);
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) nestedNotesTypeAdapter;
        return typeAdapter;
    }

    private static final class NestedNotesTypeAdapter
            extends TypeAdapter<List<Note>> {

        private final TypeAdapter<List<Note>> noteListTypeAdapter;

        private NestedNotesTypeAdapter(final TypeAdapter<List<Note>> noteListTypeAdapter) {
            this.noteListTypeAdapter = noteListTypeAdapter;
        }

        @Override
        public void write(final JsonWriter out, final List<Note> value) {
            throw new UnsupportedOperationException();
        }

        @Override
        public List<Note> read(final JsonReader in)
                throws IOException {
            // "Unwrap" the Note property here
            in.beginObject();
            List<Note> notes = null;
            while ( in.hasNext() ) {
                final String name = in.nextName();
                switch ( name ) {
                case "Note":
                    // If we've reached the Note property -- just read the list
                    notes = noteListTypeAdapter.read(in);
                    break;
                default:
                    throw new MalformedJsonException("Unrecognized " + name + " at " + in);
                }
            }
            in.endObject();
            return notes;
        }

    }
}

两种实现的测试用例:

for ( final String resource : ImmutableList.of("version-1.json", "version-2.json") ) {
    System.out.println(resource);
    try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q43868120.class, resource) ) {
        final ResponseV1 response = gson.fromJson(jsonReader, ResponseV1.class);
        for ( final Note note : response.notes.notes ) {
            System.out.println(note.message);
        }
    }
}

for ( final String resource : ImmutableList.of("version-1.json", "version-2.json") ) {
    System.out.println(resource);
    try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q43868120.class, resource) ) {
        final ResponseV2 response = gson.fromJson(jsonReader, ResponseV2.class);
        for ( final Note note : response.notes ) {
            System.out.println(note.message);
        }
    }
}

两者都会生成以下内容:

version-1.json
电池消耗突然增加。
转换器的功率可能过高。
version-2.json
电池寿命即将到期。


谢谢!看起来解决方案有点复杂,但肯定值得一试,我会看看能从中得到什么 :) 再次感谢! - codeKiller
所以根据你的回答,我应该只使用ResponseV2,并将Notes名称与Note类一起使用?我有点困惑,抱歉。 - codeKiller
@eddie 你可以。V2只是代表第二种方法,与V1不同的是,不需要中间包装器。Note在两个版本之间共享。你可以根据自己的喜好选择V1或V2。 - Lyubomyr Shaydariv
如果在同一个JSON响应中,我有一个带有数组版本的NoteNotes字段,以及另一个带有单个对象版本的NoteNotes,这是否应该起作用? - codeKiller
@eddie,答案展示了两个例子(我的V1和V2):它们都适用于你的两个JSON文档(你的V1和V2),并给出相同的结果。我没有注意到我在使用“版本”一词时出现了冲突的术语。试试看吧。 - Lyubomyr Shaydariv

0
你遇到的问题是GSON期望一个数组,但JSON是对象的序列化。
基本上,GSON期望'['但找到了'{'。
所以你应该修改你的代码,将版本2中的JSON反序列化为单个Note对象。
此外,你应该查看两个JSON版本。
根据GSON,你的版本2 JSON略有无效。你有一个Note对象列表,必须表示为
"Note"[ notes... ] 这正是版本1中的情况。
我建议将你的版本2 JSON更改为类似以下内容: "Notes"{ "Note":[ 你拥有的任何笔记对象都可以放在这里。 ] } 如果修改JSON不在你的能力范围内,则考虑为类似于此类的情况创建另一个类。
你应该创建一个带有@SerializedNames和Note对象的新类,然后使用它来反序列化JSON。

谢谢你的回答,你是对的,改变JSON响应不在我的控制范围内...所以我只能勉强接受它。 - codeKiller
嗨,Nikhil,我不确定我是否理解你的意思......创建另一个类很简单,但是你必须考虑到我的JSON响应比我发布的那一部分要长得多,因此,更多的“Notes”字段可能会出现在同一个JSON中,其中一些在VERSION 1中,一些在VERSION 2中....然后,如何知道每次使用哪个类来处理{Notes}[Notes] - codeKiller
你可以尝试将JSON作为JSON对象获取,然后获取它的成员“Note”。 - Nikhil R
检查它的类型,无论是JSONArray还是对象,从那里开始就应该很容易了。 - Nikhil R

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接