简体   繁体   English

将Java Bean展平为地图

[英]Flattening Java Bean to a Map

I am stuck at converting Java Bean to Map . 我坚持将Java Bean转换为Map There are many resources on the internet, but unfortunately they all treat converting simple beans to Maps. 互联网上有很多资源,但不幸的是,它们都将简单的bean转换为地图。 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<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")

I have tried using Apache Commons BeanUtils . 我尝试过使用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). 这两种方法都是BeanUtils#describe(Object)BeanMap(Object)生成一个“深层次”为1的Map(我的意思是只有"homeAddress"键,将MyHomeAddress对象保存为值)。 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 ie "order.customer.contactInfo.home" . 我的方法应该越来越深入地进入对象,直到它遇到基本类型(或字符串),然后它应该停止挖掘并插入键,即"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: 我已经扩展了Radiodef的答案,还包括Collections,Maps Arrays和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;
}

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. Java允许类将其私有字段命名为与继承类拥有的私有字段相同的名称。

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. 如果你的bean继承自Object以外的东西,你需要稍微改变一下你的想法。 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 . 此示例仅返回SubBean字段。

Java lets us do this: 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;
}

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" . 并不是说任何人都应该这样做,但是如果你想使用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());
    }
}

You could always use the Jackson Json Processor . 你总是可以使用Jackson Json处理器 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 some Java bean. 其中pojo是一些Java bean。 You can use some nice annotations on the bean to control the serialization. 您可以在bean上使用一些不错的注释来控制序列化。

You can re-use the ObjectMapper. 您可以重用ObjectMapper。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM