Convert java.util.Set of <key,value> object to simple pojo object - java

Getting a java.util.Set of objects which has only key-value pairs as input like below
public class KeyValueObject {
String key;
String value;
// good old constructor, setters and getters
}
Input Object:
java.util.Set inputObject;
KeyValuePair pair1 = new KeyValuePair("Name":"John");
KeyValuePair pair2 = new KeyValuePair("Age":"28");
KeyValuePair pair3 = new KeyValuePair("Location":"Cincinnati");
inputObject.add(pair1);
inputObject.add(pair2);
inputObject.add(pair3);
With the "inputObject" as the request coming in, how to convert this to a simple POJO object which has all the keys described above as individual parameters like below:
Public class SimplePojoObject {
private String name;
private String age;
private String location;
// Good old setters and getters
}
The incoming object has around 52 objects and that is why the manual way of mapping is not the right way to solve this issue. Please suggest on any possible way of mapping this data

You can do it like this:
Convert Set<KeyValueObject> to a JsonNode (or Map<String, String>) object.
Convert the generated JsonNode (or Map<String, String>) object to SimplePojoObject using ObjectMapper (you can also use Gson library instead of ObjectMapper)
If you already have a Map<String, String> object instead of Set<KeyValueObject> then you can do it in just one line:
SimplePojoObject simplePojoObject = new ObjectMapper().convertValue(map, SimplePojoObject.class);

The easiest way is to write a small method that calls the setters:
public SimplePojoObject buildSimplePojoObject(Set<KeyValuePair> properties) {
SimplePojoObject result = new SimplePojoObject();
for (KeyValuePair prop : properties) {
switch (prop.getKey()) {
case "Name":
result.setName(prop.getValue());
break;
case "Age":
result.setAge(prop.getValue());
break;
case "Location":
result.setLocation(prop.getValue());
break;
default:
// Throw an exception or ignore it.
throw new IllegalArgumentException("Unknown property "+ prop.getKey());
}
}
return result;
}
But if you want to do that dynamically, you certainly could:
public SimplePojoObject buildSimplePojoObject(Set<KeyValuePair> properties) {
SimplePojoObject result = new SimplePojoObject();
Lookup l = MethodHandles.publicLookup();
MethodType mt = MethodType.methodType(void.class, String.class);
for (KeyValuePair prop : properties) {
MethodHandle mh = l.findVirtual(SimplePojoObject.class, "set" + prop.getKey());
try {
mh.invokeExact(result, prop.getValue());
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable t) {
// MethodHandle.invokeExact is declared to throw Throwable, so we have to catch it.
throw new RuntimeException(e);
}
}
return result;
}

Related

Object in Object to HashMap with reflection

Project:
I am currently using a tool called Netuno to develop an api, so to do the export of objects to json it is necessary to do the transformation of the object to HashMap, for this a method was developed in a parent class to export the object.
The method:
public Map<String, Object> export() {
Object obj = this;
Map<String, Object> map = new HashMap<>();
for (Field field : obj.getClass().getDeclaredFields()) {
field.setAccessible(true);
try {
map.put(field.getName(), field.get(obj));
} catch (Exception e) {
//todo e
}
}
return map;
}
This works, but only for simple objects.
My problem:
If the object has complex objects inside my method it has no ability to export them to HashMap either.
Example structure:
public abstract class Master {
public Map < String, Object >
export () {
Object obj = this;
Map < String, Object > map = new HashMap < > ();
for (Field field: obj.getClass().getDeclaredFields()) {
field.setAccessible(true);
try {
map.put(field.getName(), field.get(obj));
} catch (Exception e) {
//todo e
}
}
return map;
}
}
public class Foo extends Master {
private int a;
private int b;
private String c;
private Bar bar;
//...
}
public class Bar extends Master {
private int q;
private int w;
private String e;
//...
}
I use it this way:
return new Bar(/*data*/).export();
Output:
{
"a": 2,
"b": 5,
"c": "abc",
"bar": "myproject.myPackage.Bar#XXXXX"
}
Expected Output:
{
"a": 2,
"b": 5,
"c": "abc",
"bar": {
"q": 10,
"w": 15,
"e": "it works"
}
}
You need to decide if a value should me put plain in the map or recursively. Therefore you can use a simple method like this:
private boolean isSimpleType(Class<?> type) {
return type.isPrimitive() ||
Boolean.class == type ||
Character.class == type ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Enum.class.isAssignableFrom(type);
}
This method returns true for all primitive types or the Wrapper objects, Strings and Enums. You can simply adjust that methods to fit your needs. Look here for more details on how to determine if a class is simple.
Now you can use to transform an object:
public Map<String, Object> convert(Object object) {
Map<String, Object> map = new HashMap<>();
for (Field field : object.getClass().getDeclaredFields()) {
field.setAccessible(true);
try {
if (isSimpleType(field.getType())) {
map.put(field.getName(), field.get(object));
} else {
map.put(field.getName(), convert(field.get(object)));
}
} catch (Exception e) {
e.printStackTrace();
}
}
return map;
}
You also can adjust this to do fir your export method.
The result for your example will be this:
{a=2, b=5, bar={q=10, e=it works, w=15}, c=abc}
Beside that I would strongly recommend using an library (like Jackson), which already does stuff like this by perfection. Here is the same solution using Jackson:
Map result = new ObjectMapper().convertValue(object, Map.class);
Or if you need type safety:
ObjectMapper mapper = new ObjectMapper();
MapType mapType = mapper.getTypeFactory().constructMapType(Map.class, String.class, Object.class);
Map<String, Object> result = mapper.convertValue(value, mapType);

