如何将Java对象(bean)转换为键值对(反之亦然)?


91

说我有一个非常简单的java对象,它仅具有一些getXXX和setXXX属性。该对象仅用于处理值,基本上是记录或类型安全(和性能)映射。我经常需要将此对象转换为键值对(字符串或类型安全)或从键值对转换为该对象。

除了反射或手动编写代码来进行此转换之外,实现此目的的最佳方法是什么?

一个示例可能是通过jms发送此对象,而不使用ObjectMessage类型(或将传入消息转换为正确的对象)。


java.beans.Introspector.getBeanInfo()。它内置在JDK中。
罗恩侯爵

Answers:


52

总是有apache commons beanutils,但是当然它在后台使用了反射


8
此外,在引擎盖下使用反射没有任何问题。特别是如果结果被缓存(以备将来使用),则基于反射的访问通常对于大多数用例而言足够快。
StaxMan 2010年

22
确切地说,BeanMap(bean)是常见的beanutils的特定部分。
vdr

3
忽略性能考虑,我发现从下面的答案中结合使用BeanMap()和Jackson的ObjectMapper可以得到最佳结果。BeanMap成功创建了我的对象的更完整的地图,然后Jackson将其转换为简单的LinkedHashMap结构,从而允许在管道中进行进一步的修改。objectAsMap = objectMapper.convertValue(new BeanMap(myObject),Map.class);
michaelr524

在Android上无法使用,因为它使用java.beans的平台上严格限制了依赖项。有关详细信息,请参见相关问题
SqueezyMo

在大多数情况下,提及Apache Commons的解决方案是最佳解决方案。
рüффп

177

许多潜在的解决方案,但让我们再添加一个。使用Jackson(JSON处理库)进行“少JSON”转换,例如:

ObjectMapper m = new ObjectMapper();
Map<String,Object> props = m.convertValue(myBean, Map.class);
MyBean anotherBean = m.convertValue(props, MyBean.class);

此博客条目有更多示例)

您基本上可以转换任何兼容的类型:兼容的意思是,如果您确实从类型转换为JSON,并且从该JSON转换为结果类型,则条目将匹配(如果配置正确,也可以忽略无法识别的条目)。

对于可能会遇到的情况,包括Maps,Lists,数组,原语和类bean POJO,效果很好。


2
这应该是公认的答案。BeanUtils很好,但是不能处理数组和枚举
Manish Patel

8

代码生成将是我能想到的唯一其他方法。就个人而言,我有一个通常可重用的反射解决方案(除非部分代码绝对对性能至关重要)。使用JMS听起来像是过分杀伤(附加的依赖关系,这甚至不是它的意思)。此外,它可能在引擎盖下也使用反射。


性能很关键,所以我认为反思已经结束。我希望可能会有一些使用asm或cglib的工具。我已经开始再次查看Google的协议缓冲区。我没有收到您对JMS的评论,我正在使用它在机器之间交流信息。
Shahbaz

我误会了。至于性能,在性能很重要与特定部分在性能上至关重要之间是有区别的。我将做一个基准测试,以了解与应用程序其他操作相比,它到底有多重要。
Michael Borgwardt 2009年

那也是我的想法。您(直接或间接)使用反射,或者必须生成代码。(当然,也可以自己编写额外的代码)。
Extraneon 2010年

8

这是一种将Java对象转换为Map的方法

public static Map<String, Object> ConvertObjectToMap(Object obj) throws 
    IllegalAccessException, 
    IllegalArgumentException, 
    InvocationTargetException {
        Class<?> pomclass = obj.getClass();
        pomclass = obj.getClass();
        Method[] methods = obj.getClass().getMethods();


        Map<String, Object> map = new HashMap<String, Object>();
        for (Method m : methods) {
           if (m.getName().startsWith("get") && !m.getName().startsWith("getClass")) {
              Object value = (Object) m.invoke(obj);
              map.put(m.getName().substring(3), (Object) value);
           }
        }
    return map;
}

