I have a POJO and using object mapper to convert it into a map. I want the map to contain all fields of the POJO in it's entryset even if their value is null. So that entry might be null in the map I am creating.
Map<String, Object> oldDetails = objectMapper.convertValue(oldFields, Map.class);
I've tried using below snippet but that didn't help me.
objectMapper.setDefaultPropertyInclusion(JsonInclude.Value.construct(JsonInclude.Include.ALWAYS, JsonInclude.Include.ALWAYS));
Any other idea that I can use here?
My POJO:
#JsonInclude(NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class BaseFields {
#JsonProperty("number") protected String number;
#JsonProperty("name") protected String name;
#JsonProperty("dob") protected String dob;
}
There are other classes inheriting from BaseFields such as.
#JsonInclude(NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class CustomerBaseFields extends BaseFields {
#JsonProperty("email") protected String email;
}
Now there is method that converts an instance of ChangedBaseFields into a map.
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, true);
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
objectMapper.setDefaultPropertyInclusion(JsonInclude.Value.construct(JsonInclude.Include.ALWAYS, JsonInclude.Include.ALWAYS));
Map<String, Object> oldDetails = objectMapper.convertValue(baseFields, Map.class);
But say if a field like email is null in object, I'm not getting a key email in my map.
Here's simple code demonstrating that your code in fact works without any additional configuration with default ObjectMapper.
package java17test;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
public class Test {
public static void main(String[] args) {
var objectMapper = new ObjectMapper();
var oldFields = new TestClass();
oldFields.setFirstName("first name");
Map<?, ?> oldDetails = objectMapper.convertValue(oldFields, Map.class);
oldDetails.forEach((key, value) -> System.out.println(key + "=" + value));
}
static class TestClass {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
}
Output:
firstName=first name
lastName=null
You probably have an X-Y problem because you mentioned in a comment to vbezhenar's answer that your intention is to compare the fields of two objects of the same type, to find out which fields have changed.
You don't have to use Jackson to do that.
Here's a simple pure Java (no dependencies) way of comparing two objects:
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
public class Utils {
/**
* Compares two objects of the same type, returning the field values that are different.
*
* #param o1 an object
* #param o2 another object
* #return a map of field name to a two-element array of the two different values of o1 and o2
*/
public static <T> Map<String, Object[]> comparePojos(T o1, T o2) throws IllegalAccessException {
Map<String, Object[]> diffs = new LinkedHashMap<>();
for (Field field : o1.getClass().getDeclaredFields()) {
field.setAccessible(true);
Object[] values = {field.get(o1), field.get(o2)};
if (!values[0].equals(values[1]))
diffs.put(field.getName(), values);
}
return diffs;
}
}
class UtilsDemo {
public static void main(String[] args) throws IllegalAccessException {
SamplePojo a = new SamplePojo("alice", 42);
SamplePojo b = new SamplePojo("bob", 42);
Utils.comparePojos(a, b).forEach((k, v) -> System.out.println(k + ": " + Arrays.toString(v)));
}
}
class SamplePojo {
public SamplePojo(String name, int age) {
this.name = name;
this.age = age;
}
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Related
We have two Pojo files.
Person {
String name;
int age;
String address;
String phoneNo;
boolean isMarried;
}
and
OtherPerson {
//mandatory fields are name and age
String name_other;
int age_other;
//other fields
Map<String, Object> otherFields;
}
and a json file which defines the mapping between the fields using name
mappingJson {
"name":"name_other",
"age":"age_other",
"address":"address_other",
"phoneNo":"phoneno_other",
"isMarried":"ismarried_other"
}
Please let me know the best approach to convert Person to OtherPerson. So that the mandatory fields map to name_other and age_other while the other fields should be added to the map(otherFields)
It may be
Person->Person(json)->OtherPerson
Or Person->OtherPerson.
EDIT:
"Use case: We have an API which used to accepts a POJO 'A' but now it needs to accept POJO 'B' as an input argument. This POJO needs to get converted into POJO 'A' which can then be used for persisting into the database. Also POJO 'B' is not under our control"
That's a perfect fit for Jackson Converter! :)
It could work like this:
class OtherPerson {
#JsonProperty("name")
public String name_other;
#JsonProperty("age")
public int age_other;
Map<String, Object> otherFields = new LinkedHashMap<>();;
#JsonAnySetter
public void add(String key, Object value) {
otherFields.put(key, value);
}
}
// ...
Person person = new Person();
person.name = "Avinash";
person.age = 25;
person.address = "Mumbai";
person.phoneNo = "910731";
person.isMarried = true; // :( sorry ladies!
// ...
ObjectMapper mapper = new ObjectMapper();
// If we cannot put #JsonAutoDetect on top of Person.class,
// we need to add handling of non-public fields
// since Person seems to neither have public fields nor setters
mapper.configOverride(Person.class)
.setVisibility(JsonAutoDetect.Value.defaultVisibility()
.withFieldVisibility(JsonAutoDetect.Visibility.NON_PRIVATE));
OtherPerson other = mapper.convertValue(person, OtherPerson.class);
VoilĂ !
I personally would do this without JSON. It's my understanding that some fields in the Map are optional while name and age are mandatory. In the case of the optional content, I would use the Ternary operator to create the person object. This allows you to add some default value if the optional field is not available.
Main
import java.util.HashMap;
import java.util.Map;
/**
*
* #author blj0011
*/
public class JavaApplication30 {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
Map<String, Object> map1 = new HashMap();
map1.put("address", "123 Hello Street");
map1.put("phoneNo", "555-555-5555");
map1.put("isMarried", true);
OtherPerson otherPerson = new OtherPerson("John Doe", 22, map1);
Map<String, Object> map2 = new HashMap();
map2.put("address", "4456 Bye Road");
map2.put("isMarried", false);
OtherPerson otherPerson2 = new OtherPerson("Jane Doe", 21, map2);
Person person1 = new Person(otherPerson.getName_other(), otherPerson.getAge_other(),
otherPerson.getOtherFields().containsKey("address") ? otherPerson.getOtherFields().get("address").toString(): "",
otherPerson.getOtherFields().containsKey("phoneNo") ? otherPerson.getOtherFields().get("phoneNo").toString(): "",
otherPerson.getOtherFields().containsKey("isMarried") ? Boolean.valueOf(otherPerson.getOtherFields().get("isMarried").toString()): false);
System.out.println(person1);
Person person2 = new Person(otherPerson2.getName_other(), otherPerson2.getAge_other(),
otherPerson2.getOtherFields().containsKey("address") ? otherPerson2.getOtherFields().get("address").toString(): "",
otherPerson2.getOtherFields().containsKey("phoneNo") ? otherPerson2.getOtherFields().get("phoneNo").toString(): "",
otherPerson2.getOtherFields().containsKey("isMarried") ? Boolean.valueOf(otherPerson2.getOtherFields().get("isMarried").toString()): false);
System.out.println(person2);
}
}
Person
/**
*
* #author blj0011
*/
public class Person {
private String name;
private int age;
private String address;
private String phoneNo;
private boolean isMarried;
public Person(String name, int age, String address, String phoneNo, boolean isMarried) {
this.name = name;
this.age = age;
this.address = address;
this.phoneNo = phoneNo;
this.isMarried = isMarried;
}
public boolean isIsMarried() {
return isMarried;
}
public void setIsMarried(boolean isMarried) {
this.isMarried = isMarried;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhoneNo() {
return phoneNo;
}
public void setPhoneNo(String phoneNo) {
this.phoneNo = phoneNo;
}
#Override
public String toString() {
return "Person{" + "name=" + name + ", age=" + age + ", address=" + address + ", phoneNo=" + phoneNo + ", isMarried=" + isMarried + '}';
}
}
OtherPerson
/**
*
* #author blj0011
*/
public class OtherPerson {
//mandatory fields are name and age
private String name_other;
private int age_other;
//other fields
private Map<String, Object> otherFields;
public OtherPerson(String name_other, int age_other, Map<String, Object> otherFields) {
this.name_other = name_other;
this.age_other = age_other;
this.otherFields = otherFields;
}
public Map<String, Object> getOtherFields() {
return otherFields;
}
public void setOtherFields(Map<String, Object> otherFields) {
this.otherFields = otherFields;
}
public String getName_other() {
return name_other;
}
public void setName_other(String name_other) {
this.name_other = name_other;
}
public int getAge_other() {
return age_other;
}
public void setAge_other(int age_other) {
this.age_other = age_other;
}
}
Output
Person{name=John Doe, age=22, address=123 Hello Street, phoneNo=555-555-5555, isMarried=true}
Person{name=Jane Doe, age=21, address=4456 Bye Road, phoneNo=, isMarried=false}
As you can see in the output OtherPerson2 did not have a phone number. Empty string was use as the default value.
I am getting an exception while trying to filter and iterate over a Optional using Java 8. I have an object Subject which is being added in an array list and a value of null also.
Problem Statement: I have an ArrayList, I want to iterate it, filter it and then based on that, only print that record which fulfills the condition.
package com.example.app;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class OptionalTest {
public static void main(String[] args) {
System.out.println("getSubject: " + getSubjects());
// print the Subject with the name "Math"
System.out.println("getSubject " + getSubjects().filter((Subject s) -> s.getName().equalsIgnoreCase("Math")));
}
private static Optional getSubjects() {
Subject subject1 = new Subject(1, "Math", (short)2, "");
Subject subject2 = new Subject(2, "Social Science", (short)4, "Social Science");
Subject subject3 = new Subject(3, "English", (short)6, "Literature");
List<Subject> subjects = new ArrayList<>();
Optional<List<Subject>> optional = Optional.of(subjects);
subjects.add(subject1);
subjects.add(subject2);
subjects.add(null);
subjects.add(subject3);
return optional;
}
}
class Subject {
int id;
String name;
short type;
String description;
public Subject(int id, String name, short type, String description) {
this.id = id;
this.name = name;
this.type = type;
this.description = description;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public short getType() {
return type;
}
public void setType(short type) {
this.type = type;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#Override
public String toString() {
return "\nSubject{" + "id=" + id + ", name=" + name + ", type=" + type + ", description=" + description + '}'+"\n";
}
}
Using Optional.filter would be used to filter List instances as per your code snippet. This is not what you want:
Optional.of(getSubjects()).filter(predicate) //filters lists, not subjects in lists
Your intention is probably to use the a list of Subject objects, then filter. It's the filter method of the Stream interface that returns an Optional instance:
I'd change this method:
private static List<Subject> getSubjects(){
Subject subject1 = new Subject(1, "Math", (short)2, "");
Subject subject2 = new Subject(2, "Social Science", (short)4, "Social Science");
Subject subject3 = new Subject(3, "English", (short)6, "Literature");
List<Subject> subjects = new ArrayList<>();
subjects.add(subject1);
subjects.add(subject2);
subjects.add(null);
subjects.add(subject3);
return subjects;
}
And then use it as follows:
Optional<Subject> filtered = getSubjects()
.stream().filter(s -> s.getName().equalsIgnoreCase("Math"))
//Find first is just one of the many Stream methods
//returning an optional
//It's correct to use it in this case because you know
//only one value is expected to match the filter predicate.
.findFirst();
In fact, if you expect more than one subject to match your filter, you should collect, instead, instead of picking one. In this case, you don't need an optional:
List<Subject> mathSubjects = getSubjects()
.stream().filter((s -> s.getName().equalsIgnoreCase("Math")))
.collect(Collectors.toList());
You can do it very simply using lambda expression, I am providing you a sample so that you can modify according to your need.
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class Java8Optional {
public static void main(String[] args) {
List<Employee> employeeList = new ArrayList<>();
employeeList.add(new Employee(1, "syed"));
employeeList.add(new Employee(2, "az"));
employeeList.add(null);
employeeList.add(new Employee(4, "Rof"));
employeeList.forEach(n -> Optional.ofNullable(n).ifPresent(e -> System.out.println("Employee ID="+e.employeeId+"\tEmployee Name="+e.employeeName)));
}
static class Employee {
Integer employeeId;
String employeeName;
public Integer getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public Employee(Integer employeeId, String employeeName) {
super();
this.employeeId = employeeId;
this.employeeName = employeeName;
}
}
}
Can't get string from json object. I get
Gson gson = new Gson();
Map<String, Object> resultMap = new HashMap<String, Object>();
resultMap = (Map<String, Object>)gson.fromJson(result, resultMap.getClass());
if (handler!=null) {
handler.responseReceived(new HashMap(resultMap),_queryName);
}
and in another class I need to get Elements of JsonObject
public void responseReceived(HashMap<String, Object> resp, String action) {
Log.e("!!!", action);
Log.e("!!!", "--------");
Log.e("!!!", resp.toString());
//Json Parsed, need just to get elements of jsonobject
}
Now how to get String, elements from HashMap<String,Object> resp?
We can print the values with your current code.
Use map.keySet().toString() to print the String values from map
or if you want to print the Objects use map.values().toString()
Checkout the example below for more details,
Created a small object class Employee
package com.zack.demo;
public class Employee {
private String name;
private int age;
private String gender;
public Employee(String name, int age, String gender)
{
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
The GSONTest class has different input and output formats which can print values from Map
package com.zack.demo;
import java.util.HashMap;
import java.util.Map;
import com.google.gson.Gson;
public class GsonTest {
public static void main(String[] args) {
Gson gson = new Gson();
Map<String, Object> resultMap = new HashMap<String, Object>();
Employee o1 = new Employee("Test1", 10, "Male");
resultMap.put("T1", o1);
Employee o2 = new Employee("Test2", 20, "Male");
resultMap.put("T2", o2);
String result = gson.toJson(resultMap, resultMap.getClass());
System.out.println(result);
Map<String, Object> resultOutputMap = new HashMap<String, Object>();
resultOutputMap = (Map<String, Object>) gson.fromJson(result,
resultOutputMap.getClass());
System.out.println("Complete Map: "+ resultOutputMap.toString());
System.out.println("Values: "+resultOutputMap.values().toString());
System.out.println("Keys: "+resultOutputMap.keySet().toString());
}
}
Output from the above class
{"T1":{"name":"Test1","age":10,"gender":"Male"},"T2":{"name":"Test2","age":20,"gender":"Male"}}
Complete Map: {T1={name=Test1, age=10.0, gender=Male}, T2={name=Test2, age=20.0, gender=Male}}
Values: [{name=Test1, age=10.0, gender=Male}, {name=Test2, age=20.0, gender=Male}]
Keys: [T1, T2]
I have a enum representing severity level
public enum Severity {
HIGH("H"), MEDIUM("M"), LOW("L");
}
Person one = new Person();
one.setSeverity(Severity.HIGH);
other fields ...
Person two = new Person();
two.setSeverity(Severity.LOW);
.....
Person three = new Person();
three.setSeverity(Severity.HIGH);
List<Person> persons = Lists.newArrayList();
persons.add(one);
persons.add(two);
persons.add(three);
I would like to sort persons list to sort by severity field (i.e HIGH,MEDIUM then LOW).
My expected results after sorting the persons list should be in the order of HIGH,HIGH,LOW ?
can i know how i can achieve this ?
note : I am making use of com.google.common.collect
Try below code
Create an ENUM
package com.rais;
public enum Severity {
HIGH("H"), MEDIUM("M"), LOW("L");
private final String value;
private Severity(String value) {
this.value = value;
}
}
Now Create Person class according to your requirement eg.
package com.rais;
public class Person {
private Severity severity;
private String name;
public Person(Severity severity, String name) {
super();
this.severity = severity;
this.name = name;
}
public Severity getSeverity() {
return severity;
}
public void setSeverity(Severity severity) {
this.severity = severity;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Finally create a Test Client and apply below logic.
package com.rais;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class TestClient {
public static void main(String[] args) {
Person one = new Person(Severity.HIGH, "shayam");
Person two = new Person(Severity.MEDIUM, "mohan");
Person three = new Person(Severity.LOW, "radha");
Person four = new Person(Severity.HIGH, "rakesh");
Person five = new Person(Severity.MEDIUM, "kailash");
Person six = new Person(Severity.LOW, "rais");
Person seven = new Person(Severity.LOW, "abhishek");
List<Person> persons = new ArrayList<Person>();
persons.add(one);
persons.add(two);
persons.add(three);
persons.add(four);
persons.add(five);
persons.add(six);
persons.add(seven);
Collections.sort(persons, new Comparator<Person>() {
#Override
public int compare(Person person1, Person person2) {
if(person1.getSeverity()==person2.getSeverity())
{
return person1.getName().compareTo(person2.getName());
}
else{
return person1.getSeverity().compareTo(person2.getSeverity());
}
}
});
for (Person person : persons) {
System.out.println(person.getName()+" "+ person.getSeverity());
}
}
}
I am sure you will get below output.
rakesh HIGH
shayam HIGH
kailash MEDIUM
mohan MEDIUM
abhishek LOW
radha LOW
rais LOW
Use Comparable or comparator and then apply
Collection.sort().
if using comparable interface you have to implement compareTo method and
Collection.sort(<list>)
and if using comparator then you have to override compareTo method and
Collection.sort(<list>, <comparator>)
and when to use comparatot or comparable read link:
http://iandjava.blogspot.in/2012/10/comparable-and-comparator.html
If you are using Google Collections, upgrade to Google Guava. Use its ComparisonChain class. Are you sure you want HIGH, MEDIUM, LOW in that order? The reverse fits Java comparisons better.
How do Persons have a severity level? Perhaps your class deserves a better name.
I would make Person implement Comparable, which makes the sorting code very simple and brief.
Note that enums are implicitly Comparable:
public enum Severity {
HIGH("H"), MEDIUM("M"), LOW("L");
private final String code;
private Severity(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}
public class Person implements Comparable<Person> {
private Severity severity;
private final String name;
public Person(Severity severity, String name) {
this.severity = severity;
this.name = name;
}
public Severity getSeverity() {
return severity;
}
public void setSeverity(Severity severity) {
this.severity = severity;
}
public String getName() {
return name;
}
#Override
public int compareTo(Person person) {
return severity == person.severity ? name.compareTo(person.name)
: severity.compareTo(person.severity);
}
#Override
public String toString() {
return name + "(" + severity +")";
}
}
Now some test code:
Person one = new Person(Severity.HIGH, "one");
Person two = new Person(Severity.LOW, "two");
Person three = new Person(Severity.HIGH, "three");
List<Person> persons = new ArrayList<Person>();
persons.add(one);
persons.add(two);
persons.add(three);
Collections.sort(persons);
System.out.println(persons);
Output:
[one(HIGH), three(HIGH), two(LOW)]
I start using BeanUtils to convert a Properties files to a JavaBean.
Using BeanUtils.populate, I'm able to do this nicely. But I can achieve the retro conversion from the JavaBean to the Map correctly (only simple values are store).
See this sample based on the Employee Class form the BeanUtils documentation.
import org.apache.commons.beanutils.BeanUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class Employee {
private Map<String, Address> addressMap = new HashMap<String, Address>();
private List<Employee> subordinateList = new ArrayList<Employee>();
private String firstName;
private String lastName;
public Address getAddress(String type) {
if (!addressMap.containsKey(type)) {
addressMap.put(type, new Address());
}
return addressMap.get(type);
}
public void setAddress(String type, Address address) {
addressMap.put(type, address);
}
public Employee getSubordinate(int index) {
if (subordinateList.size() <= index) {
subordinateList.add(new Employee());
}
return subordinateList.get(index);
}
public void setSubordinate(int index, Employee subordinate) {
subordinateList.add(index, subordinate);
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public class Address {
private String city;
private String street;
private int number;
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
public static void main(String[] args) throws Exception {
Map<String, Object> map = new TreeMap<String, Object>();
map.put("firstName", "MyfirstName");
map.put("lastName", "MylastName");
map.put("address(pro).city", "MyProCity");
map.put("address(pro).street", "MyProStreet");
map.put("address(pro).number", 22);
map.put("subordinate[1].firstName", "Sub1FirstName");
map.put("subordinate[1].lastName", "Sub1LastName");
map.put("address(perso).city", "MyPersoCity");
map.put("address(perso).street", "MyPersoStreet");
map.put("address(perso).number", 2);
map.put("subordinate[0].firstName", "Sub0FirstName");
map.put("subordinate[0].lastName", "Sub0LastName");
Employee employee = new Employee();
BeanUtils.populate(employee, map);
System.out.println(employee.getFirstName());
System.out.println(employee.getLastName());
System.out.println(employee.getAddress("pro").city);
System.out.println(employee.getAddress("pro").street);
System.out.println(employee.getAddress("pro").number);
System.out.println(employee.getAddress("perso").city);
System.out.println(employee.getAddress("perso").street);
System.out.println(employee.getAddress("perso").number);
System.out.println(employee.getSubordinate(0).firstName);
System.out.println(employee.getSubordinate(0).lastName);
System.out.println(employee.getSubordinate(1).firstName);
System.out.println(employee.getSubordinate(1).lastName);
Map<String, Object> map2 = BeanUtils.describe(employee);
System.out.println("----------------");
System.out.println(map2);
}
}
The result :
MyfirstName
MylastName
MyProCity
MyProStreet
22
MyPersoCity
MyPersoStreet
2
Sub0FirstName
Sub0LastName
Sub1FirstName
Sub1LastName
----------------
{lastName=MylastName, class=class Employee, firstName=MyfirstName}
What am I missing so that the map2 stores actually keys like "address(pro).city" or "subordinate[1].firstName" using the BeanUtils.describe method ?
Finally I find a way to solve this. First of all, I need to retrieve every nested propertyName based on my current bean instance, and this recursively. So I've wrote a simple method to do this :
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class MyPropertyUtils {
public List<String> listNestedPropertyName(Object objectSource) throws Exception {
List<String> nodeNameList = new ArrayList<String>();
if (Serializable.class.isAssignableFrom(objectSource.getClass())) {
nodeNameList.add(objectSource.toString());
return nodeNameList;
}
PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(objectSource.getClass());
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
Method method = propertyDescriptor.getReadMethod();
if (propertyDescriptor.getReadMethod() == null) {
continue;
}
if (method.getGenericParameterTypes().length > 0) {
continue;
}
String name = propertyDescriptor.getName();
Object value = method.invoke(objectSource);
if (value == null) {
continue;
}
if (Map.class.isAssignableFrom(value.getClass())) { // Mapped name
Map map = ((Map) value);
name = StringUtils.substringBeforeLast(name, "Map");
for (Object key : map.keySet()) {
String mappedName = name + "(" + key.toString() + ")";
List<String> nestedNames = listNestedPropertyName(map.get(key));
for (String nestedName : nestedNames) {
nodeNameList.add(mappedName + "." + nestedName);
}
}
} else if (List.class.isAssignableFrom(value.getClass())) { // Indexed name
List list = ((List) value);
name = StringUtils.substringBeforeLast(name, "List");
for (int i = 0; i < list.size(); i++) {
String indexedName = name + "[" + i + "]";
List<String> nestedNames = listNestedPropertyName(list.get(i));
for (String nestedName : nestedNames) {
nodeNameList.add(indexedName + "." + nestedName);
}
}
} else if (Serializable.class.isAssignableFrom(value.getClass())) { // Simple Value
nodeNameList.add(name);
} else { // Nested Value
List<String> nestedNames = listNestedPropertyName(value);
for (String nestedName : nestedNames) {
nodeNameList.add(name + "." + nestedName);
}
}
}
return nodeNameList;
}
}
And then I iterate other those names to retrieve property value and then set them in the Map.
Map<String, Object> map = new HashMap<String, Object>();
MyPropertyUtils myPropertyUtils = new MyPropertyUtils();
List<String> names = myPropertyUtils.listNestedPropertyName(employee);
for (String name : names) {
map.put(name, PropertyUtils.getNestedProperty(employee, name));
}
This work well for my use case. I simply had add an accessor in my source object for accessing the Map or the List with a conventional name (propertyName + "Map" or "List").
Maybe this could interest someone. Anyway, if there is something more obvious to do it, let me know.