将Java Bean展平为地图

时间:2014-09-02 16:15:50

标签: java javabeans apache-commons

我坚持将Java Bean转换为Map。互联网上有很多资源,但不幸的是,它们都将简单的bean转换为地图。我的那些更广泛。

有简化的例子:

public class MyBean {

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

  // getters & setters

}

我的观点是产生Map<String, Object>,在这种情况下,对于以下条件是正确的:

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")

我尝试过使用Apache Commons BeanUtils。两种方法BeanUtils#describe(Object)BeanMap(Object)都会产生一个“深层次”为1的地图(我的意思是只有"homeAddress"个密钥,将MyHomeAddress个对象作为值。我的方法应该越来越深入地进入对象,直到遇到基本类型(或字符串),然后它应该停止挖掘并插入密钥,即"order.customer.contactInfo.home"

所以,我的问题是:如何轻松完成(或者是否已经存在允许我这样做的项目)?

更新

我已经扩展了Radiodef的答案,还包括了集合,地图数组和枚举:

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;
}

2 个答案:

答案 0 :(得分:5)

这是一个简单的反思/递归示例。

您应该知道,按照您提出的方式进行转换存在一些问题:

  • 地图键必须是唯一的。
  • Java允许类将其私有字段命名为与继承类拥有的私有字段相同的名称。

这个例子没有解决这些问题,因为我不确定你想如何解释它们(如果你这样做)。如果你的bean继承了Object以外的东西,你需要稍微改变一下你的想法。此示例仅考虑子类的字段。

换句话说,如果你有

public class SubBean extends Bean {

此示例仅返回SubBean

中的字段

Java允许我们这样做:

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;
}

并非任何人都应该这样做,但如果您想使用String作为键,则会出现问题,因为会有两个名为"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());
    }
}

答案 1 :(得分:1)

您可以随时使用Jackson Json Processor。像这样:

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

其中pojo是一些Java bean。您可以在bean上使用一些不错的注释来控制序列化。

您可以重复使用ObjectMapper。