Update the JSON element to NULL if it has a specific value in it

I have a JSON which looks like,
{
"person": {
"name":"Sam",
"surname":"ngonma"
},
"car": {
"make":"toyota",
"model":"yaris"
}
}
I am writing this to Amazon SQS with the below lines,
ObjectMapper mObjectMapper = new ObjectMapper();
sqsExtended.sendMessage(new SendMessageRequest(awsSQSUrl, mObjectMapper.writeValueAsString(claim)));
I have a separate array of values, if the JSON has its value in that array I have to write the field as null.
If my string array is ["Sam", "Toyota"] my final JSON should look like this,
{
"person": {
"name":null,
"surname":"ngonma"
},
"car": {
"make":null,
"model":"yaris"
}
}
The string array is externalized. It may have additional values in future too. Could someone suggest me a good link or idea to address this ?
The most flexible way I could come up with is to use Jackson's JsonAnyGetter annotation. It allows you to provide Jackson with a Map representation of the state of your pojo. filtering values from a Map can be done in iterative way. filtering values from a Map that contains Maps can be done in recursive way.
Here is a solution I built from provided question
public class Claim {
Map<String, Object> properties = new HashMap<>();
public Claim() {
// may be populated from instance variables
Map<String, String> person = new HashMap<>();
person.put("name", "Sam");
person.put("surname", "ngonma");
properties.put("person", person);
Map<String, String> car = new HashMap<>();
car.put("make", "Toyota");
car.put("model", "yaris");
properties.put("car", car);
}
// nullify map values based on provided array
public void filterProperties (String[] nullifyValues) {
filterProperties(properties, nullifyValues);
}
// nullify map values of provided map based on provided array
#SuppressWarnings("unchecked")
private void filterProperties (Map<String, Object> properties, String[] nullifyValues) {
// iterate all String-typed values
// if value found in array arg, nullify it
// (we iterate on keys so that we can put a new value)
properties.keySet().stream()
.filter(key -> properties.get(key) instanceof String)
.filter(key -> Arrays.asList(nullifyValues).contains(properties.get(key)))
.forEach(key -> properties.put(key, null));
// iterate all Map-typed values
// call this method on value
properties.values().stream()
.filter(value -> value instanceof Map)
.forEach(value -> filterProperties((Map<String, Object>)value, nullifyValues));
}
// provide jackson with Map of all properties
#JsonAnyGetter
public Map<String, Object> getProperties() {
return properties;
}
}
test method
public static void main(String[] args) {
try {
ObjectMapper mapper = new ObjectMapper();
Claim claim = new Claim();
claim.filterProperties(new String[]{"Sam", "Toyota"});
System.out.println(mapper.writeValueAsString(claim));
} catch (Exception e) {
e.printStackTrace();
}
}
output
{"car":{"model":"yaris","make":null},"person":{"surname":"ngonma","name":null}}

JSON HashMap deserialization

