andrewedgar
andrewedgar

Reputation: 877

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

Upvotes: 5

Views: 4448

Answers (3)

Viktor Petrovski
Viktor Petrovski

Reputation: 796

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!

Upvotes: 8

tynn
tynn

Reputation: 39853

Built-in Type Adapters for Moshi include Arrays, Collections, Lists, Sets, and Maps. A JsonObject type is not provided with Moshi itself, but it would be an enhanced Map<String, Any> anyhow, so just use the Map instead of an object.

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

The values are automatically converted as well. Thus you'll find strings, lists or numbers there.

Upvotes: 2

andrewedgar
andrewedgar

Reputation: 877

I made stickerData into a Map and used a GsonConverterFactory instead of Moshi.

Upvotes: -2

Related Questions