ModelMapper reverse map from a destination field onto itself - java

Is there a way ModelMapper can reverse map a value already set using PropertyMap again to another field in destination? In other words, I have a computed value and the same has to be set on two fields using PropertyMap, computation changes the value everytime and cannot be invoked twice using(converter).

Introduction
Let's consider the following versions as the current versions:
ModelMapper: 3.1.0.
It seems that by the «ModelMapper» library design:
There is no way to «reuse» the converted source value to set multiple destination property values.
Possible solution
A post-converter may be used to copy a value from one destination property to another.
Draft example program
pom.xml file
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.1.0</version>
</dependency>
Customer class
class Customer {
private String fullName;
public String getFullName() {
return fullName;
}
public void setFullName(final String fullName) {
this.fullName = fullName;
}
}
CustomerDTO class
import java.util.StringJoiner;
class CustomerDTO {
private String fullName;
private String copyOfFullName;
public String getFullName() {
return fullName;
}
public void setFullName(final String fullName) {
this.fullName = fullName;
}
public String getCopyOfFullName() {
return copyOfFullName;
}
public void setCopyOfFullName(final String copyOfFullName) {
this.copyOfFullName = copyOfFullName;
}
#Override
public String toString() {
return new StringJoiner(", ", CustomerDTO.class.getSimpleName() + "[", "]")
.add("fullName='" + fullName + "'")
.add("copyOfFullName='" + copyOfFullName + "'")
.toString();
}
}
Program class
import org.modelmapper.Converter;
import org.modelmapper.ModelMapper;
public final class Program {
public static void main(final String[] args) {
final Customer customer = new Customer();
customer.setFullName("Full name");
final ModelMapper modelMapper = new ModelMapper();
final Converter<Customer, CustomerDTO> customerConverter = context -> {
final CustomerDTO destination = context.getDestination();
// NOTE: Copying the value of the full name property to another property.
destination.setCopyOfFullName(destination.getFullName());
return destination;
};
modelMapper.typeMap(
Customer.class, CustomerDTO.class
).addMapping(
Customer::getFullName, CustomerDTO::setFullName
).setPostConverter(customerConverter);
modelMapper.validate();
final CustomerDTO customerDTO = modelMapper.map(customer, CustomerDTO.class);
System.out.println(customerDTO);
}
}
Output
CustomerDTO[fullName='Full name', copyOfFullName='Full name']
Additional references
Post-converter example. java - ModelMapper: transferring attribute from root to each elements of a list - Stack Overflow.
Answer.

Related

How to refactor the way of getting multiple #Value from a properties file

