Gson handle object or array

I came up with an answer.

private static class MyOtherClassTypeAdapter implements JsonDeserializer<List<MyOtherClass>> {
    public List<MyOtherClass> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext ctx) {
        List<MyOtherClass> vals = new ArrayList<MyOtherClass>();
        if (json.isJsonArray()) {
            for (JsonElement e : json.getAsJsonArray()) {
                vals.add((MyOtherClass) ctx.deserialize(e, MyOtherClass.class));
            }
        } else if (json.isJsonObject()) {
            vals.add((MyOtherClass) ctx.deserialize(json, MyOtherClass.class));
        } else {
            throw new RuntimeException("Unexpected JSON type: " + json.getClass());
        }
        return vals;
    }
}

Instantiate a Gson object like this

Type myOtherClassListType = new TypeToken<List<MyOtherClass>>() {}.getType();

Gson gson = new GsonBuilder()
        .registerTypeAdapter(myOtherClassListType, new MyOtherClassTypeAdapter())
        .create();

That TypeToken is a com.google.gson.reflect.TypeToken.

You can read about the solution here:

https://sites.google.com/site/gson/gson-user-guide#TOC-Serializing-and-Deserializing-Gener


Thank you three-cups for the solution!

The same thing with generic type in case it's needed for multiple types:

public class SingleElementToListDeserializer<T> implements JsonDeserializer<List<T>> {

private final Class<T> clazz;

public SingleElementToListDeserializer(Class<T> clazz) {
    this.clazz = clazz;
}

public List<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
    List<T> resultList = new ArrayList<>();
    if (json.isJsonArray()) {
        for (JsonElement e : json.getAsJsonArray()) {
            resultList.add(context.<T>deserialize(e, clazz));
        }
    } else if (json.isJsonObject()) {
        resultList.add(context.<T>deserialize(json, clazz));
    } else {
        throw new RuntimeException("Unexpected JSON type: " + json.getClass());
    }
    return resultList;
    }
}

And configuring Gson:

Type myOtherClassListType = new TypeToken<List<MyOtherClass>>() {}.getType();
SingleElementToListDeserializer<MyOtherClass> adapter = new SingleElementToListDeserializer<>(MyOtherClass.class);
Gson gson = new GsonBuilder()
    .registerTypeAdapter(myOtherClassListType, adapter)
    .create();

to share code, and to only apply the deserialization logic to specific fields:

JSON model:

public class AdminLoginResponse implements LoginResponse
{
    public Login login;
    public Customer customer;
    @JsonAdapter(MultiOrganizationArrayOrObject.class)    // <-------- look here
    public RealmList<MultiOrganization> allAccounts;
}

abstract class:

/**
 * parsed field can either be a [JSONArray] of type [Element], or an single [Element] [JSONObject].
 */
abstract class ArrayOrSingleObjectTypeAdapter<TypedList: List<Element>, Element : Any>(
    private val elementKClass: KClass<Element>
) : JsonDeserializer<TypedList> {
    override fun deserialize(
        json: JsonElement, typeOfT: Type?, ctx: JsonDeserializationContext
    ): TypedList = when {
        json.isJsonArray -> json.asJsonArray.map { ctx.deserialize<Element>(it, elementKClass.java) }
        json.isJsonObject -> listOf(ctx.deserialize<Element>(json, elementKClass.java))
        else -> throw RuntimeException("Unexpected JSON type: " + json.javaClass)
    }.toTypedList()
    abstract fun List<Element>.toTypedList(): TypedList
}

field-specific class:

class MultiOrganizationArrayOrObject
    : ArrayOrSingleObjectTypeAdapter<RealmList<MultiOrganization>,MultiOrganization>(kClass()) {
    override fun List<MultiOrganization>.toTypedList() = RealmList(*this.toTypedArray())
}