I want to deserialize this JSON "{\"m\":{\"Test\":{\"nombre\":\"jose\",\"apellidos\":\"jose\",\"edad\":30}}}" in to PersonaContainer.
public class Persona {
private String nombre;
private String apellidos;
private int edad;
... getters and setters;
}
public class PersonaContainer {
private Map m = new HashMap<String,Persona>();
public Map getM() {
return m;
}
public void setM(Map m) {
this.m = m;
}
}
Then I, create an Object of persona and put it inside persona container with the next code
public class MyJSONTest {
public static void main(String args[]) {
ObjectMapper mapper = new ObjectMapper();
Map m = new HashMap<String,persona>();
Persona p = new Persona();
p.setNombre("jose");
p.setApellidos("jose");
p.setEdad(30);
m.put("Test", p);
PersonaContainer per = new PersonaContainer();
per.setM(m);
//convert Map to json string
try {
System.out.println(mapper.writeValueAsString(per));
} catch (IOException e) {
e.printStackTrace();
}
// convert json to Map
String json = "{\"m\":{\"Test\":{\"nombre\":\"jose\",\"apellidos\":\"jose\",\"edad\":30}}}";
try {
PersonaContainer pers = mapper.readValue(json, PersonaContainer.class);
Persona per1 = (Persona) pers.getM().get("Test");
System.out.println(per1.getNombre());
} catch (Exception e) {
e.printStackTrace();
}
}
}
After the serialization, I use ObjectMapper to get deserialize JSON in to a PersonaContainer Object, but when I try to obtain "Test" from HashMap "m" and cast it to Person Object i get this error:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to persona
at MyJSONTest.main(MyJSONTest.java:52)
any advice please?
Given only
private Map m = ...;
Jackson has no idea what types you expect for the Map's keys and values. It therefore uses its defaults. For regular objects, it uses LinkedHashMap. In other words, it will deserialize
{"nombre":"jose","apellidos":"jose","edad":30}
into a LinkedHashMap instance. When you try to use it as a Persona, it fails with the ClassCastException.
Instead, don't use raw types. Declare the Map with proper generic arguments.
private Map<String, Persona> m = ...;
Note that Spring will deserialize the corresponding JSON (m) into a LinkedHashMap, not a HashMap. If you want a HashMap, declare the field with the type HashMap.

Convert POJO to Map that works with GWT

The idea is to have a POJO like:
class MyBean {
long id;
int count;
public void setCount(int count){
this.count = count;
}
}
Now, I need that the count be stored automatically as:
put("count", count);
or simply put, put("fieldname", fieldvalue);
Is there a library that can be used for this purpose that MyBean can exetnd to? I can easily do a copy constructor or something, however, the point here is automation and besides there are so many models in my app that will be having this Map stored POJO values...
You could create a simple PropertyMapGenerator using Apache Commons BeanUtils' PropertyUtils
public class PropertyMapGenerator {
public static Map<String, Object> getPropertyMap(Object object) {
HashMap<String, Object> propertyMap = new HashMap<>();
// retrieve descriptors for all properties
PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(object);
for (PropertyDescriptor descriptor : descriptors) {
// check if there is a reader method for this property i.e. if it can be accessed
if (descriptor.getReadMethod() != null) {
String name = descriptor.getName();
try {
propertyMap.put(name, PropertyUtils.getProperty(object, name));
} catch (Exception e) {
// handle this properly
e.printStackTrace();
}
}
}
return propertyMap;
}
}
Now you can simply pass your POJOs to this generator:
Map<String, Object> propertyMap = PropertyMapGenerator.getPropertyMap(myBean);

Recursive BeanUtils.describe()

