How to add custom deserializer to interface using jackson - java

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{
// ...
}

Related

How to declare a variable or Object of any class type in Java

I am quite new to Java and I am trying to deserialize the JSON using Jackson and I facing some minor issue with regards to declaring the Object/Variable type. I will provide all the codes then explain the issue for easy understanding.
I have an enum that will have the required type values:
public enum IdentifierTypeValues {
Type1,
Type2,
Type3,
//Constructor and Getter of enum values
}
Then for each of these type, I have different classes which will have different input and do a completely different type of process:
public class GenerateType1 {
private String name;
private String age;
//Getter and Setter
//Some required process based on these values
}
public class GenerateType2 {
private String address;
private String city;
private String country;
//Getter and Setter
//Some required process based on these values
}
public class GenerateType3 {
private String firstName;
private String lastName;
private String fullName;
//Getter and Setter
//Some required process based on these values
}
Now I have a wrapper class for these type of classes which will take the type based on enum and typeInfo values. I want the typeInfo values to be any of the class based type something like this:
public class TypeSyntax {
private IdentifierTypeValues indeitiferType;
private GenerateType1 / GenerateType2 / GenerateType3 identifierTypeValues;
//Here the identifierTypeValues can have the values for anytype
//How to declare a variable of any of these class type?
}
This is the class that will be used by my JSON for deserializing. I know I can add a wrapper class of those 3 types and provide that wrapper class as a type class for this. Something like this:
public class WrapperClass{
private GenerateType1 type1;
private GenerateType2 type2;
private GenerateType3 type3;
}
public class TypeSyntax{
private IdentifierTypeValues indeitiferType;
private WrapperClass identifierTypeValues;
//But using this approach will change my JSON structure which I do not want to do.
}
My JSON structure is something like this and I would like to keep it in the same way.
{
"indeitiferType":"Type1",
"identifierTypeValues":{
"name":"Batman",
"age":"2008"
}
}
Is there a way I can declare the variable of multiple type class? or any better approach to handle this by keeping the json format same? I tried searching but I am unable to search what exactly so any help would be really appriciated.
Because the type identifier exists on a different level than the other properties a wrapper class TypeSyntax needed. There are several open feature requests to add wrapping functionality to Jackson e.g. https://github.com/FasterXML/jackson-databind/issues/512
Fortunately polymorphism is supported in Jackson with #JsonTypeInfo and #JsonSubTypes annotations.
Wrapper class should look like:
public class TypeSyntax {
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "identifierType")
private GenerateTypeBase identifierTypeValues;
// getters and setters (omitted for brevity)
}
GenerateTypeBase is the common parent class
#JsonSubTypes({
#JsonSubTypes.Type(value = GenerateType1.class, name = "Type1"),
#JsonSubTypes.Type(value = GenerateType2.class, name = "Type2"),
})
public abstract class GenerateTypeBase {
private String name;
private String age;
// getters and setters (omitted for brevity)
}
In this different children classes will instantiated based on the identifierType property.
The children must extend this base class:
public class GenerateType2 extends GenerateTypeBase {
// additional properties
}
In a short test it will be:
#Test
void wrapperTest() throws IOException {
ObjectMapper mapper = new ObjectMapper();
GenerateType2 a = new GenerateType2();
a.setName("Foo");
a.setAge("13");
TypeSyntax w = new TypeSyntax();
w.setIdentifierTypeValues(a);
String json = mapper.writeValueAsString(w);
System.out.println(json);
}
and the output:
{
"identifierTypeValues":
{
"name":"Foo",
"age":"13"
},
"identifierType":"Type2"
}
Deserialization
#Test
void wrapperTest() throws IOException {
ObjectMapper mapper = new ObjectMapper();
String input = "{\"identifierTypeValues\": \"name\":\"Foo\",\"age\":\"13\"},\"identifierType\":\"Type2\"}";
TypeSyntax w = mapper.readValue(new StringReader(input), TypeSyntax.class);
assertAll(
() -> assertEquals(GenerateType2.class, o.getIdentifierTypeValues().getClass()),
() -> assertEquals("13", o.getIdentifierTypeValues().getAge())
);
}
If you want more flexibility you can write custom (de)serializer and / or custom resolver. Using custom TypeIdResolver that will possible to convert identifiers to types programmatically instead of using "key-value pairs" in #JsonSubTypes

How to learn Jackson to cast inheritors of abstract class?

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;
}

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.

ModelMapper: Choose mapping based on Child class

