How to learn Jackson to cast inheritors of abstract class? - java

I have a class:
#EqualsAndHashCode(callSuper = true)
#Data
public class AppealTemplateDto extends AbstractDto {
private List<AbstractFieldDto> fields;
}
This class contains list of AbstractFieldDto inheritors, e.g.:
#EqualsAndHashCode(callSuper = true)
#Data
#NoArgsConstructor
public class InputFieldDto extends AbstractFieldDto {
private String fieldType = FieldType.INPUT.name();
private String text;
}
Totally, there are near 6-7 inheritors, & AbstractTemplateDto may contain any set of them.
Controller:
#PostMapping
public ResponseEntity<AppealTemplateDto> create(#RequestBody AppealTemplateDto dto) {
return ResponseEntity.ok(service.save(dto));
}
When Jackson trying to parse AppealTemplateDto, it crashes with exception:
Caused by:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot
construct instance of
ru.appeal.template.dto.field.AbstractFieldDto
(no Creators, like default construct, exist): abstract types either
need to be mapped to concrete types, have custom deserializer, or
contain additional type information
As I understand, Jackson can't define, how to cast incoming AbstractFieldDto. Please, advice me, what to do?

The Annotation your are needing are:
#JsonTypeInfo
#JsonSubType
#JsonTypeName
Some explanation: if you have many implementation of your abstract type, Jackson can't guess which type is your json, you need to add a type name in json, for example as a new property (this is one of the strategies):
//tell to jackson where to find the type name
#JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = As.PROPERTY, property = "type")
// tell to jackson the implementations to scan
#JsonSubTypes({
#JsonSubTypes.Type(value = InputFieldDto.class, name = "input")
//, ...
})
public class AbstractFieldDto {
}
//tell to jackson what is the type name in json
#JsonTypeName("input")
public class InputFieldDto extends AbstractFieldDto {
private String fieldType = FieldType.INPUT.name();
private String text;
}

Related

jackson deserialize json to object without annotation

I have old Pojos inside jar. Example;
public class human implements Serializable {
}
public class Man extend human {
}
public class Woman extend human {
}
I have json like {"type":"man",...}
I want to deserialize proper class using type But I cannot use these annotations because Base class generated automatically from xsd.
#JsonTypeInfo(// use = JsonTypeInfo.Id.NAME, // include =
JsonTypeInfo.As.EXISTING_PROPERTY, // property = "queryType", //
visible = true) #JsonSubTypes({ // #Type(value =
ScenarioByCountryQuery.class, name = "scenarioByCountry"), //
#Type(value = ScenarioByMeasureQuery.class, name =
"scenarioByMeasure") })
I don want to desearlize like if type = '' etc.
I want to deserialize dynamically using this type value.

Java Jackson deserialize interfacing enums

