Java Mapstruct generic enum converter - java

I have two different enum classes and one of them use them as a value like
public enum Type{
TEST1{
#Override
public Type convertToINT() {
return Type.INTEGRATION1;
}
},
TEST2{
#Override
public Type convertToINT() {
return Type.INTEGRATION2;
}
},
TEST3{
#Override
public TypeIntegration convertToINT(){
return Type.INTEGRATION3;
}
};
public abstract TypeIntegration convertToINT();
}
public enum TypeIntegration {
INTEGRATION1,
INTEGRATION2,
INTEGRATION3
}
This enums uses in different classes for example ;
#Getter
#Setter
public class typeSaveReqDto{
private blabla;
private blabla;
private Type type;
}
#Getter
#Setter
public class typeIntegrationObject{
private blabla;
private blabla
private TypeIntegration type;
}
I want to use mapstructs and convert via auto generated classes, but mapstruct throws me exception like "The following constants from the property "Type type" enum have no corresponding constant in the "TypeIntegration type" enum and must be be mapped via adding additional mappings: TEST1, TEST2, TEST3"
I want to create EnumMapper classes for converting enums, How can i write generic classes for this with mapStructs java ?
Edit :
I generated EnumMapper
#Mapper
public class EnumMapper {
EnumMapper INSTANCE = Mappers.getMapper(EnumMapper.class);
#Named("enumToIntEnum")
public static <T extends EnumConverter<INT>,INT> INT convertToINT(T enums, #TargetType INT enumClass){
INT convertObj = ((T)enums).convertToINT();
return convertObj;
}
}
And Mapper interfaces like below
#Mapper(componentModel = "spring",uses = EnumMapper.class)
public interface TypeReqMapper {
#Mapping(source = "type" , target = "type",qualifiedByName = "enumToIntEnum")
public TypeIntegrationObject typeSaveReqDtoTotypeIntegrationObject(TypeSaveReqDto typeSaveReqDto);
}
But I got a fail like 'Can't map property "Type type" to "TypeIntegration type". Consider to declare/implement a mapping method: "TypeIntegration map(Type value)".'

Type T isn't know and doesn't contain a convertToINT method as it can be anything. So keep it simple and just write a dedicated mapper instead of trying to build one that does everything.
#Mapper(componentModel="spring")
public class TypeToTypeIntegrationMapper {
#Mapping
public TypeIntegration map(Type from) {
return from.convertToINT();
}
}
Don't make it more complex.
You could even ditch the mapper and write an expression instead.
#Mapper(componentModel = "spring",uses = EnumMapper.class)
public interface TypeReqMapper {
#Mapping(source = "type" , target = "type", expression = "java(type.convertToINT())")
public TypeIntegrationObject typeSaveReqDtoTotypeIntegrationObject(TypeSaveReqDto typeSaveReqDto);
}
MapStruct will now use the expression to generate the mapping code instead of you having to write a mapper yourself.

Related

Cannot read enum property, ClassCastException