I need to get some values from aplication.properties file and these values depend on the country, for instance these are the below values that I need to fetch:
#Value("${fr.Name}")
private String frName;
#Value("${fr.address}")
private String frAddress;
#Value("${de.Name}")
private String deName;
#Value("${de.address}")
private String deAddress;
#Value("${bz.Name}")
private String bzName;
#Value("${bz.address}")
private String bzAddress;
Then in my service method, I will be using multiple if else statement and my list of values keep increasing each time I add new country
public String apply(string country){
if ("fr".equals(country))
return frName + frAddress
else if ("de".equals(country))
return deName + deAddress
else if ("bz".equals(country))
return bzName + bzAddress
}
So how can I refactor it without using multiple if-else statements?
Look at spring configuration properties. this gives an opportunity to handle configuration properties more declarative way
https://www.baeldung.com/configuration-properties-in-spring-boot
EDIT
#Component
#ConfigurationProperties(prefix = "my", ignoreUnknownFields = false)
public class CountryBasedProps {
Map<String,CountryProps> config = new HashMap<>();
public Map<String, CountryProps> getConfig() {
return config;
}
public void setConfig(Map<String, CountryProps> config) {
this.config = config;
}
public static class CountryProps{
String name;
String address;
/*
other properties, with getters/setters
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
#Override
public String toString() {
return "CountryProps{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
#Override
public String toString() {
return "CountryBasedProps{" +
"config=" + config +
'}';
}
}
properties:
my.config.fr.name=French
my.config.fr.address=Paris
my.config.de.name=German
my.config.de.address=Berlin
output
CountryBasedProps{config={fr=CountryProps{name='French', address='Paris'}, de=CountryProps{name='German', address='Berlin'}}}
Note:
add #EnableConfigurationProperties() into your project
2.7.1. Loading YAML
You can load the configuration file directly into Map using #ConfigurationProperties and then get the corresponding value with respective key. So you can avoid using if block
application.yml If you are using application.yml
countries:
fr: fnamefaddress
de: dnamedaddress
bz: bnamebaddress
application.properties : If you are using application.properties
countries.fr=fnamefaddress
countries.de=dnamedaddress
countries.be=bnamebaddress
Configuration Class
#Configuration
#ConfigurationProperties
public class CountriesConfig {
#Getter
private Map<String, String> countries = new HashMap<String, String>();
}
output :
{bz=bnamebaddress, de=dnamedaddress, fr=fnamefaddress}
So in code you can simply use get or getOrDefault on Map
countries.get("fr");

Jackson property order based on field value

I am trying to serialise and deserialise a Java class using Jackson and the JsonPropertyOrder depends on the value of the version field in the class. If version = 1, I want order to be {"start1", "start2"}, if version = 2, order should be {"end1", "end2"}.
I have below class:
#Builder
#Value
#AllArgsConstructor(onConstructor=#__(#JsonCreator))
#JsonPropertyOrder(custom property order depending on version field)
public class ClassA {
#NonNull Integer version;
#NonNull String start1;
#NonNull String start2;
#NonNull String end1;
#NonNull String end2;
}
How can I define the JsonPropertyOrder based on version on runtime. If I should use a custom Deserializer, I cannot figure out how exactly it should be implemented and set with the ObjectMapper.
This is the code for deserialisation:
private final ObjectMapper objectMapper = new ObjectMapper();//have initialzed this as a bean
String jsonStr = "{\"version\":1, \"startLat\":\"47.6812\", \"startLng\":\"-122.3268\", \"endLat\":\"47.6074\", \"endLng\":\"-122.3377\"}";
ClassA objA = null;
try {
objA = objectMapper.readValue(jsonStr, ClassA.class);
} catch (IOException e) {
log.error("Error deserializing the string", jsonStr, e);
}
return objA;
**EDIT: I missed an imp part. The property order here matters because the serialised string might not have the field names. Is that possible to do?
So the str [1, "47.6812", "-122.3268"] will need to be deserialised to the fields version, start1, start2.
If these properties are exclusive why do not create more concise POJO by removing two of them? You can use knowledge that 1 means something different than 2 by introducing some isMethod-es or Enum. To serialise class as JSON array you need to use #JsonFormat(shape = JsonFormat.Shape.ARRAY) annotation. To deserialise you can use 3-arg constructor with #JsonCreator and #JsonProperty annotations. Properties are final. All together makes - class is well implemented. See example:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Objects;
public class JsonApp {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
ClassA classA1 = new ClassA(1, "Start_1", "Start_2");
ClassA classA2 = new ClassA(2, "End_1", "End_2");
String json1 = mapper.writeValueAsString(classA1);
String json2 = mapper.writeValueAsString(classA2);
System.out.println(json1);
System.out.println(json2);
System.out.println(mapper.readValue(json1, ClassA.class));
System.out.println(mapper.readValue(json2, ClassA.class));
}
}
#JsonFormat(shape = JsonFormat.Shape.ARRAY)
class ClassA {
private final Integer version;
private final String value1;
private final String value2;
#JsonCreator
public ClassA(#JsonProperty("version") Integer version,
#JsonProperty("start1") String value1,
#JsonProperty("start2") String value2) {
Objects.requireNonNull(version);
if (!(version == 1 || version == 2)) {
throw new IllegalArgumentException("Version is not supported!");
}
this.version = version;
this.value1 = value1;
this.value2 = value2;
}
#JsonIgnore
public boolean isStart() {
return version == 1;
}
#JsonIgnore
public boolean isEnd() {
return version == 2;
}
public Integer getVersion() {
return version;
}
public String getValue1() {
return value1;
}
public String getValue2() {
return value2;
}
#Override
public String toString() {
return "ClassA{" +
"version=" + version +
", value1='" + value1 + '\'' +
", value2='" + value2 + '\'' +
", isEnd()='" + isEnd() + '\'' +
", isStart()='" + isStart() + '\'' +
'}';
}
}
Above code prints:
[1,"Start_1","Start_2"]
[2,"End_1","End_2"]
ClassA{version=1, value1='Start_1', value2='Start_2', isEnd()='false', isStart()='true'}
ClassA{version=2, value1='End_1', value2='End_2', isEnd()='true', isStart()='false'}

Consider null and empty records as same in Collectors.groupingBy

I have a list of objects where a few records can have empty value property and a few can have null value property. Using Collectors.groupingBy I need both the records to be considered as same.
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
class Code {
private String type;
private String description;
public static void main(String[] args) {
List<Code> codeList = new ArrayList<>();
Code c = new Code();
c.setDescription("abc");
c.setType("");
codeList.add(c);
Code c1 = new Code();
c1.setDescription("abc");
c1.setType(null);
codeList.add(c1);
Map<String, List<Code>> codeMap = codeList.stream()
.collect(Collectors.groupingBy(code -> getGroupingKey(code)));
System.out.println(codeMap);
System.out.println(codeMap.size());
}
private static String getGroupingKey(Code code) {
return code.getDescription() +
"~" + code.getType();
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
The result of codeMap will have two records since it considers the empty string and the null value in the Type property as different. How can I achieve getting a single record here by considering both the null and empty records as same.
You can modify your getGroupingKey method like this:
private static String getGroupingKey(Code code) {
return code.getDescription() + "~" + (code.getType() == null ? "" : code.getType());
}
or like this:
private static String getGroupingKey(Code code) {
return code.getDescription() + "~" + Optional.ofNullable(code.getType()).orElse("");
}
or you might as well modify your getType() method directly as in:
public String getType() {
return type == null ? "" : type;
}
or:
public String getType() {
return Optional.ofNullable(type).orElse("");
}
Either should work the same. Pick one depending on your requirements I guess..
If you add the following toString method to your Code class:
#Override
public String toString() {
return "Code{" +
"type='" + type + '\'' +
", description='" + description + '\'' +
'}';
}
.. with the modified getGroupingKey method (or the getType method) the output should be as follows:
{abc~=[Code{type='', description='abc'}, Code{type='null', description='abc'}]}
1
Edit: You can also considering initializing the type to an empty String instead of null, then you would not need to modify anything:
private String type = "";
That might be an option too..

How can I unwrap a specific field in a JSON using Jackson?

I have a JSON payload that looks like this:
{
"id": 32,
"name": "[Sample] Tomorrow is today, Red printed scarf",
"primary_image": {
"id": 247,
"zoom_url": "www.site.com/in_123__14581.1393831046.1280.1280.jpg",
"thumbnail_url": "www.site.com/in_123__14581.1393831046.220.290.jpg",
"standard_url": "www.site.com/in_123__14581.1393831046.386.513.jpg",
"tiny_url": "www.site.com/in_123__14581.1393831046.44.58.jpg"
}
}
Can I unwrap a specific field and discard all the others? In other words, can I bind this directly to a POJO like this:
public class Product {
private Integer id;
private String name;
private String standardUrl;
}
There are lots of ways. Do you need to deserialize, serialize or both?
One way to deserialize would be to use a creator method that takes the image as a tree node:
public static class Product {
private Integer id;
private String name;
private String standardUrl;
public Product(#JsonProperty("id") Integer id,
#JsonProperty("name") String name,
#JsonProperty("primary_image") JsonNode primaryImage) {
this.id = id;
this.name = name;
this.standardUrl = primaryImage.path("standard_url").asText();
}
}
The creator doesn't have to be a constructor, you could have a static method that is only used for Jackson deserialization.
You'd have to define a custom serializer to reserialize this, though (e.g. a StdDelegatingSerializer and a converter to wrap the string back up as an ObjectNode)
There are different ways to skin this cat, I hope you can use Jackson 2 for this, since it offers great ways to deserialize Json data, one of my favorites deserialization features is the one I'll show you here (using Builder Pattern) because allows you to validate instances when they are being constructed (or make them immutable!). For you this would look like this:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.util.Map;
#JsonDeserialize(builder = Product.Builder.class)
public class Product {
private Integer id;
private String name;
private String standardUrl;
private Product(Builder builder) {
//Here you can make validations for your new instance.
this.id = builder.id;
this.name = builder.name;
//Here you have access to the primaryImage map in case you want to add new properties later.
this.standardUrl = builder.primaryImage.get("standard_url");
}
#Override
public String toString() {
return String.format("id [%d], name [%s], standardUrl [%s].", id, name, standardUrl);
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class Builder {
private Integer id;
private String name;
private Map<String, String> primaryImage;
public Builder withId(Integer id) {
this.id = id;
return this;
}
public Builder withName(String name) {
this.name = name;
return this;
}
#JsonProperty("primary_image")
public Builder withPrimaryImage(Map<String, String> primaryImage) {
this.primaryImage = primaryImage;
return this;
}
public Product build() {
return new Product(this);
}
}
}
To test it I created this class:
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class Test {
public static void main(String[] args) {
String serialized = "{" +
" \"id\": 32," +
" \"name\": \"[Sample] Tomorrow is today, Red printed scarf\"," +
" \"primary_image\": {" +
" \"id\": 247," +
" \"zoom_url\": \"www.site.com/in_123__14581.1393831046.1280.1280.jpg\"," +
" \"thumbnail_url\": \"www.site.com/in_123__14581.1393831046.220.290.jpg\"," +
" \"standard_url\": \"www.site.com/in_123__14581.1393831046.386.513.jpg\"," +
" \"tiny_url\": \"www.site.com/in_123__14581.1393831046.44.58.jpg\"" +
" }" +
" }";
ObjectMapper objectMapper = new ObjectMapper();
try {
Product deserialized = objectMapper.readValue(serialized, Product.class);
System.out.print(deserialized.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
The output is (using the override toString() method in Product:
id [32], name [[Sample] Tomorrow is today, Red printed scarf], standardUrl [www.site.com/in_123__14581.1393831046.386.513.jpg].
There are two ways to get the response you required. For both methods, we are going to use JsonView.
Create two types of JsonView:
public interface JViews {
public static class Public { }
public static class Product extends Public { }
}
First method
#JsonView(JViews.Public.class)
public class Product {
private Integer id;
private String name;
#JsonIgnore
private Image primaryImage;
#JsonView(JViews.Product.class)
public String getStandardUrl{
return this.primaryImage.getStandardUrl();
}
}
Second way
Using Jackson's #JsonView and #JsonUnwrapped together.
#JsonView(JViews.Public.class)
public class Product {
private Integer id;
private String name;
#JsonUnwrapped
private Image primaryImage;
}
public class Image {
private String zoomUrl;
#JsonView(JViews.Product.class)
private String standardUrl;
}
#JsonUnwrapped annotation flattens your nested object into Product object. And JsonView is used to filter accessible fields. In this case, only standardUrl field is accessible for Product view, and the result is expected to be:
{
"id": 32,
"name": "[Sample] Tomorrow is today, Red printed scarf",
"standard_url": "url"
}
If you flatten your nested object without using Views, the result will look like:
{
"id": 32,
"name": "[Sample] Tomorrow is today, Red printed scarf",
"id":1,
"standard_url": "url",
"zoom_url":"",
...
}
Jackson provided #JsonUnwrapped annotation.
See below link:
http://jackson.codehaus.org/1.9.9/javadoc/org/codehaus/jackson/annotate/JsonUnwrapped.html

Converting String to java bean type

I have a string which looks like this and it represents a pojo.
Model [Name=Mobie , location= US, actualTransferDate=null, scanserialCode=234335,1237787, modelNum=MIC 898989 ]
I want bit clearer to reader on the above string. I want to read the user checked checkbox values(represents entire row with the fileds in below pojo) in an jsp page table to another jsp page. So, in the controller i read these checked checkbox rows as bellow.
String[] checkeditems = request.getParameterValues("case");//case represents the entire row
for (String string : checkeditems) {
log.info("row1"+string);// String pasted above in the message
}
From the above it returns as a string Array which i want convert to be as a list object, so that i can easily send this list to next jsp for a view. I feel i am heading to wrong direction and doing some unrelated stuff.
I have a pojo as
public class Model{
private String Name;
private String location;
private String actualTransferDate;
private String scanserialCode;
private String modelNum;
====Getters/Setter======
How i can convert this String to this model object?
you can split the string on ", " and iterate over the result array. With BeanUtils from apache can you fill your new pojo instance.
Example:
public class Model {
private String Name;
private String location;
private String actualTransferDate;
private String scanserialCode;
private String modelNum;
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getActualTransferDate() {
return actualTransferDate;
}
public void setActualTransferDate(String actualTransferDate) {
this.actualTransferDate = actualTransferDate;
}
public String getScanserialCode() {
return scanserialCode;
}
public void setScanserialCode(String scanserialCode) {
this.scanserialCode = scanserialCode;
}
public String getModelNum() {
return modelNum;
}
public void setModelNum(String modelNum) {
this.modelNum = modelNum;
}
#Override
public String toString() {
return "[Name = " + getName() + "location = " +getLocation() + ", actualTransferDate = " + getActualTransferDate() + ", scanserialCode = " + getScanserialCode() + ", modelNum = " + getModelNum() + "]";
}
}
import org.apache.commons.beanutils.BeanUtils;
public class Main {
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException {
String model = new String("Name=Mobie , location= US, actualTransferDate=null, scanserialCode=234335,1237787, modelNum=MIC 898989");
String[] modelValues = model.split(", ");
Model m = new Model();
for (String value : modelValues) {
String[] s = value.split("=");
String fieldName = s[0];
String fieldValue = s[1];
BeanUtils.setProperty(m, fieldName, fieldValue);
}
System.out.println(m.toString());
}
}
Maven dependency:
<dependencies>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>
</dependencies>
If you want it completely dynamic, you can use Reflection.
For example, use a regular expression (Pattern/Matcher) to find the [ ... ] part, use the String before that as a class name (assuming you know the package name) and then do a simple comma/equals-sign split in the [ ... ] part and fill the fields via reflection... Not that hard to do.
You can define a constructor in the Model class which accepts the full string as input. The use StringTokenizer with delimiter as ',' to convert the string to a list of tokens. Then tokenizer each token with '='as the delimiter. This way you will have all the members of Model class tokens which can be used to initialize the values of the member variables.

Categories