这是怎么称呼的

   Test test = new Test()
   Map<String, Object> map = ConvertObjectToMap(test);

1
我认为所需的答案不会遍历每个对象上定义的方法,听起来像反射。
罗伯特·卡尔

2
我不知道你的目的是你的方法。但是我认为当您要使用通用类型的对象进行编程时,这是一个非常出色的示例
DeXoN 2012年

5

可能迟到了。您可以使用Jackson并将其转换为Properties对象。这适用于嵌套类,如果您想在abc = value中输入键。

JavaPropsMapper mapper = new JavaPropsMapper();
Properties properties = mapper.writeValueAsProperties(sct);
Map<Object, Object> map = properties;

如果您想要一些后缀,那就去做吧

SerializationConfig config = mapper.getSerializationConfig()
                .withRootName("suffix");
mapper.setConfig(config);

需要添加此依赖项

<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-properties</artifactId>
</dependency>

4

使用Java 8,您可以尝试以下操作:

public Map<String, Object> toKeyValuePairs(Object instance) {
    return Arrays.stream(Bean.class.getDeclaredMethods())
            .collect(Collectors.toMap(
                    Method::getName,
                    m -> {
                        try {
                            Object result = m.invoke(instance);
                            return result != null ? result : "";
                        } catch (Exception e) {
                            return "";
                        }
                    }));
}

3

JSON(例如,使用XStream + Jettison)是一种具有键值对的简单文本格式。例如,Apache ActiveMQ JMS消息代理支持它与其他平台/语言进行Java对象交换。


3

只需使用反射和Groovy即可:

def Map toMap(object) {             
return object?.properties.findAll{ (it.key != 'class') }.collectEntries {
            it.value == null || it.value instanceof Serializable ? [it.key, it.value] : [it.key,   toMap(it.value)]
    }   
}

def toObject(map, obj) {        
    map.each {
        def field = obj.class.getDeclaredField(it.key)
        if (it.value != null) {
            if (field.getType().equals(it.value.class)){
                obj."$it.key" = it.value
            }else if (it.value instanceof Map){
                def objectFieldValue = obj."$it.key"
                def fieldValue = (objectFieldValue == null) ? field.getType().newInstance() : objectFieldValue
                obj."$it.key" = toObject(it.value,fieldValue) 
            }
        }
    }
    return obj;
}

很酷,但容易出现StackOverflowError
Teo Choong Ping

3

使用juffrou-reflect的BeanWrapper。这是非常表现。

这是将bean转换为地图的方法:

public static Map<String, Object> getBeanMap(Object bean) {
    Map<String, Object> beanMap = new HashMap<String, Object>();
    BeanWrapper beanWrapper = new BeanWrapper(BeanWrapperContext.create(bean.getClass()));
    for(String propertyName : beanWrapper.getPropertyNames())
        beanMap.put(propertyName, beanWrapper.getValue(propertyName));
    return beanMap;
}

我自己开发了Juffrou。它是开源的,因此您可以自由使用和修改它。如果您对此有任何疑问,我们将非常乐意答复。

干杯

卡洛斯


我考虑了一下,并意识到,如果您的bean图中有循环引用,我的早期解决方案就会失败。因此,我开发了一种方法,可以透明地做到这一点,并且可以很好地处理循环引用。现在,您可以使用juffrou-reflect将bean转换为地图,反之亦然。享受:)
马丁斯

3

使用Spring时,也可以使用Spring Integration object-to-map-transformer。为此,不值得将Spring添加为依赖项。

有关文档,请在http://docs.spring.io/spring-integration/docs/4.0.4.RELEASE/reference/html/messaging-transformation-chapter.html上搜索“ Object-to-Map Transformer”

本质上,它遍历从作为输入给定的对象可到达的整个对象图,并根据对象上的所有原始类型/字符串字段生成映射。可以将其配置为输出:

  • 平面地图:{rootObject.someField = Joe,rootObject.leafObject.someField = Jane}或
  • 结构化地图:{someField = Joe,leafObject = {someField = Jane}}。

这是他们页面上的示例:

