develop with

How to override the TypeIdResolver in Jackson

Sometimes when the field isn't present in json the type resolving fails.

When building out REST APIs in Spring and Kotlin, there comes a time when you have some objects that are part of an inheritance structure that you don’t necessarily want to expose a field that allow for serialization through the normal Jackson type field approach. In order to achieve this, you will need to override the TypeIdResolver on the parent / interface of the class that will default to the concrete class.

For example, if we have an interface called Entity and we are extending it with MyEntity then when a request comes in from a certain controller we want the concrete class of that controller request body to be used to deserialize it.

Objects

Take these class examples:

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
@JsonTypeIdResolver(EntityIdResolver::class)
inferface Entity {
  var type: String
}

And the concrete class here:

class MyEntity : Entity {
  var type = "MyEntity"
}

You’ll notice we override the TypeIdResolver in the above example with our own implementation that we will get to in a bit.

API definition

The rest controller method would look something like this:

@RestController
@RequestMapping("/api/entities/my/", produces = ["application/json"])
class MyEntityController {

    @PostMapping("/")
    fun addEntity(@Valid @RequestBody entity:MyEntity): ResponseEntity<MyEntity> {
        return ResponseEntity(entity, HttpStatus.OK)
    }
}

The requesting body will be the concrete object so the resolving of the type will work without the field value in the incoming json request. Spring will use the overridden type resolver to deserialize the object to the right object in the parameter.

TypeIdResolver implementation

To make this work, we need to use the type variable that is overridden in MyEntity and use that for the standard lookup / resolution. This value equates to the field being serialized out. The type property is used for the deserialization process since its already know at the time when the object is created.

If the type is present a case statement can be used to resolve the other implementations if needed.

class EntityIdResolver :  TypeIdResolverBase() {

    private var superType: JavaType? = null

    override fun init(baseType: JavaType?) {
        superType = baseType
    }

    override fun idFromValue(obj: Any): String? {
        return idFromValueAndType(obj, obj.javaClass)
    }


    override fun idFromValueAndType(obj: Any, subType: Class<*>): String? {
        return (obj as Entity).type
    }

    override fun typeFromId(context: DatabindContext?, id: String?): JavaType {
        var subType: Class<*>? = null
        when (id) {
            "MyEntity" -> subType = MyEntity::class.java
        }
        return context!!.constructSpecializedType(superType, subType)
    }

    override fun getMechanism(): JsonTypeInfo.Id {
        return JsonTypeInfo.Id.MINIMAL_CLASS
    }
}

Hope you find this helpful, and let me know in the comments if you have any questions.

comments powered by Disqus

Want to see a topic covered? create a suggestion

Get more developer references and books in the developwith store.