androidkotlinmoshi

Moshi adapter to ignore bad elements from a polymorphic list


I have a polymorphic list of contents sent by the backend. I use PolymorphicJsonAdapterFactory to parse it to Kotlin objects. It all works fine. When the backend sends an element with an unknown type there is no problem I apply a default value.

The issue comes when the backend sends a wrong element (required attribute missing) with a recognized type. In this case, moshi stops and throws a JsonDataException. I would like to ignore these elements.

I saw this thread but it concerns a list of just one type.

The only solution I could find so far is making EVERY field on the Kotlin data classes optional and filter out later. But it's not ideal.

Example

Let's assume I have this :

enum class Type {
   FISH,
   BIRD
}

sealed class Animal(val type: Type) {
   data class Fish(
     val requiredField : String,
   ): Animal(Type.FISH)
   data class Bird(
     val requiredField : String,
   ): Animal(Type.BIRD)
}

And I receive a list of Animal.

So I'll have :

PolymorphicJsonAdapterFactory.of(Animal::class.java, "type")
            .withSubtype(Fish::class.java, Type.FISH)
            .withSubtype(Bird::class.java, Type.BIRD)
            .withSubtype(Input::class.java, ActionType.input.name))
            .withDefaultValue(UnknownEntity())

This will handle this json perfectly :

{
  "animals": [
    {
      "type": "FISH",
      "requiredField": "blablabla",
    },
    {
      "type": "ANIMAL",
      "requiredField": "blablabla"
    },
    {
       "type": "Nothing",
    }
  ]
}

But not this one because fish required field is missing are missing :

{
  "animals": [
    {
      "type": "FISH",
    },
    {
      "type": "ANIMAL",
      "requiredField": "blablabla"
    },
  ]
}

Do you have a solution for this ? Seems like a pretty normal usecase


Solution

  • I used my SkipBadElementsListAdapter from your linked thread, and it seems to do what you want. Correct me if I misunderstood (preferably with a demonstrating test case).

    fun main() {
      val animalJsonAdapterFactory = PolymorphicJsonAdapterFactory.of(Animal::class.java, "type")
        .withSubtype(Animal.Fish::class.java, Type.FISH.name)
        .withSubtype(Animal.Bird::class.java, Type.BIRD.name)
      val moshi = Moshi.Builder()
        .add(SkipBadElementsListAdapter.Factory)
        .add(animalJsonAdapterFactory)
        .build()
      val animalsJsonAdapter =
        moshi.adapter<List<Animal>>(Types.newParameterizedType(List::class.java, Animal::class.java))
      println(animalsJsonAdapter.fromJson(encoded))
    }
    
    enum class Type {
      FISH,
      BIRD
    }
    
    sealed class Animal(val type: Type) {
      @JsonClass(generateAdapter = true)
      data class Fish(
        val requiredField: String,
      ) : Animal(Type.FISH)
    
      @JsonClass(generateAdapter = true)
      data class Bird(
        val requiredField: String,
      ) : Animal(Type.BIRD)
    }
    
    val encoded = """
      [
        {
          "type": "FISH",
          "requiredField": "blablabla"
        },
        {
          "type": "BIRD",
          "requiredField": "blablabla"
        },
        {
          "type": "BIRD"
        }
      ]
    """.trimIndent()
    

    This prints [Fish(requiredField=blablabla), Bird(requiredField=blablabla)]. It ignores the bad element, as it seems you want it to do.