How to use modelMapper to convert nested classes - java

I have a simple class that I want to map to a DTO class using modelMapper.
class Source {
private String name;
private String address;
List<Thing> things;
// getters and setters follows
}
class Thing {
private String thingCode;
private String thingDescription;
// getters and setters
}
and I want to convert these to a sourceDTO that contains a list of ThingDTOs, for example
class sourceDTO {
private String name;
private String address;
List<ThingDTO> things;
// getters and setters.
}
class ThingDTO {
private String thingCode;
private String thingDescription;
// getters and setters
}
If I drop my list of Things and list of ThingsDTO then modelmapper is a delight to use,
modelMapper.map(source, SourceDTO.class);
But I can't work out how to get the mapper to convert the List of Things to List of ThingDTOs. From the documentation, I think I need to create a mapper class that extends PropertyMap but I can't work out how to configure it.
Any pointers to the relevant documentation would be welcome

I think if you configure your ModelMapper as LOOSE or STANDARD it will do for you.
modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.LOOSE);
Otherwhise you could try next:
You may create a converter like:
public class ListThingToThingDTOConverter implements Converter<List<Thing>, List<ThingDTO>> {
#Override
public List<ThingDTO> convert(MappingContext<List<Thing>, List<ThingDTO>> context) {
List<Thing> source = context.getSource();
List<ThingDTO> output = new ArrayList<>();
...
//Convert programmatically List<Thing> to List<ThingDTO>
...
return output;
}}
Then customize a Mapping Thing to ThingDTO as next:
public class SourceToSourceDTOMap extends PropertyMap<Thing, ThingDTO> {
#Override
protected void configure(){
using(new ListThingToThingDTOConverter()).map(source.getThings()).setThings(null);
}
Finally you must add SourceToSourceDTOMap to your ModelMapper as below:
modelMapper = new ModelMapper();
modelMapper.addMappings(new SourceToSourceDTOMap());

You can map like the below code by creating generics . link for reference
http://modelmapper.org/user-manual/generics/
imports :
import java.lang.reflect.Type;
import org.modelmapper.ModelMapper;
import org.modelmapper.TypeToken;
In your service or controller class:
ModelMapper modelMapper = new ModelMapper();
Type listType = new TypeToken<SourceDTO>(){}.getType();
SourceDTO sourceDTO = modelMapper.map(source,listType);

Related

How to define reusable classes with dynamic types inside?

I have a class SuccessResponse that contains another custom class SuccessCustomerResponseBody, both of these classes are a POJO model for some XML structure.
#JacksonXmlRootElement(localName = "response")
public class SuccessResponse {
private SuccessCustomerResponseBody ok;
public SuccessCustomerResponseBody getOk() {
return ok;
}
public void setOk(SuccessCustomerResponseBody ok) {
this.ok = ok;
}
}
and SuccessCustomerResponseBody
#Getter
#Setter
public class SuccessCustomerResponseBody {
#JacksonXmlElementWrapper(localName = "customers")
private List<Customer> customers;
private String requestId;
#JacksonXmlProperty(localName = "customer")
public List<Customer> getCustomers() {
return customers;
}
}
This model is used to fetch data from a database, and return as an XML response in service controller GetCustomer. It looks okay except when it's required to add another controller to return for example customer packages. So, the easiest way, to create two more classes like SuccessPackageResponse and SuccessPackageResponseBody, rename SuccessResponse to SuccessCustomerResponse and that's it. Well done
#JacksonXmlRootElement(localName = "response")
public class SuccessPackageResponse {
private SuccessPackageResponseBody ok;
public SuccessPackageResponseBody getOk() {
return ok;
}
public void setOk(SuccessPackageResponseBody ok) {
this.ok = ok;
}
}
SuccessPackageResponseBody
#Getter
#Setter
public class SuccessPackageResponseBody {
#JacksonXmlElementWrapper(localName = "packages")
private List<Package> packages;
private String requestId;
#JacksonXmlProperty(localName = "package")
public List<Package> getPackages() {
return packages;
}
}
The main problem is that this approach produces a lot of code. So, I tried to re-use SuccessResponse by implementing this class as generic one.
#JacksonXmlRootElement(localName = "response")
public class SuccessResponse<T> {
private T ok;
public T getOk() {
return ok;
}
public void setOk(T ok) {
this.ok = ok;
}
}
Looks better, right? We can use it like:
SuccessResponse<SuccessCustomerResponseBody> successCustomerResponse = objectMapper.convertValue(node.getMap().get("response"), SuccessResponse.class);
I thought that this is my solution, but I found that ObjectMapper configs are not applied in such approach and I don't understand why.
objectMapper.configOverride(String.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
To finalise, I have two questions:
Any idea how to re-use SuccessResponse in case when custom type (SuccessCustomerResponseBody) can be dynamically changed.
Why ObjectMapper config isn't applied in solution with generic?
I suppose the reason of losing ObjectMapper config is related to Java Type Erasure, because element type information is not available to Jackson in runtime.
So, to fix it I decided to use TypeReference to set correct type.
objectMapper.convertValue(node.getMap().get("response"), new TypeReference<MyCustomType>() {});
After some research I found another way to resolve my issue with Generic Data type. JavaType is quite useful for my case. Please, see the code below
JavaType customerResponseDataType = objectMapper.getTypeFactory().constructParametricType(SuccessResponse.class, SuccessCustomerResponseBody.class);
SuccessResponse<SuccessCustomerResponseBody> successCustomerResponse = objectMapper.convertValue(node.getMap().get("response"), customerResponseDataType)
where SuccessResponse.classis generic with input type SuccessCustomerResponseBody.class

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 bind a Map object containing Pojos to a yaml file?

In a spring-boot/ spring-cloud application, I would like to bind a Map object to my application.yml but I've got a "Elements ... where lef unbound error".
In my class called Ebox, I would like to bind a map called infosTenants, indentified by a string and containing values of type InfosTenant.
Below my application.yml (without the getters / setters of each classes or subclasses)
#ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
public class ApplicationProperties {
private Ebox ebox = new Ebox();
public ApplicationProperties() {
}
// getters/setters ...
public static class Ebox {
private String authUrl;
private Map<String, InfosTenant> infosTenants = new HashMap<>();
public Ebox() {
}
public class InfosTenant{
private String clientId="";
private String clientSecret="";
public InfosTenant() {
}
// getters/setters ...
}
}
}
In my application.yml, I defined one tenant in my tenants map, indentified by the key tenant1.
application:
ebox:
auth-url: https://oauth-server/api/oauth/token
infos-tenants:
tenant1:
client-id: myclient
client-secret: secret
But all values under infos-tenants were left unbound.
Does somebody have an idea ?
Thanks
I found my error, inner classes should be static, I forgot the static before class InfosTenant.
public static class InfosTenant{
private String clientId="";
private String clientSecret="";
public InfosTenant() {
}
// getters/setters ...
}

serialize and deserialize without map name

I have the following Object:
public class Class_a{
private List<class_b> someList;
}
public class Class_b{
private Map<String,String> someMap;
}
My json will look like this:
"someList":[{"someMap":{"strKey1":"strValue1"}},{"someMap":{"strKey2":"strValue2"}}]
Is it possible to serialize a Json that will look like this, without changing my Objects (and I will have the option to deserialize the Object):
"someList":[{"strKey1":"strValue1"},{"strKey2":"strValue2"}]
*I know that if will defined my object like this:
public class Class_a{
private List<Map<Strung,String>> someList;
}
i will get a Json like I want - but I am trying to find more elegant solution then 'list' that contain a 'map'
My project use spring framework and Jackson.
This worked for me I only had to add getters and setters to your classes and I was able to parse with jackson:
#Test
public void t() throws IOException {
String json = "{\"someList\":[{\"someMap\":{\"strKey1\":\"strValue1\"}},{\"someMap\":{\"strKey2\":\"strValue2\"}}]}";
Class_a a = new ObjectMapper().readValue(json, Class_a.class);
System.out.println(a);
}
#Getter
#Setter
public class Class_a{
private List<Class_b> someList;
}
#Getter
#Setter
public class Class_b{
private Map<String,String> someMap;
}
I'm using lombok but that's nothing special, you can create the getters/setters manually too and will work

Java Object mapping framework working with builder pattern

Is there any class mapping framework which works with builders? I would like to keep some of my classes immutable and avoid multiple constructors - the Builder Pattern comes to the rescue. However I can't any mapping framework which would use builder automatically instead of getters/setters.
I got the following working with Lombok and ModelMapper. See: http://modelmapper.org/getting-started/
public class MyService {
private ModelMapper modelMapper;
public MyService(){
this.modelMapper = new ModelMapper();
this.modelMapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.STRICT)
.setDestinationNamingConvention(LombokBuilderNamingConvention.INSTANCE)
.setDestinationNameTransformer(LombokBuilderNameTransformer.INSTANCE);
}
public OutputDTO aMethod(final InputDTO input){
return modelMapper.map(input, OutputDTO.OutputDTOBuilder.class).build();
}
}
Where LombokBuilderNamingConvention is:
import org.modelmapper.spi.NamingConvention;
import org.modelmapper.spi.PropertyType;
public class LombokBuilderNamingConvention implements NamingConvention {
public static LombokBuilderNamingConvention INSTANCE = new LombokBuilderNamingConvention();
#Override
public boolean applies(String propertyName, PropertyType propertyType) {
return PropertyType.METHOD.equals(propertyType);
}
#Override
public String toString() {
return "Lombok #Builder Naming Convention";
}
}
And LombokBuilderNameTransformer is:
import org.modelmapper.spi.NameTransformer;
import org.modelmapper.spi.NameableType;
public class LombokBuilderNameTransformer implements NameTransformer {
public static final NameTransformer INSTANCE = new LombokBuilderNameTransformer();
#Override
public String transform(final String name, final NameableType nameableType) {
return Strings.decapitalize(name);
}
#Override
public String toString() {
return "Lombok #Builder Mutator";
}
}
And OutputDTO can look like:
#Builder // Has .builder() static method
#Value // Thus immutable
public class OutputDTO {
private String foo;
private int bar;
}
This can be easily done with MapStruct and using a custom naming strategy for builders.
Have a look here in the documentation how to use Custom Accessor naming strategy.
Your mappings then need to look like:
#Mapper
public interface MyMapper {
default Immutable map(Source source) {
return mapToBuilder(source).build();
}
Immutable.Builder mapToBuilder(Source source);
}
Within MapStruct we are already working on a feature that would support out of the box support for builders. You can follow this issue for more details.
Update
MapStruct now (since 1.3.0.Beta1) has out of the box support for Immutables. This means that the mapper before can be written like:
#Mapper
public interface MyMapper {
Immutable map(Source source);
}
The assumption is that there is a public static method without parameters in Immutable that returns the builder
Uing Lombok and ModelMapper configure as:
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(AccessLevel.PRIVATE);
By default ModelMapper uses only public setter method to map. When the class annotated with Lombok builder annotation it made the setter method as private. So to allow the ModelMapper to use the private setter method we need to add the above configureation.
OR
Configuration builderConfiguration = modelMapper.getConfiguration().copy()
.setDestinationNameTransformer(NameTransformers.builder())
.setDestinationNamingConvention(NamingConventions.builder());
modelMapper.createTypeMap(MyEntity.class, MyDto.MyDtoBuilder.class, builderConfiguration);
where MyEnity class is:
#Data
private static class MyEntity {
private Long id;
private String name;
private String value;
}
and builder class is:
#Data
#Builder
private static class MyDto {
private final Long id;
private final String name;
private final String value;
}
click here for detail

Categories

Resources