¿Cómo convertir un objeto Java (bean) a pares clave-valor (y viceversa)?

Digamos que tengo un objeto java muy simple que solo tiene algunas propiedades getXXX y setXXX. Este objeto se usa solo para manejar valores, básicamente un registro o un mapa de tipo seguro (y de rendimiento). A menudo necesito ocultar este objeto a pares de valores clave (ya sea cadenas o tipo seguro) o convertir pares de valores clave a este objeto.

Aparte de la reflexión o el código de escritura manual para hacer esta conversión, ¿cuál es la mejor manera de lograr esto?

Un ejemplo podría ser enviar este objeto a través de jms, sin utilizar el tipo ObjectMessage (o convertir un mensaje entrante al tipo correcto de objeto).

Siempre hay beanutils de apache commons, pero por supuesto usa reflections bajo el capó

Muchas soluciones posibles, pero agreguemos solo una más. Use Jackson (JSON processing lib) para hacer la conversión “json-less”, como:

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

( esta entrada de blog tiene algunos ejemplos más)

Básicamente, puede convertir cualquier tipo compatible: compatible, lo que significa que si convirtió de tipo a JSON, y de ese tipo JSON a resultado, las entradas coincidirían (si está configurado correctamente también puede simplemente ignorar las no reconocidas).

Funciona bien para los casos que cabría esperar, incluidos mapas, listas, matrices, primitivos, POJOs similares a frijoles.

La generación de código sería la única otra forma en que se me ocurre. Personalmente, obtuve una solución de reflexión generalmente reutilizable (a menos que esa parte del código sea absolutamente crítica para el rendimiento). El uso de JMS suena como overkill (dependencia adicional, y eso ni siquiera es lo que significa). Además, probablemente también usa la reflexión debajo del capó.

Este es un método para convertir un objeto Java en un Mapa

 public static Map ConvertObjectToMap(Object obj) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { Class< ?> pomclass = obj.getClass(); pomclass = obj.getClass(); Method[] methods = obj.getClass().getMethods(); Map map = new HashMap(); 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; } 

Así es como llamarlo

  Test test = new Test() Map map = ConvertObjectToMap(test); 

Con Java 8 puedes probar esto:

 public Map 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 ""; } })); } 

JSON , por ejemplo, usando XStream + Jettison, es un formato de texto simple con pares de valores clave. Es compatible, por ejemplo, con el intermediario de mensajes JMS de Apache ActiveMQ para el intercambio de objetos Java con otras plataformas / idiomas.

La mejor solución es usar Dozer. Solo necesitas algo como esto en el archivo mapeador:

  org.dozer.vo.map.SomeComplexType java.util.Map  

Y eso es todo, ¡Dozer se ocupa del rest!

URL de documentación de Dozer

Simplemente usando la reflexión y 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; } 

Usa Juffrou- reflect’s BeanWrapper. Es muy eficiente.

Aquí es cómo puedes transformar un frijol en un mapa:

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

Desarrollé Juffrou yo mismo. Es de código abierto, por lo que puede usarlo y modificarlo. Y si tiene alguna pregunta al respecto, estaré más que feliz de responder.

Aclamaciones

Carlos

Al usar Spring, también se puede usar Spring Integration object-to-map-transformer. Probablemente no valga la pena agregar Spring como una dependencia solo por esto.

Para la documentación, busque “Transformador de objeto a mapa” en http://docs.spring.io/spring-integration/docs/4.0.4.RELEASE/reference/html/messaging-transformation-chapter.html

Básicamente, atraviesa todo el gráfico de objetos accesible desde el objeto dado como entrada, y produce un mapa a partir de todos los campos de tipo primitivo / Cadena en los objetos. Se puede configurar para generar:

  • un mapa plano: {rootObject.someField = Joe, rootObject.leafObject.someField = Jane} o
  • un mapa estructurado: {someField = Joe, leafObject = {someField = Jane}}.

Aquí hay un ejemplo de su página:

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

La salida será:

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

Un transformador inverso también está disponible.

Puedes usar el framework Joda:

http://joda.sourceforge.net/

y aprovecha JodaProperties. Sin embargo, esto estipula que cree beans de una manera particular e implemente una interfaz específica. Sin embargo, le permite devolver un mapa de propiedad de una clase específica, sin reflexión. El código de muestra está aquí:

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

Si no desea codificar las llamadas a cada getter y setter, la reflexión es la única forma de llamar a estos métodos (pero no es difícil).