I have the given situtation:
This is the interface I am implementing:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = MasterDevice.class, name = "COMPUTER"),
#JsonSubTypes.Type(value = SlaveDevice.class, name = "FLASH_DRIVE"),
})
interface DeviceType{
String getName();
}
The interface is used by two enums:
public enum MasterDevice implements DeviceType{
COMPUTER("Computer");
private String name;
public MasterDevice(String name){
this.name=name;
}
#Override public String getName(){return this.name;}
}
The second one is for devices you can attach to the MasterDevice.
public enum SlaveDevice implements DeviceType{
FLASH_DRIVE("USB Drive");
private String name;
public SlaveDevice(String name){
this.name=name;
}
#Override public String getName(){return this.name;}
}
The POJO that I want to deserialize is:
public class DeviceInformation{
private DeviceType type;
}
And the json String I want to deserialize look like this:
String info = "{\"type\":\"COMPUTER\"}";
ObjectMapper mapper = new ObjectMapper();
DeviceInformation deviceInfo = mapper.readValue(info, DeviceInformation.class);
All research was proposing implementing a custom deserializer for the DeviceType which I am not keen to do since it seems so bad to maintain.
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class DeviceType]: missing type id property '#type' (for POJO property 'type')`
It seems like Jackson searches for an type property on the DeviceType which of course it does not have. How do I tell Jackson that the Enum selection is based on the enum value (COMPUTER, FLASH_DRIVE)?
I think you're expecting too many levels to be collapsed for you simply by giving a bunch of things the same field and property names.
The JSON required for your current setup would be:
String info = "{\"type\": {\"type\": \"COMPUTER\", \"COMPUTER\": null}}";
Here, the outer "type" is for DeviceInformation, the inner "type:COMPUTER" pair are for DeviceType polymorphism of MasterDevice. And the final "COMPUTER" is to instantiate MasterDevice.COMPUTER (this last bit of weirdness feels like a bug with the Jackson implementation).
To make it more obvious what's going on, here's a simplified version with some renaming:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type"
)
#JsonSubTypes({
#JsonSubTypes.Type(value = MasterDevice.class, name = "MASTER"),
#JsonSubTypes.Type(value = SlaveDevice.class, name = "SLAVE"),
})
interface DeviceType {
}
public enum MasterDevice implements DeviceType {
LAPTOP, SERVER;
}
public enum SlaveDevice implements DeviceType {
FLASH_DRIVE, WEBCAM;
}
public class DeviceInformation {
public DeviceType deviceType;
}
Then:
String info = "{\"deviceType\": {\"type\": \"MASTER\", \"SERVER\": null}}";
ObjectMapper mapper = new ObjectMapper();
DeviceInformation deviceInfo = mapper.readValue(info, DeviceInformation.class));
If you want something more elegant, then you'll likely need a custom serializer.

Nesting multiple levels of Jackson WRAPPER_OBJECTs

By no means am I a Jackon/JSON wizard, which is probably evident from the following issue I'm running into:
I have 2 possible data structures I'm receiving.
The first one is called amountTransaction:
{
"amountTransaction": {
"clientCorrelator":"54321",
"endUserId":"tel:+16309700001"
}
}
Which is represented by the following Java object:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonTypeName(value = "amountTransaction")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class AmountTransaction {
private String clientCorrelator;
private String endUserId;
...
}
However the amountTransaction object also appears as child element of the paymentTransactionNotification object:
{
"paymentTransactionNotification": {
"amountTransaction": {
"clientCorrelator": "54321",
"endUserId": "tel:+16309700001"
}
}
}
..which I thought would be represented by:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonTypeName(value = "paymentTransactionNotification")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class PaymentTransactionNotification {
private AmountTransaction amountTransaction;
...
}
Parsing the JSON with the amountTransaction object alone works fine. It's a pretty straightforward example of a WRAPPER_OBJECT.
However when trying to parse the JSON for the paymentTransactionNotification, I'm getting an exception indicating that it can't properly deal with the amountTransaction as element of the paymentTransactionNotification:
com.fasterxml.jackson.databind.JsonMappingException: Could not resolve type id 'clientCorrelator' into a subtype of [simple type, class com.sf.oneapi.pojos.AmountTransaction]
Any thoughts on how I can properly annotate this so my code can properly deal with both stand alone, as well as encapsulated amountTransaction objects?
By default wrapping root node in Jackson is disabled. You can wrap inner objects but if you want to wrap root node you need to enable jackson feature for it (https://jira.codehaus.org/browse/JACKSON-747):
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationConfig.Feature.WRAP_ROOT_VALUE);
objectMapper.enable(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE);
When you enabled these features you already said Jackson to wrap the root element and you don't need #JsonTypeInfo and #JsonTypeName anymore. You can simple delete them. But now you need to customize the root node name and you can use #JsonRootName for it. Your classes should look like this:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonRootName("amountTransaction")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class AmountTransaction {
private String clientCorrelator;
private String endUserId;
...............
}
And
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonRootName("paymentTransactionNotification")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class PaymentTransactionNotification {
private AmountTransaction amountTransaction;
.............
}
I've tried and Jackson converted both JSON requests as expected.

How to add custom deserializer to interface using jackson

Saying I have an interface A, I want to use custom deserializer for all classes implement interface A, So I use code below but it doesn't work, While CustomAserializer works.
So what should I do to deserialize all classes implement A using my custom deserializer.
Thanks.
module.addDeserializer(A.class, new CustomADeserializer());
module.addSerializer(A.class, new CustomASerializer())
It seems you forgot to annotate your implementation classes with #JsonDeserialize(using = ImplementationClazz.class) to indicate that the class should be used to deserialize the abstract class or interface.
The following is a simple example to deserialize an interface having multiple implementations using Jackson.
Here is my interface:
#JsonDeserialize(using = UserDeserializer.class)
public interface User {
}
One implementation of the interface:
#JsonDeserialize(as = ServiceUser.class)
public class ServiceUser implements User{
private String name;
private String role;
//constructor, setters & getters
Second implementation:
#JsonDeserialize(as = AdminUser.class)
public class AdminUser implements User {
private String role;
private String designation;
//constructor, setters & getters
And here is the deserializer:
public class UserDeserializer extends JsonDeserializer<User> {
#Override
public User deserialize(JsonParser jp, DeserializationContext context) throws IOException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
ObjectNode root = mapper.readTree(jp);
/*write your own condition*/
if (root.has("name") && root.get("name").asText().equals("XYZ")) {
return mapper.readValue(root.toString(), ServiceUser.class);
}
return mapper.readValue(root.toString(), AdminUser.class);
}
}
You may get a StackOverflowError if you don't annotate the implementation classes. All implementation classes should deserialize themselves, otherwise it will use the deserializer from the parent class which leads to a StackOverflowError.
Just in case someone need a solution to serialize and desiralize inheritance hierarchy
you can use jackson annotation in more elegant way : JsonTypeInfo and JsonSubTypes
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = ServiceUser.class, name = "service"),
#Type(value = AdminUser.class, name = "admin")
})
public interface User{
// ...
}

Jackson : Losing type information

I am trying to serialize/deserialize the following
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
#JsonSubTypes({
#JsonSubTypes.Type(value = IdBundleCombine.class),
#JsonSubTypes.Type(value = IdBundleDistinct.class) })
public abstract class IdBundle
{
String sharedId;
Long internalId;
//getters
}
public class IdBundleCombine extends IdBundle
{
//setters
}
public class IdBundleDistinct extends IdBundle
{
//setters
}
with the following code
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(new File("foo.json"), someInstanceOfIdBundle);
Which produce the following (without type information as you can see):
{"sharedId":"foobar","internalId":1234}
So i get an error missing property '#type' that is to contain type id when I try to deserialize it.
I tried every combination of parameters for #JsonTypeInfo and #JsonSubTypes I could find without ever succeeding in getting the type information to show in my file. I also tried to play with the #JsonTypeName on the subType without results.
My only guess is that I am doing something wrong with the mapper, but I can't find anything on the subject since most of the people seem to either don't want the type information to show up in the json string, or to have problems with the deserialization process.
I did try using the following annotation and it worked, even with the property tag.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.PROPERTY, property = "#type")
Add 'name' attribute to #Type for sub types and give 'property' attribute of #JsonTypeInfo any value of your choice. Class below
<!-- language: java -->
#JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "meta-type")
#JsonSubTypes({#Type(value = IdBundleCombine.class, name = "bundle-combine"),
#Type(value = IdBundleDistinct.class, name = "bundle-distinct")})
public abstract class IdBundle{
}
will produce following json in case it's IdBundleCombine
{"meta-type": "bundle-combine", "sharedId":"foobar","internalId":1234}

Categories

Resources