Great Blog Post. I also have been dealing with something similar but a bit more you could say weirder :D. My case still revolves around old classes instead of records since I want to have some fields that are shared and controlled separately. I'll appreciate to have your comments about it :DD....
The Scenario
My scenario revolves around a system in which I'm trying to create an entity of type "Item". Items in my system have different types which for each type we have a separate table which results in a separate entity. As an example, let's say we have an Item of type Book and Disk.
Both of these classes/entities will extend a base class called BaseItem in which it contains shared fields for both of them and also all other future Item types. Other then that I also have an enum in which we record all item types called ItemType which right now it will look like this:
public enum ItemType {
BOOK,
DISK
}
Till this part everything is okay but stuff get a bit complex when we dive into the specific policies related to the request for creating a new Item.
The API Call
The JSON for this API call goes as follows:
{
"type": <string of which it should indicate value for the enum>
"detail": <object in which it should contain feilds related to matched class that was specified by the value of "type" property>
}
As I explained above, both Book and Disk have some base fields that are shared in both of them which they take those fields by extending the BaseClass, but each will have their own set of fields that is not shared. To get those fields we have a property called "detail" in which we accept an object that will later map this object to the correct class by using the value of type field as a helper. Till this section everything is explained but the implementation becomes a bit more complex on a specific rule.
The Challenge
Let's on deserialization of the type field using the annotations we want to use the enum itself directly as the type (Basically going by the @RequestBody logic in Spring) so on wrong values given for the "type" property key we raise an error for the user giving them hints on what the allowed values are. At the same time we also want to make life easier for the called (and harder for the developer at the same time :D) so we also want to allow them to present the values in a case insensitive manner, so that both "type": "book" and "type":"BOOK" are resolved to ItemType.BOOK. At first glance this might be simple (since you can configure your mapper to accept case insensitive values for enum in jackson) but it becomes complex when we remember that we also want to use @JsonSubTypes so we map the value given in the "type" property to be used to help us determine and find the correct class for the detail field. To show an overview of the code on why this will create an issue which led me to work on custom type id resolvers:
public record CreateItem {
ItemType type,
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "type"
)
@JsonSubTypes(
@JsonSubTypes.Type(value = Book.class, name = "BOOK"),
@JsonSubTypes.Type(value = Disk.class, name = "DISK")
)
ItemDetail detail // Interface in which both the Book and Disk class implement
}
As you can see if the value given in the type field even bypasses the case insensitivity we still have a problem as the @JsonSubTypes annotation requires it to have all capital letters so we won't actually have case insensivity.
Final Note
First of all thanks for reading my comment(both the authors and anyone else :)). I'm still in the middle of trying to find an elegant way in which I can solve this issue cleanly without having to write heavy amounts of boilerplate code but still I'm not such an expert on Spring and Jackson so if you see any inconsistencies or wrong explanation I really really appreciate it that you inform me so I can learn more.
Great Blog Post. I also have been dealing with something similar but a bit more you could say weirder :D. My case still revolves around old classes instead of records since I want to have some fields that are shared and controlled separately. I'll appreciate to have your comments about it :DD....
The Scenario
My scenario revolves around a system in which I'm trying to create an entity of type "Item". Items in my system have different types which for each type we have a separate table which results in a separate entity. As an example, let's say we have an Item of type
BookandDisk. Both of these classes/entities will extend a base class calledBaseItemin which it contains shared fields for both of them and also all other future Item types. Other then that I also have an enum in which we record all item types calledItemTypewhich right now it will look like this:Till this part everything is okay but stuff get a bit complex when we dive into the specific policies related to the request for creating a new Item.
The API Call
The JSON for this API call goes as follows:
As I explained above, both
BookandDiskhave some base fields that are shared in both of them which they take those fields by extending theBaseClass, but each will have their own set of fields that is not shared. To get those fields we have a property called "detail" in which we accept an object that will later map this object to the correct class by using the value of type field as a helper. Till this section everything is explained but the implementation becomes a bit more complex on a specific rule.The Challenge
Let's on deserialization of the type field using the annotations we want to use the enum itself directly as the type (Basically going by the @RequestBody logic in Spring) so on wrong values given for the "type" property key we raise an error for the user giving them hints on what the allowed values are. At the same time we also want to make life easier for the called (and harder for the developer at the same time :D) so we also want to allow them to present the values in a case insensitive manner, so that both
"type": "book"and"type":"BOOK"are resolved toItemType.BOOK. At first glance this might be simple (since you can configure your mapper to accept case insensitive values for enum in jackson) but it becomes complex when we remember that we also want to use @JsonSubTypes so we map the value given in the "type" property to be used to help us determine and find the correct class for thedetailfield. To show an overview of the code on why this will create an issue which led me to work on custom type id resolvers:As you can see if the value given in the
typefield even bypasses the case insensitivity we still have a problem as the @JsonSubTypes annotation requires it to have all capital letters so we won't actually have case insensivity.Final Note
First of all thanks for reading my comment(both the authors and anyone else :)). I'm still in the middle of trying to find an elegant way in which I can solve this issue cleanly without having to write heavy amounts of boilerplate code but still I'm not such an expert on Spring and Jackson so if you see any inconsistencies or wrong explanation I really really appreciate it that you inform me so I can learn more.