0

I'm trying to write a custom JsonSerializer for a sealed class but the serialize method is not being called. For simplicity, I created a sample sealed class

sealed class Parent {
  data class Child1(val name: String): Parent()
  data class Child2(val name: String): Parent()
}

and then created a JsonSerializer for this like this:

class ParentSerializer : JsonSerializer<Parent>, JsonDeserializer<Parent> {
  companion object {
    const val CLASSNAME = "CLASSNAME"
    const val DATA  = "DATA"
  }

  override fun serialize(src: Parent, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
    val jsonObject = JsonObject()
    jsonObject.addProperty(CLASSNAME, src.javaClass.name)
    jsonObject.add(DATA, context.serialize(src))
    return jsonObject
  }

  override fun deserialize(jsonElement: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Parent {
    val jsonObject = jsonElement.asJsonObject
    val className = jsonObject.get(CLASSNAME) as JsonPrimitive
    val objectClass = Class.forName(className.asString)
    return context.deserialize(jsonObject.get(DATA), objectClass)
  }
}

and I tried serializing a list using this, but the serialize method is not called, but interestingly enough deserialize method is getting called(though it's currently erroring out that it's not able to find CLASSNAME, since serialize wasn't called first).

I created a test for this

@Test
  fun check() {
    val list = listOf(Parent.Child1("first"), Parent.Child2("second"))
    
    val gson = GsonBuilder()
        .registerTypeAdapter(Parent::class.java, ParentSerializer())
        .create()

    val jsonString = gson.toJson(list)
    println(jsonString) // here serialize isn't called

    val type = object : TypeToken<List<Parent>>() {}.type
    val deserializedList = gson.fromJson<List<Parent>>(jsonString, type) // here deserialize is called correctly
    assertThat(deserializedList).isEqualTo(list) 
  }
Shivam Pokhriyal
  • 1,044
  • 11
  • 26

1 Answers1

1

There are two things which coincide here:

  • The Gson.toJson method without explicit type argument uses the runtime type of the argument, so List<?> here, and therefore performs serialization for Child1 respectively Child2 and not for Parent
  • GsonBuilder.registerTypeAdapter registers an adapter for that specific class, but not subclasses

You could probably either add an additional type argument for List<Parent> when calling toJson (e.g. toJson(list, type)), or use GsonBuilder.registerTypeHierarchyAdapter instead of registerTypeAdapter. However, using registerTypeHierarchyAdapter won't directly work for your code because it calls context.serialize(src) which leads to infinite recursion, something the method JsonSerializer.serialize warns against. You would have to use a TypeAdapterFactory instead of JsonSerializer to solve this.

However, there is a major security issue with your code: By using Class.forName you essentially let the user who provides the JSON data load an arbitrary class from your classpath and then through Gson call its constructor and set arbitrary field values for it. In the worst case this can lead to a remote code execution vulnerability.
Since you know that the class you want to load is a subclass of Parent, your code should explicitly enforce that when using Class.forName, for example like this:

val objectClass = Class.forName(className.asString, false, this.javaClass.classLoader)
    .asSubclass(Parent::class.java)

This does not directly initialize the requested class (false argument) and with asSubclass makes sure the class is actually Parent or a subclass of it before doing anything else with it.

However, it appears the general problem you are trying to solve is polymorphic deserialization. There are already multiple answers about this (such as this one) where you might find ideas for other solutions.

As side note: Since Gson 2.10 there are Gson.fromJson overloads with TypeToken parameter, for example fromJson(String, TypeToken). You should prefer those over the fromJson(..., Type) overload you used because they provide type safety.

Marcono1234
  • 5,856
  • 1
  • 25
  • 43