将 Java Bean 展平为 Map [英] Flattening Java Bean to a Map

查看:30
本文介绍了将 Java Bean 展平为 Map的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在将 Java Bean 转换为 Map.互联网上有很多资源,但不幸的是,它们都处理将简单的 bean 转换为 Maps.我的范围更广一些.

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.

有一个简化的例子:

public class MyBean {

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

  // getters & setters

}

我的观点是生成 Map,在这种情况下,它适用于以下条件:

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

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

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

更新

我已经扩展了 Radiodef 的答案,以包括集合、映射数组和枚举:

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

推荐答案

这是一个简单的反射/递归示例.

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:

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

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

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.

换句话说,如果你有

public class SubBean extends Bean {

这个例子只会从 SubBean 返回字段.

this example will only return fields from SubBean.

Java 让我们这样做:

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

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

这篇关于将 Java Bean 展平为 Map的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