繁体   English   中英

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

[英]How to convert a Java object (bean) to key-value pairs (and vice versa)?

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

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

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

许多潜在的解决方案,但让我们再添加一个。 使用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 转换为结果类型,则条目将匹配(如果配置正确,也可以忽略无法识别的类型)。

适用于预期的情况,包括映射、列表、数组、原语、类 bean 的 POJO。

总是有 apache commons beanutils但当然它在引擎盖下使用反射

这是一种将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);

代码生成将是我能想到的唯一其他方式。 就我个人而言,我得到了一个通常可重用的反射解决方案(除非代码的那部分绝对是性能关键)。 使用 JMS 听起来有点矫枉过正(额外的依赖,这甚至不是它的意思)。 此外,它可能也在引擎盖下使用反射。

可能是聚会迟到了。 您可以使用 Jackson 并将其转换为 Properties 对象。 这适用于嵌套类,并且如果您想要 for 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>

使用 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 "";
                        }
                    }));
}

简单地使用反射和 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;
}

使用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。 它是开源的,因此您可以自由使用和修改。 如果您对此有任何疑问,我将非常乐意回答。

干杯

卡洛斯

使用 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 。 . . 等等}

也可使用反向变压器。

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

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

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

然后你的 getter 和 setter 返回并将值设置到 HashMap 中,当需要将它转换为映射时,瞧! ——已经是地图了。

借助一点 AOP 技巧,您甚至可以通过允许您仍然使用特定于每个值名称的 getter 和 setter 来保持 bean 固有的不灵活性,而不必实际编写单独的 getter 和 setter。

您可以使用 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)
            );
}

您可以使用 Joda 框架:

http://joda.sourceforge.net/

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

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

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

您能否重构有问题的类以使用 Properties 对象来保存实际数据,并让每个 getter 和 setter 对其调用 get/set? 然后你就有了一个非常适合你想做的事情的结构。 甚至还有以键值形式保存和加载它们的方法。

最好的解决方案是使用 Dozer。 你只需要在映射器文件中这样的东西:

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

就是这样,Dozer 负责其余的工作!!!

推土机文档网址

如果你真的很想要性能,你可以走代码生成路线。

您可以通过自己进行反射并构建一个 mixin AspectJ ITD 来完成此操作。

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

我两个都做过。 人们对 Spring Roo 嗤之以鼻,但它确实是最全面的 Java 代码生成。

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

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

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

在 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());
    }
}

我的 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)

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

什么领域应该是关键? 从 bean 中获取该字段并将任何其他非瞬态值附加到值映射中。

回来的路还算轻松。 读取 key(x) 并首先写入 key,然后将每个列表条目写回一个新对象。

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

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

通过使用 Gson,

  1. 将 POJO object转换为 Json
  2. 将 Json 转换为地图

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

我们可以使用 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);

 }

}

输出:

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

添加杰克逊库

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

然后您可以使用对象映射器生成地图。

ObjectMapper objectMapper = new ObjectMapper();

Map<String, Object> map = objectMapper.convertValue(
    yourJavaObject, 
    new TypeReference<Map<String, Object>>() {}
);

暂无
暂无

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

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