public class Parent{
    private Child child;
    private String name; 
    // setters and getters are omitted
}

public class Child{
   private String name; 
   private List<String> nickNames;
   // setters and getters are omitted
}

输出将是:

{person.name = George,person.child.name = Jenna,person.child.nickNames [0] = Bimbo。。。等等}

也可以使用反向变压器。


2

您可以使用Joda框架:

http://joda.sourceforge.net/

并利用JodaProperties。这确实要求您以特定方式创建bean,并实现特定接口。但是,它确实允许您从特定类返回属性映射,而无需进行反射。示例代码在这里:

http://pbin.oogly.co.uk/listings/viewlistingdetail/0e78eb6c76d071b4e22bbcac748c57


看起来很有趣,但是不幸的是它看起来不再像以前那样被维护了。此外,它返回的属性映射是<String,String>的映射,因此请不要键入safe。
Shahbaz

1
的确,自2002年以来一直未进行维护。我只是好奇是否存在基于非反射的解决方案。它返回的属性映射实际上只是一个标准映射,没有任何泛型……
Jon

2

如果您不希望对每个getter和setter的调用进行硬编码,则反射是调用这些方法的唯一方法(但这并不困难)。

您可以重构所涉及的类以使用Properties对象保存实际数据,并让每个getter和setter对其进行调用/设置吗?然后,您将拥有一个非常适合您想要做的结构。甚至还有方法以键值形式保存和加载它们。


没有方法,因为这取决于您什么是密钥,什么取决于价值!编写进行这种转换的服务应该很容易。对于简单表示,CSV可能是可接受的。
Martin K.

2

最好的解决方案是使用推土机。您只需要在映射器文件中添加以下内容:

<mapping map-id="myTestMapping">
  <class-a>org.dozer.vo.map.SomeComplexType</class-a>
  <class-b>java.util.Map</class-b>
</mapping> 

就是这样,推土机负责其余的工作!!!

推土机文档URL


为什么需要映射文件?如果知道如何进行转换,为什么还要做这些额外的工作-仅获取源对象和期望的类型?
StaxMan 2010年

好。很好地知道原因,即使我不确定它是否有效:)
StaxMan 2010年

2

当然,绝对有最简单的转换方法-根本没有转换!

不要使用在类中定义的私有变量,而应使类仅包含一个HashMap,用于存储实例的值。

然后,您的获取器和设置器返回值并将其设置到HashMap中或从HashMap中设置出来,当需要将其转换为地图时,瞧!-已经是地图了。

借助一点AOP向导,您甚至可以通过使用特定于每个值名称的getter和setter,而不必实际编写各个getter和setter,从而保持Bean固有的灵活性。


2

您可以使用Java 8流过滤器收集器属性,

public Map<String, Object> objectToMap(Object obj) {
    return Arrays.stream(YourBean.class.getDeclaredMethods())
            .filter(p -> !p.getName().startsWith("set"))
            .filter(p -> !p.getName().startsWith("getClass"))
            .filter(p -> !p.getName().startsWith("setClass"))
            .collect(Collectors.toMap(
                    d -> d.getName().substring(3),
                    m -> {
                        try {
                            Object result = m.invoke(obj);
                            return result;
                        } catch (Exception e) {
                            return "";
                        }
                    }, (p1, p2) -> p1)
            );
}

1

我的JavaDude Bean注释处理器生成了执行此操作的代码。

http://javadude.googlecode.com

例如:

@Bean(
  createPropertyMap=true,
  properties={
    @Property(name="name"),
    @Property(name="phone", bound=true),
    @Property(name="friend", type=Person.class, kind=PropertyKind.LIST)
  }
)
public class Person extends PersonGen {}

上面的代码生成了超类PersonGen,该类包括一个createPropertyMap()方法,该方法为使用@Bean定义的所有属性生成一个Map。

(请注意,我将稍微更改下一个版本的API,注释属性将为defineCreatePropertyMap = true)


