Smoothing Java Bean onto a Map

I am stuck when converting Java Bean to Map . There are a lot of resources on the Internet, but, unfortunately, they all consider converting simple beans to Maps. Mine are a little more extensive.

There is a simplified example:

 public class MyBean { private String firstName; private String lastName; private MyHomeAddress homeAddress; private int age; // getters & setters } 

My task is to create a Map<String, Object> , which in this case is true for the following conditions:

 map.containsKey("firstName") map.containsKey("lastName") map.containsKey("homeAddress.street") // street is String map.containsKey("homeAddress.number") // number is int map.containsKey("homeAddress.city") // city is String map.containsKey("homeAddress.zipcode") // zipcode is String map.containsKey("age") 

I tried using Apache Commons BeanUtils . Both BeanUtils#describe(Object) and BeanMap(Object) create a map for which the "deep level" is 1 (I mean that there is only the "homeAddress" key containing the MyHomeAddress object as a value). My method should enter objects deeper and deeper until it encounters a primitive type (or String), then it should stop digging and pasting the key ie "order.customer.contactInfo.home" .

So my question is: how can it be easily implemented (or is there an existing project that would allow me to do this)?

Update

I expanded the Radiodef answer to include collections, map arrays, and enumerations as well:

 private static boolean isValue(Object value) { final Class<?> clazz = value.getClass(); if (value == null || valueClasses.contains(clazz) || Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz) || value.getClass().isArray() || value.getClass().isEnum()) { return true; } return false; } 
+7
java apache-commons javabeans
source share
2 answers

Here is a simple reflexive / recursive example.

You should know that there are some problems with the conversion, as you asked:

  • Card keys must be unique.
  • Java allows classes to call their private fields the same name as a private field that belongs to an inherited class.

This example does not address these issues because I'm not sure how you want to address them (if you do). If your beans inherits from something other than Object , you need to change your idea a bit. This example only considers subclass fields.

In other words, if you have

 public class SubBean extends Bean { 

this example will return only fields from SubBean .

Java allows us to do this crazy:

 package com.company.util; public class Bean { private int value; } package com.company.misc; public class Bean extends com.company.util.Bean { private int value; } 

Not that anyone did this, but it is a problem if you want to use String as keys.

Here is the code:

 import java.lang.reflect.*; import java.util.*; public final class BeanFlattener { private BeanFlattener() {} public static Map<String, Object> deepToMap(Object bean) throws IllegalAccessException { Map<String, Object> map = new LinkedHashMap<>(); putValues(bean, map, null); return map; } private static void putValues( Object bean, Map<String, Object> map, String prefix ) throws IllegalAccessException { Class<?> cls = bean.getClass(); for(Field field : cls.getDeclaredFields()) { field.setAccessible(true); Object value = field.get(bean); String key; if(prefix == null) { key = field.getName(); } else { key = prefix + "." + field.getName(); } if(isValue(value)) { map.put(key, value); } else { putValues(value, map, key); } } } private static final Set<Class<?>> valueClasses = ( Collections.unmodifiableSet(new HashSet<>(Arrays.asList( Object.class, String.class, Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class ))) ); private static boolean isValue(Object value) { return value == null || valueClasses.contains(value.getClass()); } } 
+5
source share

You can always use the Jackson Json Processor . Like this:

 import com.fasterxml.jackson.databind.ObjectMapper; //... ObjectMapper objectMapper = new ObjectMapper(); //... @SuppressWarnings("unchecked") Map<String, Object> map = objectMapper.convertValue(pojo, Map.class); 

where pojo is a Java bean. To control serialization, you can use a few notes in a bean.

You can reuse ObjectMapper.

+1
source share

All Articles