Monday, 25 April 2022

Moshi: How to provide adapter for PolymorphicJsonAdapterFactory.withFallbackJsonAdapter while configuring Moshi.Builder

I'm trying to use PolymorphicJsonAdapterFactory for JSON which provides different structure depending on type field, like below.

{
  "notifications": [
    {
      "type": "achievement",
      "title": "New Achievement!",
      "content": "You got new achievement Level 100! (100pt)"
      "metadata": {
        "category": "level",
        "id": "level100",
        "points": 100,
      }
    },
    {
      "type": "message",
      "name": "message",
      "content": "Cloud: hello, ~"
      "metadata": {
        "from": "Cloud",
        "content": "hello, ~ "
      }
    },
    {
      "type": "new", <--- e.g. this is unknown type for app
      "name": "something new",
      "content": "You are informed something about. Please check app://~",
      "deeplink": "app://~"
    }
  ]
}

Suppose our app already prepared for some known type, achievement and message. But there's a chance to add new type, in this case new. At this time, we want app to work with minimum properties title and content. In short, I want map that new type to Other fallback class.

data class Notifications(
    val notifications: List<Notification>
)

sealed class Notification {
    abstract val type: String
    abstract val title: String
    abstract val content: String
}

data class Achievement(
    override val type: String,
    override val title: String,
    override val content: String,
    val metadata: Metadata,
): Notification() {
    data class Metadata(
        val category: String,
        val id: String,
        val points: Int,
    )
}

data class Message(
    override val type: String,
    override val title: String,
    override val content: String,
    val metadata: Metadata,
): Notification() {
    data class Metadata(
        val from: String,
        val content: String,
    )
}

// Any unknown `type` goes here.
data class Other(
    override val type: String,
    override val title: String,
    override val content: String,
): Notification()

val moshi = Moshi.Builder()
    .add(
        PolymorphicJsonAdapterFactory.of(Notification::class.java, "type")
            .withSubtype(Achievement::class.java, "achievement")
            .withSubtype(Message::class.java, "message")
            .withFallbackJsonAdapter(...)
    )
    .addLast(KotlinJsonAdapterFactory())
    .build()

If moshi configuration and Notification is so simple like this, it is able to write .withFallbackJsonAdapter(object: Adapter()...).

But if we have some customization in Moshi.Builder, we may want to use adapter from configured moshi val adapter = moshi.adapter() so we can't write adapter inline. Then, I thought creating two moshi like this.

val moshiWithoutPoly = Moshi.Builder()
    // some other base configuration going here
    .build()

val moshi = moshiWithoutPoly.newBuilder()
    .add(
        PolymorphicJsonAdapterFactory.of(Notification::class.java, "type")
            .withSubtype(Achievement::class.java, "achievement")
            .withSubtype(Message::class.java, "message")
            .withFallbackJsonAdapter(moshiWithoutPoly.adapter(Other::class.java) as JsonAdapter<Any>)
    )
    .build()

Is it fine or is there more proper solution for this?
(Or I'm wondering if it is eligible for proposing new API like .withFallbackSubtype(Other::class.java) or .withFallbackAdapterFactory(factory) for such situation.)



from Moshi: How to provide adapter for PolymorphicJsonAdapterFactory.withFallbackJsonAdapter while configuring Moshi.Builder

No comments:

Post a Comment