Search code examples
androidretrofit2moshi

How to deserialize raw JSON objects with Moshi/Retrofit


I have a Sticker class and its wrapper:

@JsonClass(generateAdapter = true)
class StickerDto(
        @Json (name = "totalAnimatedStickers") val total: Int,
        @Json(name = "pages") val pages: Int,
        @Json(name = "data") val stickers: List<Sticker>

)

@JsonClass(generateAdapter = true)
class Sticker(
        @Json(name = "name") val name: String,
        @Json(name = "id") val id: String,
        @Json(name = "stickerData") val stickerData: JsonObject,
        var isSelected:Boolean = false
)

The stickerData attribute comes from the api with a dynamic json object with unknown attributes

"stickerData": {}

How do I deserialize an object like that using Moshi?

My current retrofit client:

 private fun createNewFriendsClient(authRefreshClient: AuthRefreshClient,
                                       preferencesInteractor: PreferencesInteractor): FriendsApiClient {

        val logger = run {
            val httpLoggingInterceptor = HttpLoggingInterceptor()
            httpLoggingInterceptor.apply {
                httpLoggingInterceptor.level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE
            }
        }

        val okHttp = OkHttpClient.Builder().addInterceptor(logger).authenticator(RefreshUserAuthenticator(authRefreshClient, preferencesInteractor,
                UnauthorizedNavigator(SDKInternal.appContext, Interactors.preferences))).build()


        return Retrofit.Builder()
                .client(okHttp)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(MoshiConverterFactory.create())
                .baseUrl(Interactors.apiEndpoint)
                .build()
                .create(FriendsApiClient::class.java)


    }

Gives me an

"Unable to create converter for class StickerDto"
Caused by NoJsonAdapter for java.util.Comparator<? super java.lang.String>

error. What converter do I need to use if not that Moshi one? Trying to pull it down as a string also gives an error as it is expecting and object. I just need that string.

Edit, the Json string is very long but it begins like this:

{"tileId":"1264373a-24d8-4c10-ae90-d6e8f671410c","friendId":"2c50f187-039a-4f85-b12b-0c802396a611","name":"David Carey","message":"Joined WeAre8","animatedSticker":{"v":"5.5.7","fr":24,"ip":0,"op":48,"w":1024,"h":1024,"nm":"party_popper","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":3,"nm":"C | Position","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":45,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[176,892,0],"to":[-6.667,6.667,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":7,"s":[136,932,0],"to":[0,0,0],"ti":[-6.667,6.667,0]},{"t":11,"s":[176,892,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":7,"s":[115,75,100]},{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":11,"s":[95,105,100]},{"t":20,"s":[100,100,100]}],"ix":6}},"ao":0,"ef":[{"ty":5,"nm":"Controller","np":13,"mn":"Pseudo/DUIK controller","ix":1,"en":1,"ef":[{"ty":6,"nm":"Icon","mn":"Pseudo/DUIK controller-0001","ix":1,"v":0},{"ty":2,"nm":"Color","mn":"Pseudo/DUIK controller-0002","ix":2,"v":{"a":0,"k":[0.92549020052,0.0941176489,0.0941176489,1],"ix":2}},{"ty":3,"nm":"Position","mn":"Pseudo/DUIK controller-0003","ix":3,"v":{"a":0,"k":[0,0],"ix":3}},{"ty":0,"nm":"Size","mn":"Pseudo/DUIK controller-0004","ix":4,"v":{"a":0,"k":100,"ix":4}},{"ty":0,"nm":"Orientation","mn":"Pseudo/DUIK controller-0005

Solution

  • Note that JsonObject is a class from the gson package, so if you want to use Moshi you will need to switch to JSONObject which is the default class supported by Android.

    To do this you will need to write your own JSONObject adapter.

    First, write your adapter class:

    import com.squareup.moshi.FromJson
    import com.squareup.moshi.JsonReader
    import com.squareup.moshi.JsonWriter
    import com.squareup.moshi.ToJson
    import okio.Buffer
    import org.json.JSONException
    import org.json.JSONObject
    
    class JSONObjectAdapter {
        
        @FromJson
        fun fromJson(reader: JsonReader): JSONObject? {
            // Here we're expecting the JSON object, it is processed as Map<String, Any> by Moshi
            return (reader.readJsonValue() as? Map<String, Any>)?.let { data ->
                try {
                    JSONObject(data)
                } catch (e: JSONException) {
                    // Handle exception
                    return null
                }
            }
        }
        
        @ToJson
        fun toJson(writer: JsonWriter, value: JSONObject?) {
            if (value != null) {
                writer.value(Buffer().writeUtf8(value.toString()))
            } else {
                writer.value(null as String?)
            }             
        }
       
    }
    

    Adjust your retrofit build to provide custom Moshi object when creating the MoshiConverterFactory:

        .addConverterFactory(MoshiConverterFactory.create(Moshi.Builder().add(JSONObjectAdapter()).build()))
    

    and then you are good to go and use JSONObject

    @Json(name = "stickerData") val stickerData: JSONObject
    

    Good luck and I hope this helps!