Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to parse polymorphic objects in Kotlin using Moshi

Currently having an issue with PolymorphicJsonAdapterFactory and Kotlins sealed class. I have an API that that returns polymorphic home components and I am trying to parse and create polymorphic object using Moshi in Kotlin but I getting following error:

  Caused by: java.lang.IllegalArgumentException: Cannot serialize abstract class home.HomeComponent
for class home.HomeComponent
for java.util.List<home.HomeComponent> components
for class home.HomeContent

Code:

sealed class HomeComponent(@Json(name = "_type") val type: HomeContentType) {

data class BannerComponent(@field:Json(name = "_id")
                               val id: String,
                               val image: String) : HomeComponent(HomeContentType.banner)

data class SpecialProductsComponent(@field:Json(name = "_id")
                                        val id: String,
                                        val style: List<Product>) : HomeComponent(HomeContentType.specialProducts)

data class CarouselBannerComponent(@field:Json(name = "_id")
                                       val id: String,
                                       val style: String,
                                       val images: List<String>) : HomeComponent(HomeContentType.carousel)
}

enum class HomeContentType {
    @Json(name = "banner")banner,
    @Json(name = "products")Products,
    @Json(name = "carousel")carousel
}

ApiFetcher Class:


class HomeApiFetcher(private val backend: HomeContentBackend) : HomeFetcher {

    companion object {
        fun from(retrofit: Retrofit,
                 moshi: Moshi): HomeApiFetcher {

            val moshi = moshi.newBuilder()
                    .add(
                            PolymorphicJsonAdapterFactory.of(HomeComponent::class.java, "_type")
                                    .withSubtype(HomeComponent.BannerComponent::class.java, HomeContentType.banner.name)
                                    .withSubtype(HomeComponent.SpecialProductsComponent::class.java, HomeContentType.specialProducts.name)
                                    .withSubtype(HomeComponent.CarouselBannerComponent::class.java, HomeContentType.carousel.name))
                    .add(KotlinJsonAdapterFactory())
                    .build()

            val homeBackend = retrofit
                    .newBuilder()
                    .addConverterFactory(MoshiConverterFactory.create(moshi))
                    .build()
                    .create(HomeContentBackend::class.java)
            return HomeApiFetcher(homeBackend)
        }
    }

    override fun getHomeContent(): Single<HomeContent> {
        return backend.load()
    }
}

JSON from API:

{
    "common": {
        "background": "",
        "backgroundType": "NONE",
        "floatingImgs": {
            "left": "",
            "right": ""
        },
        "actualDate": "2019-09-23T15:03:20.8626882Z"
    },
    "components": [{
            "image": "http://image1.url",
            "_id": "carousel1",
            "style": "SLIDE",
            "_type": "banner"
        },
        {
            "products": [],
            "_id": "id1",
            "style": "CARD",
            "_type": "products"
        },
        {
            "images": ["http://image1.url",
                "http://image1.url",
                "http://image1.url"
            ],
            "_id": "carousel1",
            "style": "SLIDE",
            "_type": "carousel"
        }
    ]
}

I am not sure what's wrong in my code. I am getting Cannot serialize abstract class HomeComponent error

like image 412
user1288005 Avatar asked Jan 26 '23 19:01

user1288005


1 Answers

I've changed @field:Json to Json, Products to products and SpecialProductsComponent fields. So finally I got:

sealed class HomeComponent(@Json(name = "_type") val type: HomeContentType) {

    data class BannerComponent(
        @Json(name = "_id")
        val id: String,
        val image: String
    ) : HomeComponent(HomeContentType.banner)

    data class SpecialProductsComponent(
        @Json(name = "_id")
        val id: String,
        val style: String,
        val products: List<Product>
    ) : HomeComponent(HomeContentType.products)

    data class CarouselBannerComponent(
        @Json(name = "_id")
        val id: String,
        val style: String,
        val images: List<String>
    ) : HomeComponent(HomeContentType.carousel)
}

enum class HomeContentType {
    @Json(name = "banner")
    banner,
    @Json(name = "products")
    products,
    @Json(name = "carousel")
    carousel
}

and now it seems that all works fine. I checked it with following code:

fun main() {
    val moshi = Moshi.Builder()
        .add(
            PolymorphicJsonAdapterFactory.of(HomeComponent::class.java, "_type")
                .withSubtype(HomeComponent.BannerComponent::class.java, HomeContentType.banner.name)
                .withSubtype(HomeComponent.SpecialProductsComponent::class.java, HomeContentType.products.name)
                .withSubtype(HomeComponent.CarouselBannerComponent::class.java, HomeContentType.carousel.name)
        )
        .add(KotlinJsonAdapterFactory())
        .build()

    val json = """
[
  {
    "image": "http://image1.url",
    "_id": "carousel1",
    "style": "SLIDE",
    "_type": "banner"
  },
  {
    "products": [],
    "_id": "id1",
    "style": "CARD",
    "_type": "products"
  },
  {
    "images": [
      "http://image1.url",
      "http://image1.url",
      "http://image1.url"
    ],
    "_id": "carousel1",
    "style": "SLIDE",
    "_type": "carousel"
  }
]
    """.trimIndent()

    val adapter =
        moshi.adapter<List<HomeComponent>>(Types.newParameterizedType(List::class.java, HomeComponent::class.java))
    val result = adapter.fromJson(json)
    println(result)
}
like image 178
Andrei Tanana Avatar answered Feb 07 '23 17:02

Andrei Tanana