Is there a version of BeanUtils.describe(customer) that recursively calls the describe() method on the complex attributes of 'customer'.
class Customer {
String id;
Address address;
}
Here, I would like the describe method to retrieve the contents of the address attribute as well.
Currently, all I have can see the name of the class as follows:
{id=123, address=com.test.entities.Address#2a340e}
Funny, I would like the describe method to retrieve the contents of nested attributes as well, I don't understand why it doesn't. I went ahead and rolled my own, though. Here it is, you can just call:
Map<String,String> beanMap = BeanUtils.recursiveDescribe(customer);
A couple of caveats.
I'm wasn't sure how commons BeanUtils formatted attributes in collections, so i went with "attribute[index]".
I'm wasn't sure how it formatted attributes in maps, so i went with "attribute[key]".
For name collisions the precedence is this: First properties are loaded from the fields of super classes, then the class, then from the getter methods.
I haven't analyzed the performance of this method. If you have objects with large collections of objects that also contain collections, you might have some issues.
This is alpha code, not garunteed to be bug free.
I am assuming that you have the latest version of commons beanutils
Also, fyi, this is roughly taken from a project I've been working on called, affectionately, java in jails so you could just download it and then run:
Map<String, String[]> beanMap = new SimpleMapper().toMap(customer);
Though, you'll notice that it returns a String[], instead of a String, which may not work for your needs. Anyway, the below code should work, so have at it!
public class BeanUtils {
public static Map<String, String> recursiveDescribe(Object object) {
Set cache = new HashSet();
return recursiveDescribe(object, null, cache);
}
private static Map<String, String> recursiveDescribe(Object object, String prefix, Set cache) {
if (object == null || cache.contains(object)) return Collections.EMPTY_MAP;
cache.add(object);
prefix = (prefix != null) ? prefix + "." : "";
Map<String, String> beanMap = new TreeMap<String, String>();
Map<String, Object> properties = getProperties(object);
for (String property : properties.keySet()) {
Object value = properties.get(property);
try {
if (value == null) {
//ignore nulls
} else if (Collection.class.isAssignableFrom(value.getClass())) {
beanMap.putAll(convertAll((Collection) value, prefix + property, cache));
} else if (value.getClass().isArray()) {
beanMap.putAll(convertAll(Arrays.asList((Object[]) value), prefix + property, cache));
} else if (Map.class.isAssignableFrom(value.getClass())) {
beanMap.putAll(convertMap((Map) value, prefix + property, cache));
} else {
beanMap.putAll(convertObject(value, prefix + property, cache));
}
} catch (Exception e) {
e.printStackTrace();
}
}
return beanMap;
}
private static Map<String, Object> getProperties(Object object) {
Map<String, Object> propertyMap = getFields(object);
//getters take precedence in case of any name collisions
propertyMap.putAll(getGetterMethods(object));
return propertyMap;
}
private static Map<String, Object> getGetterMethods(Object object) {
Map<String, Object> result = new HashMap<String, Object>();
BeanInfo info;
try {
info = Introspector.getBeanInfo(object.getClass());
for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
Method reader = pd.getReadMethod();
if (reader != null) {
String name = pd.getName();
if (!"class".equals(name)) {
try {
Object value = reader.invoke(object);
result.put(name, value);
} catch (Exception e) {
//you can choose to do something here
}
}
}
}
} catch (IntrospectionException e) {
//you can choose to do something here
} finally {
return result;
}
}
private static Map<String, Object> getFields(Object object) {
return getFields(object, object.getClass());
}
private static Map<String, Object> getFields(Object object, Class<?> classType) {
Map<String, Object> result = new HashMap<String, Object>();
Class superClass = classType.getSuperclass();
if (superClass != null) result.putAll(getFields(object, superClass));
//get public fields only
Field[] fields = classType.getFields();
for (Field field : fields) {
try {
result.put(field.getName(), field.get(object));
} catch (IllegalAccessException e) {
//you can choose to do something here
}
}
return result;
}
private static Map<String, String> convertAll(Collection<Object> values, String key, Set cache) {
Map<String, String> valuesMap = new HashMap<String, String>();
Object[] valArray = values.toArray();
for (int i = 0; i < valArray.length; i++) {
Object value = valArray[i];
if (value != null) valuesMap.putAll(convertObject(value, key + "[" + i + "]", cache));
}
return valuesMap;
}
private static Map<String, String> convertMap(Map<Object, Object> values, String key, Set cache) {
Map<String, String> valuesMap = new HashMap<String, String>();
for (Object thisKey : values.keySet()) {
Object value = values.get(thisKey);
if (value != null) valuesMap.putAll(convertObject(value, key + "[" + thisKey + "]", cache));
}
return valuesMap;
}
private static ConvertUtilsBean converter = BeanUtilsBean.getInstance().getConvertUtils();
private static Map<String, String> convertObject(Object value, String key, Set cache) {
//if this type has a registered converted, then get the string and return
if (converter.lookup(value.getClass()) != null) {
String stringValue = converter.convert(value);
Map<String, String> valueMap = new HashMap<String, String>();
valueMap.put(key, stringValue);
return valueMap;
} else {
//otherwise, treat it as a nested bean that needs to be described itself
return recursiveDescribe(value, key, cache);
}
}
}
The challenge (or show stopper) is problem that we have to deal with an object graph instead of a simple tree. A graph may contain cycles and that requires to develop some custom rules or requirements for the stop criteria inside the recursive algorithm.
Have a look at a dead simple bean (a tree structure, getters are assumed but not shown):
public class Node {
private Node parent;
private Node left;
private Node right;
}
and initialize it like this:
root
/ \
A B
Now call a describe on root. A non-recursive call would result in
{parent=null, left=A, right=B}
A recursive call instead would do a
1: describe(root) =>
2: {parent=describe(null), left=describe(A), right=describe(B)} =>
3: {parent=null,
{A.parent=describe(root), A.left=describe(null), A.right= describe(null)}
{B.parent=describe(root), B.left=describe(null), B.right= describe(null)}}
and run into a StackOverflowError because describe is called with objects root, A and B over and over again.
One solution for a custom implementation could be to remember all objects that have been described so far (record those instances in a set, stop if set.contains(bean) return true) and store some kind of link in your result object.
You can simple use from the same commom-beanutils:
Map<String, Object> result = PropertyUtils.describe(obj);
Return the entire set of properties for which the specified bean provides a read method.

Categories