For my model I have problem with reading enum property. After reading from mongoDB, the property 'value' has type String instead of MyEnum.
My model:
public class Record {
#Id
public String id;
public Wrapper<MyEnum> wrappedEnum;
...
}
public class Wrapper<T extends Enum<?>> extends BaseWrapper<T>{
...
}
public class BaseWrapper<T> {
public T value;
...
}
Code:
Record record = new Record();
Wrapper<MyEnum> wrapper = new Wrapper<>();
wrapper.setValue(MyEnum.A);
record.setWrappedEnum(wrapper);
repository.save(record);
repository.findAll().forEach(rec -> {
Object value = rec.getWrappedEnum().value;
System.out.println("rec.getWrappedEnum().value: " + value); // rec.getWrappedEnum().value: A
System.out.println("rec.getWrappedEnum().getValue(): " + value.getClass()); // rec.getWrappedEnum().getValue(): class java.lang.String
MyEnum valEnum = rec.getWrappedEnum().getValue(); // throws Caused by: java.lang.ClassCastException: class java.lang.String cannot be cast to class com.example.MyEnum (java.lang.String is in module java.base of loader 'bootstrap'; com.example.MyEnum is in unnamed module of loader 'app')
});
Is any option to configure mongodb to read this model correctly?
The reason why you are getting a ClassCastException is because you didn't specify how the persistence layer should actually persist your wrapped enum.
I suppose you are using JPA as the persistence API. There are actually multiple ways to achieve what you want. The more convenient way for your case would be to register a converter, so that JPA knows how to persist your Enum and map it back. A basic example could look like this (Note that there are setter for the value field in BaseWrapper):
#Converter
public class WrapperConverter implements AttributeConverter<Wrapper, String> {
#Override
public String convertToDatabaseColumn(Wrapper attribute) {
return attribute.getValue().toString();
}
#Override
public Wrapper convertToEntityAttribute(String dbData) {
MyEnum myEnum = MyEnum.valueOf(dbData);
Wrapper<MyEnum> wrappedEnum = new Wrapper<>();
wrappedEnum.setValue(myEnum);
return wrappedEnum;
}
}
Register the converter:
#Entity
public class Record {
#Id
private String id;
#Convert(converter = WrapperConverter.class)
private Wrapper<MyEnum> wrappedEnum;

Writing custom deserializer for polymorphic type hierarchy Jackson

I'm experimenting with Jackson deserialization for inheritance in Java.
I've a base class:
#Getter //Lombok #Getter
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value=ClassA.class, name = "classA")
})
public abstract class BaseClass {
private List<String> fields;
#JsonCreator
public BaseClass(#JsonProperty("fields") final List<String> fields) {
this.fields = fields;
}
}
ClassA is also abstract
#Getter
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "typeA", include = JsonTypeInfo.As.PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value=SubClassA.class, name = "subclassA")
})
public abstract class ClassA extends BaseClass{
private String mode;
#JsonCreator
public ClassA(#JsonProperty("fields") final List<String> fields, #JsonProperty("mode") String mode) {
super(fields);
this.mode = mode;
}
}
My subClassA:
public class SubClassA extends ClassA {
private String dummyField;
public SubClassA(#JsonProperty("fields") final List<String> fields, #JsonProperty("mode") String mode,
#JsonProperty("dummyField") String dummyField) {
super(fields, mode);
this.dummyField = dummyField;
}
}
If I pass in a JSON of in the following form:
{
"type": "classA",
"typeA": "subclassA",
"mode": "testingMode",
"fields": ["1", "2"],
"dummyField": "dummy"
}
I get an error Cannot construct instance of ClassA (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
I came across this https://github.com/FasterXML/jackson-databind/issues/374 which says this is a known issue with Jackson.
How do I go about writing a customDeserializer for this.
In classA I tried doing this:
#JsonDeserialize(using = ClassADeserializer.class)
and ClassADeserializer is:
public class ClassADeserializer extends StdDeserializer<ClassA> {
private final JsonDeserializer<?> defaultDeserializer;
public ClassADeserializer(JsonDeserializer<?> defaultDeserializer) {
super(ClassA.class);
this.defaultDeserializer = defaultDeserializer;
}
#Override public ClassA deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return (ClassA) defaultDeserializer.deserialize(jsonParser, deserializationContext);
}
which obviously doesn't work. How do I go about writing a custom deserializer for this?
Problem:
You pass in json "type": "classA",... That means jackson first try to create instance of ClassA..During deserialization jackson search #JsonCreator constructor first..If #JsonCreator missing or can not call #JsonCreator constructor then jackson create object with default constructor and call setter method... In your ClassA #JsonCreator constructor with 2 arguments but jackson call with 3 arguments.. So its fail. then jackson call default constructor to create instance. but default constructor also missing.. thats why u get this error: Cannot construct instance of ClassA (no Creators, like default construct, exist)..
Solution:
As you want to deserialize to SubClassA... You need to use #JsonCreator in SubClassA...Then you need to use #JsonIgnoreProperties to ignore properties type so that jackson create instance of SubClassA instead of ClassA....
Try with below SubClassA:
#Getter
#JsonIgnoreProperties(ignoreUnknown = true)
public class SubClassA extends ClassA {
private String dummyField;
#JsonCreator
public SubClassA(#JsonProperty("fields") final List<String> fields, #JsonProperty("mode") String mode,
#JsonProperty("dummyField") String dummyField) {
super(fields, mode);
this.dummyField = dummyField;
}
}

Java generics override static methods workaround

I'm working on a project that requires me to serialize and deserialize generic objects. The way I'm going about this, is defining an abstract class Serializer that implements a toBytes() and a static fromBytes(). All is well with this approach, as I can pass an object instance to a generic class Foo that expects a Serializer subclass, and I can ensure the object knows how to serialize and deserialize itself.
Now my question. Java serialization kinda sucks. I have multiple implementations I'd like to try swapping in and out, and ultimately I'd like the user to be able to decide the format. How would I go about changing the implementation details of Serializer? I know I can't override static methods, so how would I do this without decoupling Foo and Serializer and not being able to ensure my generic object has the appropriate toBytes() and fromBytes() method in Foo?
Here is code if anyone is confused:
public abstract class Serializer {
public static Serializer fromBytes(byte[] bytes) {
...
}
public byte[] toBytes() {
...
}
}
public class Foo<T extends Serializer> {
private T t;
public Foo(T t) {
this.t = t;
}
public void foo() {
t.toBytes(); //this will polymorph into the correct call because it's called on the object instance and not the Serializer class
}
public void bar(byte[] bytes) {
T.fromBytes(bytes); // I'd like to be able to override this method so I can use different implementations
}
}
I'm not sure if this is a good approach, but how about using Jackson library and serialize your object as a json node? for example:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = SoundFile.class, name = "sound"),
#Type(value = VideoFile.class, name = "video")
})
abstract class File{
private String id;
private String type;
#JsonCreator
public File(#JsonProperty("id") String id)
{
this.id=id;
}
public String getId() {return this.id;}
public abstract String getType();
}
class SoundFile extends File{
#JsonCreator
public SoundFile(#JsonProperty("id") String id) {
super(id);
}
#Override
public String getType() {
return "sound";
}
}
class VideoFile extends File{
#JsonCreator
public VideoFile(#JsonProperty("id") String id) {
super(id);
}
#Override
public String getType() {
return "video";
}
}
public class GenericApp {
public static void main(String[] args) {
ObjectMapper om = new ObjectMapper();
List<File> files = Arrays.asList(new VideoFile("1"),new SoundFile("2"));
//serialize
List<byte[]> fileSerialized = files.stream().map(file->{
try {
return om.writeValueAsBytes(file);
}catch(Exception e) {return null;}
}).collect(Collectors.toList());
//de-serialize
List<File> filesDeSerialized = fileSerialized.stream().map(bytes ->{
try {
return om.readValue(bytes, File.class);
}
catch(Exception e) {return null;}
}).collect(Collectors.toList());
filesDeSerialized.stream().forEach(file->{
System.out.println("id :"+file.getId()+" - "+file.getClass());
});
}
}
this would properly deserialize these objects and print:
id :1 - class com.dsncode.stackoverflow.VideoFile
id :2 - class com.dsncode.stackoverflow.SoundFile
however, you should define a #JsonTypeInfo and a #JsonSubType for all your sub-classes of your Generic Type. Because, by indicating this field, you will indicate to Jackson deserializer, which class should create for your generic type.

Using different projection for each entity subtype

Is it possible define diferent Projections by SubTypes and Spring Data REST
use the most concrete projection about class type?
This issue was exposed on JIRA issue DATAREST-739 and also exists a merge commit but this not appears on official changelog and also I don't found any documentation or guide to solved it with the current releases.
The use case sample used into the issue is:
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#DiscriminatorColumn(name="type")
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
public abstract class Message implements Identifiable<UUID> { ... }
#Entity
#DiscriminatorValue("TEXT")
#JsonTypeName("TEXT")
public class TextMessage extends Message { ... }
#Entity
#DiscriminatorValue("TODO")
#JsonTypeName("TODO")
public class TodoMessage extends Message { Boolean isDone; }
#Projection(name = "summary", types = TodoMessage.class)
public class TodoMessageSummary { Boolean getIsDone(); }
#Projection(name = "summary", types = TextMessage.class)
public class TextMessageSummary { ... }
public interface MessageRepo extends JpaRepository<Message, UUID> { ... }
#RepositoryRestResource(excerptProjection = TodoMessageSummary.class)
public interface TodoMessageRepo extends JpaRepository<Message, UUID> { ... }
#RepositoryRestResource(excerptProjection = TextMessageSummary.class)
public interface TextMessageRepo extends JpaRepository<TextMessage, UUID> { ... }
First issue: how to define an excerpt projection for MessageRepo to use TodoMessageSummary for TodoMessage entities and TextMessageSummary for TextMessage?
Second issue: how to define projection for another entity that has a Message field? Let's say you have the following:
#Projection(name = "summary", types = Dashboard.class)
public class DashboardSummary {
List<Message> getMessages();
}
SOLVED:
The trick is use inheritance into the subtype projections:
#Projection(name = "summary", types = Message.class)
public class MessageSummary {
#Value("#{target.getClass().getSimpleName()}")
String getType();
}
#Projection(name = "summary", types = TextMessage.class)
public class TextMessageSummary extends MessageSummary { ... }
#Projection(name = "summary", types = TodoMessage.class)
public class TodoMessageSummary extends MessageSummary {
Boolean getIsDone();
}
The Spring REST #RepositoryRestResource returns an array of Messages using the concrete subtype projection ( isDone must be appear into TodoMessage instances)
This issue a bit more complex if you need to made the same into a extendend #RequestMapping into a Controller, to do so I use the next snnipeed:
Page<Message> results = repository.findAll(predicate, pageable);
Converter<? super Message, ? extends MessageSummary> converter= l -> {
if(l instanceof TextMessage){
return projectionFactory.createProjection(TextMessageSummary.class,l);
}
else if(l instanceof TodoMessage){
return projectionFactory.createProjection(TodoMessageSummary.class,l);
}
else {
return projectionFactory.createProjection(MessageSummary.class,l);
}
};
Page<MessageSummary> projected =results.map(converter);
return pagedAssembler.toResource(projected);
Note that if only need resource type info on frontend for read only purpose (i.e. for POST/PUT use concrete subtype endpoints) I think that is not really needed use the #JsonTypeInfo because using SpEL into projections allow get this type info more easy and flexible:
#Value("#{target.getClass().getSimpleName()}")
String getType();
Yes it's possible. We have such sub-types structure in our application.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
public abstract class Vehicle {
protected VehicleType type;
}
public class Plane extends Vehicle {
private String title;
}
Projections for them:
public interface VehicleProjection {
String getType();
}
#Projection(name = "default", types = Plane.class)
public interface PlaneProjection extends VehicleProjection {
String getTitle();
}
And rest repository:
#RepositoryRestResource(collectionResourceRel = "collectionName", path = "path",
excerptProjection = VehicleProjection.class)
public interface RestVehicleRepository<T extends Vehicle> extends MongoRepository<T, String> {
}
Also we've registered those projections in configuration. Not sure whether it is required for your case, because we have more then one projection for each sub-type:
#Configuration
public class CustomRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(final RepositoryRestConfiguration config) {
config.getProjectionConfiguration().addProjection(PlaneProjection.class,
"default", Plane.class);
}
}