¿Se puede refactorizar la clase en cuestión para usar un objeto Properties para contener los datos reales, y dejar que cada getter y setter simplemente invoquen get / set? Entonces tienes una estructura adecuada para lo que quieres hacer. Incluso hay métodos para guardarlos y cargarlos en la forma de clave-valor.

Por supuesto, es el medio de conversión más simple posible, ¡sin conversión!

en lugar de usar variables privadas definidas en la clase, haga que la clase contenga solo un HashMap que almacena los valores para la instancia.

Luego, sus getters y setters regresan y establecen valores dentro y fuera del HashMap, y cuando es hora de convertirlo en un mapa, ¡voilá! – ya es un mapa.

Con un poco de magia de AOP, incluso podría mantener la inflexibilidad inherente en un bean al permitirle usar getters y setters específicos para cada nombre de valores, sin tener que escribir realmente los getters y setters individuales.

Puede usar las propiedades del recostackdor de filtro de flujo java 8,

 public Map 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) ); } 

Mi JavaDude Bean Annotation Processor genera código para hacer esto.

http://javadude.googlecode.com

Por ejemplo:

 @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 {} 

Lo anterior genera la superclase PersonGen que incluye un método createPropertyMap () que genera un Mapa para todas las propiedades definidas usando @Bean.

(Tenga en cuenta que estoy cambiando ligeramente la API para la próxima versión – el atributo de anotación será defineCreatePropertyMap = true)

¡Debería escribir un servicio de transformación genérico! Use generics para mantenerlo libre de tipo (para que pueda convertir cada objeto a clave => valor y viceversa).

¿Qué campo debería ser la clave? Obtenga ese campo del bean y agregue cualquier otro valor no transitorio en un mapa de valores.

El camino de regreso es bastante fácil. Lea la clave (x) y escriba al principio la clave y luego cada entrada de la lista a un nuevo objeto.

¡Puede obtener los nombres de las propiedades de un frijol con los beanutils de apache commons !

Si realmente quieres rendimiento, puedes ir a la ruta de generación de código.

Puedes hacer esto en tu lugar haciendo tu propio reflection y construyendo una mezcla en AspectJ ITD.

O puede usar Spring Roo y hacer un complemento de Spring Roo . Su complemento Roo hará algo similar a lo anterior, pero estará disponible para todos los que utilicen Spring Roo y no tendrá que usar Anotaciones en tiempo de ejecución.

He hecho ambas cosas. La gente miente en Spring Roo pero realmente es la generación de código más completa para Java.

Otra forma posible es aquí.

BeanWrapper ofrece funcionalidad para establecer y obtener valores de propiedad (individualmente o en bloque), obtener descriptores de propiedad y consultar propiedades para determinar si son legibles o escribibles.

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

Si se trata de un árbol de objetos simple para la asignación de listas de valores clave, donde la clave puede ser una descripción de ruta punteada desde el elemento raíz del objeto hasta la hoja que se inspecciona, es bastante obvio que una conversión de árbol a una lista clave-valor es comparable a Objeto de mapeo xml. Cada elemento dentro de un documento XML tiene una posición definida y se puede convertir en una ruta. Por lo tanto, tomé XStream como una herramienta de conversión básica y estable y reemplacé las partes jerárquicas de controlador y escritor con una implementación propia. XStream también viene con un mecanismo de seguimiento de ruta básico que, al combinarse con los otros dos, conduce estrictamente a una solución adecuada para la tarea.

Con la ayuda de la biblioteca de Jackson, pude encontrar todas las propiedades de clase de tipo String / integer / double, y los valores respectivos en una clase Map. ( sin usar reflexiones api! )

 TestClass testObject = new TestClass(); com.fasterxml.jackson.databind.ObjectMapper m = new com.fasterxml.jackson.databind.ObjectMapper(); Map props = m.convertValue(testObject, Map.class); for(Map.Entry entry : props.entrySet()){ if(entry.getValue() instanceof String || entry.getValue() instanceof Integer || entry.getValue() instanceof Double){ System.out.println(entry.getKey() + "-->" + entry.getValue()); } } 

Probablemente tarde a la fiesta. Puede usar Jackson y convertirlo en un objeto Properties. Esto es adecuado para clases anidadas y si desea la clave en el para abc = valor.

 JavaPropsMapper mapper = new JavaPropsMapper(); Properties properties = mapper.writeValueAsProperties(sct); Map map = properties; 

si quieres un sufijo, solo hazlo

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

necesito agregar esta dependencia

  com.fasterxml.jackson.dataformat jackson-dataformat-properties