BeanUtils converting java.util.Map to nested bean

Skynet picture Skynet · Feb 12, 2014 · Viewed 20.4k times · Source

I have a Java bean which has a field which in turn is another bean

public class BeanOne {
   private String fieldOne;
   private BeanTwo fieldTwo;

   public String getFieldOne() {return this.fieldOne;}  
   public void setFieldOne(String fieldOne){this.fieldOne = fieldOne}

   public BeanTwo getFieldTwo() {return this.fieldTwo;}  
   public void setFieldTwo(BeanTwo fieldTwo){this.fieldTwo = fieldTwo}
}

public class BeanTwo {
   private String fieldOne;

   public String getFieldOne() {return this.fieldOne;}  
   public void setFieldOne(String fieldOne){this.fieldOne = fieldOne}
}

I am trying to pass a map to BeanUtils to try and convert the following map into BeanOne

Map beanOneMap = new HashMap<String, Object>();
beanOneMap.put("fieldOne", "fieldOneValue");
Map beanTwoMap = new HashMap<String, Object>();
beanTwoMap.put("fieldOne", "fieldOneValue");
beanOneMap.put("fieldTwo", beanTwoMap);

BeanOne beanOne = new BeanOne();
BeanUtils.populate(beanOne, beanOneMap);

But it throws an error saying - Cannot invoke BeanOne.setFieldTwo on bean class 'class Bean' - argument type mismatch - had objects of type "java.util.HashMap" but expected signature "BeanTwo"

How can I use BeanUtils to correctly populate the inner bean ?

Answer

Gaetan picture Gaetan · Nov 26, 2015

You should use Spring's BeanWrapper class. It supports nested properties, and optionally create inner beans for you:

BeanOne one = new BeanOne();
BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(one);
wrapper.setAutoGrowNestedPaths(true);

Map<String, Object> map = new HashMap<>();
map.put("fieldOne", "fieldOneValue");
map.put("fieldTwo.fieldOne", "fieldOneValue");

wrapper.setPropertyValues(map);

assertEquals("fieldOneValue", one.getFieldOne());
BeanTwo two = one.getFieldTwo();
assertNotNull(two);
assertEquals("fieldOneValue", two.getFieldOne();

The killer feature of auto-creating inner beans is achieved thanks to wrapper.setAutoGrowNestedPaths(true). The default value is false, which means you will get a NullValueInNestedPathException if an element in the property path is null.