Jackson serializes strange output

I am using Jackson to convert json to an object. However, the json looks wrong. Here is what I am seeing:
"interfaces": {"interfaces": [
"HA_1",
"HA_2"
]},
There should not be two interfaces. I want to see:
"interfaces": [
"HA_1",
"HA_2"
]},
I am not sure how this is happening. I can show you my conversion classes:
#XmlAccessorType(XmlAccessType.NONE)
public class InterfacesRep implements Serializable {
private static final long serialVersionUID = -1503363608473342020L;
#XmlElement(name = "interface", type = String.class)
private Collection<String> all = new ArrayList<String>();
public InterfacesRep() {}
public InterfacesRep(Collection<String> all) {
this.all = all;
}
public Collection<String> getAll() {
return all;
}
public void setAll(List<String> all) {
this.all = all;
}
}
And the outer class:
public class OuterRep {
private static final long serialVersionUID = -1719378545790376294L;
#XmlElement(name = "interfaces", type=InterfacesRep.class)
private InterfacesRep interfaces;
public OuterRep() {
}
public InterfacesRep getInterfaces() {
return interfaces;
}
public void setInterfaces(InterfacesRep interfaces) {
this.interfaces = interfaces;
}
}
Do you know why I see "interfaces" twice?
Because you are defining it on the property at both levels.The outer class has a property name called "interfaces" and the inner class's Collection is also named "interfaces".
This simplest fix (in my mind) would be to not use a wrapper class for the Collection. Just put the collection in the outer class.
On a side note, why are you using Jackson's XML annotations to serialize JSON?

Categories