TL;DR
I want to use modelMapper in a way that I map from AbstractParent to AbstractParentDTO and later in the ModelMapper-Config call the specific mappers for each Sub-class and then skip the rest of the (abstrac-class) mappings.
How is that Possible? Is this the right approach? Is there a design flaw?
What I have:
The parent entity:
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "type")
public abstract class Parent {
//some more fields
}
One child entity:
//Basic Lombok Annotations
#DiscriminatorValue("child_a")
public class ChildA extends Parent {
//some more fields
}
Another child entity:
#DiscriminatorValue("child_b")
public class ChildB extends Parent {
//some more fields
}
Then I have the parent DTO class:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
#JsonSubTypes({
#JsonSubTypes.Type(value = ChildA.class, name = "child_a"),
#JsonSubTypes.Type(value = ChildB.class, name = "child_b"),
public abstract class ParentDTO {
//some more fields
}
One Child DTO:
public class ClassADTO extends ParentDTO {
//some more fields
}
and another DTO:
public class ClassBDTO extends ParentDTO {
//some more fields
}
In my case I'll get DTO's from the controller and map them to Entities when giving them to the Service. I'll have to do the same thing in 5-6 Endpoints.
The Endpoints look roughly like this:
#PreAuthorize(CAN_WRITE)
#PutMapping("/{id}")
public ResponseEntity<ParentDTO> update(
#PathVariable("id") UUID id,
#RequestBody #Valid ParentDTO parentDTO) {
Parent parent = parentService.update(id, parentDTO);
if (parentDTO instanceof ChildADTO) {
return ResponseEntity.ok(modelMapper.map(parent, ChildADTO.class));
} else if (parentDTO instanceof ChildBDTO) {
return ResponseEntity.ok(modelMapper.map(parent, ChildBDTO.class));
}
throw new BadRequestException("The Parent is not Valid");
}
Only that I have a few more Childs that make things even bulkier.
What I want:
Instead of checking a bunch of times what instance the DTO (or Entity) is, I simply want to write for example:
modelmapper.map(parent, ParentDTO.class)
and do the "instance of..." check ONCE in my ModelMapper Configuration.
What I've tried:
I already have different Converters for every possible direction and mapping-case defined in my ModelMapper Configuration (since they require more complex mapping anyways).
I've tried to solve my problem by writing one more Converter for the Parent Classes and setting it as a ModelMapper PreConverter:
//from Entity to DTO
Converter<Parent, ParentDTO> parentParentDTOConverter = mappingContext -> {
Parent source = mappingContext.getSource();
ParentDTO dest = mappingContext.getDestination();
if (source instanceof CHildA) {
return modelMapper.map(dest, ChildADTO.class);
} else if (source instanceof ChildB) {
return modelMapper.map(dest, ChildBDTO.class);
}
return null;
};
and:
modelMapper.createTypeMap(Parent.class, ParentDTO.class)
.setPreConverter(parentParentDTOConverter);
But I'm always getting the same MappingError:
1) Failed to instantiate instance of destination
com.myexample.data.dto.ParentDTO. Ensure that
com.myexample.data.dto.ParentDTOO has a non-private no-argument
constructor.
which I get (I guess), I cannot construct an Object of an abstract class. But thats not what I'm trying, am I?
I guess that modelMapper is still doing the rest of the Mapping after finishing with my PreConverter. I've also tried to set it with .setConverter but always with the same result.
Does anyone knows how to 'disable' the custom mappings? I don't
really want to write "pseudo-mappers" that act like mappers and just
call the specific mappers for each scenario.
Is my design just bad? How would you improve it?
Is this just not implemented into ModelMapper yet?
Any help and hint is appreciated.
Well, the solution I found uses converters. In this case modelMapper doesn't try to create a new instance of abstract class, but uses the converter directly.
You can put all the converters in same place
modelMapper.createTypeMap(ChildA.class, ParentDTO.class)
.setConverter(mappingContext -> modelMapper.map(mappingContext.getSource(), ClassADTO.class));
modelMapper.createTypeMap(ChildB.class, ParentDTO.class)
.setConverter(mappingContext -> modelMapper.map(mappingContext.getSource(), ClassBDTO.class));
....
I would use ObjectMapper instead of ModelMapper.
In Parent class add the possibility to get the discriminator value.
//..
public class Parent {
#Column(name = "type", insertable = false, updatable = false)
private String type;
//getters and setters
}
Your ParentDTO should be mapped to Child(*)DTO
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = ChildADTO.class, name = "child_a"),
#JsonSubTypes.Type(value = ChildBDTO.class, name = "child_b")
})
public abstract class ParentDTO {
// ..
}
in the conversion service/method add an object mapper with ignore unknown (to ignore what you did not declare in your DTO class)
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
just simply call :
Parent parent = // get from repository
ParentDTO parentDTO = objectMapper.readValue(objectMapper.writeValueAsBytes(parent), ParentDTO.class);
In this way, your ParentDTO is always instantiated with the right type.
How about
TypeMap<Parent.class, ParentDTO.class> typeMap = modelMapper.createTypeMap(Parent.class, ParentDTO.class);
typeMap
.include(ChildA .class, ClassADTO .class)
.include(ChildB.class, ClassbDTO.class);
reference :http://modelmapper.org/user-manual/type-map-inheritance

How to ignore json property in encapsulated design

class A{
private B b;
//other properties
//getter setter
}
// unable to add jsonIgnore in this class due to dependency in other module
class B {
int id;
String name;
String defname;
}
I want to ignore defname in class A JSON building by codehaus.jackson API.
I need {a:{id:value,name:value}}.
You can use Mixin for this purpose.
First Create an abstract class with JsonIgnore annotation:
abstract class MixIn{
#JsonIgnore
abstract String getDefname(); }
Then use it as below. (Be sure your getter name of defName field as getDefName() in your B class or change it in Mixin class as yours.)
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn( B.class, MixIn.class );
objectMapper.writeValue( System.out, new A() );
This prints:
{"b":{"id":1,"name":"Sercan"}}

Categories

Resources