您是否完成了代码审查?这似乎不是一个好方法!您可以通过注释更改继承路径!如果bean已经在一类上扩展了怎么办?为什么要写两次属性?
Martin K.

如果您已经有一个超类,则使用@Bean(superclass = XXX.class,...),它会在之间插入生成的超类。这对于代码审查来说非常有用-不过要筛选容易出错的样板。
Scott Stanchfield's

不确定“两次写入属性”是什么意思-它们在哪里出现两次?
Scott Stanchfield's

@Martin K.如果您真的不想使用反射,即使在幕后,也可能会被迫进行某种代码生成。
Extraneon 2010年

1

您应该编写一个通用的转换服务!使用泛型使其保持自由类型(以便您可以将每个对象转换为key => value并返回)。

哪个字段应该是关键?从Bean中获取该字段,并将任何其他非瞬态值附加到值映射中。

回来的路很容易。读取key(x)并首先写入密钥,然后将每个列表条目写回到新对象。

您可以使用apache commons beanutils获得bean的属性名称!


1

如果您真的想要性能,可以采用代码生成途径。

您可以通过自己进行反思并构建一个mixin AspectJ ITD来实现此目的。

或者,您可以使用Spring Roo并制作一个Spring Roo Addon。您的Roo插件将执行与上述操作类似的操作,但所有使用Spring Roo的用户都可以使用,而您不必使用运行时注释。

我都做过 人们不喜欢Spring Roo,但这确实是Java最全面的代码生成。


1

另一种可能的方式是在这里。

BeanWrapper提供了以下功能:设置和获取属性值(单独或批量),获取属性描述符以及查询属性以确定它们是否可读或可写。

Company c = new Company();
 BeanWrapper bwComp = BeanWrapperImpl(c);
 bwComp.setPropertyValue("name", "your Company");

1

如果涉及到简单的对象树到键值列表的映射,其中键可能是从对象的根元素到要检查的叶子的点状路径描述,那么很明显,从树到键值列表的转换可与对象到xml的映射。XML文档中的每个元素都有一个定义的位置,可以转换为路径。因此,我将XStream用作基本且稳定的转换工具,并用自己的实现替换了分层驱动程序和编写器部件。XStream还具有基本的路径跟踪机制,该机制与其他两种结合使用,严格地导致了适合该任务的解决方案。


1

借助Jackson库,我能够找到String / integer / double类型的所有类属性,以及Map类中的各个值。(不使用反射API!

TestClass testObject = new TestClass();
com.fasterxml.jackson.databind.ObjectMapper m = new com.fasterxml.jackson.databind.ObjectMapper();

Map<String,Object> props = m.convertValue(testObject, Map.class);

for(Map.Entry<String, Object> entry : props.entrySet()){
    if(entry.getValue() instanceof String || entry.getValue() instanceof Integer || entry.getValue() instanceof Double){
        System.out.println(entry.getKey() + "-->" + entry.getValue());
    }
}

0

通过使用Gson,

  1. 转换POJO object到Json
  2. 转换Json到地图

        retMap = new Gson().fromJson(new Gson().toJson(object), 
                new TypeToken<HashMap<String, Object>>() {}.getType()
        );

0

我们可以使用Jackson库将Java对象轻松转换为Map。

<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.6.3</version>
</dependency>

如果在Android项目中使用,则可以按以下步骤在应用程序的build.gradle中添加jackson:

implementation 'com.fasterxml.jackson.core:jackson-core:2.9.8'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.9.8'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8'

样例实施

public class Employee {

    private String name;
    private int id;
    private List<String> skillSet;

    // getters setters
}

public class ObjectToMap {

 public static void main(String[] args) {

    ObjectMapper objectMapper = new ObjectMapper();

    Employee emp = new Employee();
    emp.setName("XYZ");
    emp.setId(1011);
    emp.setSkillSet(Arrays.asList("python","java"));

    // object -> Map
    Map<String, Object> map = objectMapper.convertValue(emp, 
    Map.class);
    System.out.println(map);

 }

}

输出:

{name = XYZ,id = 1011,技能= [python,java]}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.