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