Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flattening Java Bean to a Map

I am stuck at converting Java Bean to Map. There are many resources on the internet, but unfortunately they all treat converting simple beans to Maps. My ones are a little bit more extensive.

There's simplified example:

public class MyBean {

  private String firstName;
  private String lastName;
  private MyHomeAddress homeAddress;
  private int age;

  // getters & setters

}

My point is to produce Map<String, Object> which, in this case, is true for 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 have tried using Apache Commons BeanUtils. Both approaches BeanUtils#describe(Object) and BeanMap(Object) produce a Map which "deep level" is 1 (I mean that there's only "homeAddress" key, holding MyHomeAddress object as a value). My method should enter the objects deeper and deeper until it meets a primitive type (or String), then it should stop digging and insert key i.e. "order.customer.contactInfo.home".

So, my question is: how can it be easliy done (or is there already existing project which would allow me to do that)?

update

I have expanded Radiodef answer to include also Collections, Maps Arrays and Enums:

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;
}
like image 506
Maciej Dobrowolski Avatar asked Sep 02 '14 16:09

Maciej Dobrowolski


People also ask

Can we convert object to map in Java?

In Java 8, we have the ability to convert an object to another type using a map() method of Stream object with a lambda expression. The map() method is an intermediate operation in a stream object, so we need a terminal method to complete the stream.

How do you turn an object into a map?

To convert an object to a Map , call the Object. entries() method to get an array of key-value pairs and pass the result to the Map() constructor, e.g. const map = new Map(Object. entries(obj)) . The new Map will contain all of the object's key-value pairs.

What are the methods correct for Java Bean?

To access the JavaBean class, we should use getter and setter methods.


1 Answers

Here's a simple reflective/recursive example.

You should be aware that there are some issues with doing a conversion the way you've asked:

  • Map keys must be unique.
  • Java allows classes to name their private fields the same name as a private field owned by an inherited class.

This example doesn't address those because I'm not sure how you want to account for them (if you do). If your beans inherit from something other than Object, you will need to change your idea a little bit. This example only considers the fields of the subclass.

In other words, if you have

public class SubBean extends Bean {

this example will only return fields from SubBean.

Java lets us do this:

package com.acme.util;
public class Bean {
    private int value;
}

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

Not that anybody should be doing that, but it's a problem if you want to use String as the keys, because there would be two keys named "value".

import java.lang.reflect.*;
import java.util.*;

public final class BeanFlattener {
    private BeanFlattener() {}

    public static Map<String, Object> deepToMap(Object bean) {
        Map<String, Object> map = new LinkedHashMap<>();
        try {
            putValues(bean, map, null);
        } catch (IllegalAccessException x) {
            throw new IllegalArgumentException(x);
        }
        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()) {
            if (field.isSynthetic() || Modifier.isStatic(field.getModifiers()))
                continue;
            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<?>> VALUE_CLASSES =
        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
            // etc.
        )));

    private static boolean isValue(Object value) {
        return value == null
            || value instanceof Enum<?>
            || VALUE_CLASSES.contains(value.getClass());
    }
}
like image 85
Radiodef Avatar answered Sep 22 '22 08:09

Radiodef