Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

android - Gson: deserialize objects that can be a single object or an array

I am using Gson to parse a JSON response from a certain API. Everything was working ok, but now seems like one of the fields of the response can come in an array form or in a single element form, so that I am getting com.google.gson.JsonSyntaxException: java.lang.IllegalStateException:

Here you can see the fragment of the two JSON versions causing problems:

VERSION 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."
            }
        ]
    }
}

VERSION 2

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

To parse the VERSION 1, I am using the following class:

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

}

This works with the VERSION 1 but when it finds a part of the JSON response matching the VERSION 2, of course it gives:

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

How can I make it deserialize Notes, whatever format they have?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

This question belongs to one of the most famous Gson-related question groups, I guess, and improperly designed JSON responses hurt. You can find the exact solution here: Make GSON accept single objects where it expects arrays . Once you have that type adapter factory, you can annotate your mappings like these:

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;

}

IMO, you can proceed even further and just remove the inner wrapper class.

final class ResponseV2 {

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

}

Where NestedNotesTypeAdapterFactory is implemented like this:

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

    }
}

Test cases for both implementations:

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

Both produce the following:

version-1.json
The battery consumption raised suddenly.
The power on the converter might be too high.
version-2.json
Life time for the battery will expire soon


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
...