Mostafa Saadat
Mostafa Saadat

Reputation: 111

Moshi + Retrofit - Handling JSON response of unknown type

I'm using Moshi and I have the following data classes

@JsonClass(generateAdapter = true)
data class A (
    @Json(name = "_id")
    val id: String?,
    @Json(name = "b")
    val b: B? = null
)

@JsonClass(generateAdapter = true)
data class B (
    @Json(name = "_id")
    val id: String?,
    @Json(name = "foo")
    val foo: String? = null
    @Json(name = "c")
    val c: C? = null
)

@JsonClass(generateAdapter = true)
data class C (
    @Json(name = "_id")
    val id: String?,
    @Json(name = "bar")
    val bar: String? = null
)

My API sometimes returns the object as just an ID, while other times it returns it as the actual object. For example, sometimes when I get the object A it will return

{
    _id: "111111111",
    b: {
        _id: "222222222",
        foo: "foo",
        c: {
            _id: "333333333",
            bar: "bar"
        }
    }
}

but other times it may return

{
    _id: "111111111",
    b: "222222222"
}

or

{
    _id: "111111111",
    b: {
        _id: "222222222",
        foo: "foo",
        c: "333333333"
    }
}

As we see, it may return a string representing the object, or the populated object itself. How do I create a custom Moshi adapter to handle this? If it returns an id representing the object, I'd like it to create an object with just the id populated and the rest of the fields set to null.

I tried creating a custom adapter like so

class bAdapter {
    @FromJson
    fun fromJson(b: Any): B {
        return when (b) {
            is String -> B(b)
            else -> b as B
        }
    }
}

but I get the error

com.squareup.moshi.JsonDataException: java.lang.ClassCastException: com.squareup.moshi.LinkedHashTreeMap cannot be cast to com.example.B

Upvotes: 3

Views: 1872

Answers (1)

Mostafa Saadat
Mostafa Saadat

Reputation: 111

If anyone is having the same problem, I solved it by using the nifty @JsonQualifier annotation like so:

@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
internal annotation class ObjectString

class ObjectStringAdapter {
    @FromJson
    @ObjectString
    fun fromJson(id: String): Object {
        return Object(id)
    }

    @ToJson
    fun toJson(@ObjectString obj: Object): String {
        return obj.toString()
    }
}

class ObjectAdapter {
    @FromJson
    fun fromJson(jsonReader: JsonReader, @ObjectString stringAdapter: JsonAdapter<Object>, defaultAdapter: JsonAdapter<Object>): Object {
        return when (JsonReader.Token.STRING) {
            jsonReader.peek() -> {
                stringAdapter.fromJson(jsonReader)
            }
            else -> {
                defaultAdapter.fromJson(jsonReader)
            }
        }!!
    }
}

where Object is your class.

Basically created two adapters, one for a string and one for an object, and used jsonReader.peek() to peek at the value and determine at runtime which one to use depending on the type.

For reference: Moshi LocalDateTime adapter with multiple format

Upvotes: 1

Related Questions