20

Is there a library that can do the following?:

Given an Object and a HashMap, it enumerates the keys of the Hashmap and looks up the setters for these keys in the Object and sets the associated values. Something looking like that:

public Object setData(Object object, HashMap<String, Object> fields) {
   for (Entry<String, Object> entry : fields.entrySet()) {
      Method m = object.getClass().getMethod("set" + entry.getKey(), entry.getValue().getClass());
      if (m != null) {
         m.invoke(object, entry.getValue());
      }
   }
   return object;
}

The task looks simple at the first look but there are some nuances that I hope someone has already taken care of. As you know, reinventing the wheel (the good wheel) is a bad approach.

pmr
  • 58,701
  • 10
  • 113
  • 156
forker
  • 2,609
  • 4
  • 23
  • 24

5 Answers5

21

Look at Apache Commons BeanUtils

org.apache.commons.beanutils.BeanUtils.populate(Object bean, Map properties)

Javadoc:
Populate the JavaBeans properties of the specified bean, based on the specified name/value pairs. This method uses Java reflection APIs to identify corresponding "property setter" method names, and deals with setter arguments of type String, boolean, int, long, float, and double.

e-zinc
  • 4,491
  • 2
  • 20
  • 17
  • 2
    The caveat "only deals with setter arguments of type String, boolean, int, long, flat and double" is VERY worth noting, for that reason I believe that the below answer (but using BeanUtils.copyProperty() rather than BeanUtils.setProperty()) is the better alternative. – fragorl Sep 03 '15 at 02:26
  • Be aware though, populate method sets default values to some of the members if the respective value inside that map is null. E.g. if . the object has Integer (Wrapper object) field and if its value inside map is null then it gets set to 0. If you want to copy the exact value then try iterating over map and use BeanUtils.copyProperty method on each field. By default populate uses setProperty method. – omjego Jan 09 '19 at 11:26
  • 2
    Be careful if you use lombok! This solution will not work properly If you have generated acessors with chain = true (@Accessors(chain=true). Because classes which are used in BeanUtils need voidness of setters. Look at this question for more information: https://stackoverflow.com/questions/22743765/beanutils-not-works-for-chain-setter – HereAndBeyond Jul 30 '19 at 08:54
7

Better use BeanUtils class:

public Object setData(Object object, HashMap<String, Object> fields) {
   for(Entry<String, Object> entry : fields.entrySet()) {
      BeanUtils.setProperty(object, entry.getKey(), entry.getValue());
   }
   return object;
}
Wojciech Owczarczyk
  • 5,595
  • 2
  • 33
  • 55
  • Thanks @Wojtek O., with your hint I've found the populate method right before e-zinc answered with it! – forker Jan 17 '12 at 14:23
3

I have a BeanAsMap class that I wrote a long time ago. The method asMap returns a Map that is a view on a Java bean (POJO). You can call putAll on that Map, passing it the Map that you want to copy data from.

Feel free to use my code mentioned above.

Example:

MyClass bean = ...;
Map<String, Object> inputData = ...;

Map<String, Object> view = BeanAsMap.asMap(bean);
view.putAll(inputData);
Jesper
  • 202,709
  • 46
  • 318
  • 350
  • Your code is fine @Jesper, but the question is `Map -> Bean` and **NOT** `Bean -> Map` – Felipe Conde Jul 08 '15 at 11:44
  • 1
    @FelipeConde My code works two ways - you can create a new instance of a bean class, then wrap it with `BeanAsMap` and call `put` on the map which will set the bean properties. If you have an existing map, you can call `putAll` on the wrapper map to copy your existing map's values to the bean. – Jesper Jul 08 '15 at 12:12
  • @Jesper For simple JSON your code works fine but it fails in case of nested JSON. – Dhaval Simaria Mar 24 '20 at 12:30
3

BeanUtils is fine.

But, as good practice, i would not write code that use reflection. Or as the last solution i have, if none other has been found.

This code cannot be tracked in IDE like Eclipse (no call hierarchy), making the developer think that the setters are never called. He can break your code and that will still compile.

Too high level of abstraction like this makes the code difficult to understand.

Code that is being obfuscated will be broken by the obfuscator itself when writting such things.

Best solution would be to rethink the use of reflection to set the object fields.

Jerome L
  • 607
  • 5
  • 11
  • Could not agree more. – Erik K. Dec 23 '13 at 18:21
  • 3
    While I agree that reflection should not be used as a general-purpose tool, there are occasionally good reasons to use it (for example, setting the properties of a Bean object from JSON or other dynamic source). – Greg Brown Nov 20 '15 at 18:55
0

Check out http://commons.apache.org/beanutils/, in particular BeanUtils.populate():

http://commons.apache.org/beanutils/v1.8.3/apidocs/index.html

quaylar
  • 2,617
  • 1
  • 17
  • 31