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

92

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 convertir este objeto en pares de valores clave (ya sean cadenas o tipo seguro) o convertir de pares de valores clave a este objeto.

Aparte de la reflexión o la escritura manual de código para realizar esta conversión, ¿cuál es la mejor manera de lograrlo?

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

Shahbaz
fuente
java.beans.Introspector.getBeanInfo(). Está integrado en el JDK.
Marqués de Lorne

Respuestas:

52

Siempre hay beanutils de apache commons pero, por supuesto, usa la reflexión debajo del capó

Maurice Perry
fuente
8
Además, no hay nada de malo en utilizar la reflexión, bajo el capó. Especialmente si los resultados se almacenan en caché (para uso futuro), el acceso basado en reflexión suele ser lo suficientemente rápido para la mayoría de los casos de uso.
StaxMan
22
Para ser precisos, BeanMap (bean) es la parte específica de beanutils común que hace el truco.
vdr
3
Sin tener en cuenta las consideraciones de rendimiento, descubrí que el uso de BeanMap () en combinación con el ObjectMapper de Jackson de la respuesta a continuación me dio los mejores resultados. BeanMap logró crear un mapa más completo de mi objeto y luego Jackson lo convirtió en una estructura simple de LinkedHashMap que permitió modificaciones más adelante en el proceso. objectAsMap = objectMapper.convertValue (nuevo BeanMap (myObject), Map.class);
michaelr524
No funcionará en Android porque usa java.beansdependencias muy limitadas en la plataforma. Consulte la pregunta relacionada para obtener más detalles.
SqueezyMo
Una solución que menciona a Apache Commons es, en la mayoría de los casos, la mejor solución.
рüффп
177

Muchas soluciones potenciales, pero agreguemos solo una más. Use Jackson (lib de procesamiento JSON) para hacer una conversión "sin json", como:

ObjectMapper m = new ObjectMapper();
Map<String,Object> 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 JSON a tipo de resultado, las entradas coincidirían (si se configuran correctamente, también pueden ignorar las no reconocidas).

Funciona bien para los casos que uno esperaría, incluidos mapas, listas, matrices, primitivas, POJO similares a frijoles.

StaxMan
fuente
2
Esta debería ser la respuesta aceptada. BeanUtilses bueno, pero no puede manejar matrices y enumeraciones
Manish Patel
8

La generación de código sería la única otra forma en la que puedo pensar. 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). Usar JMS parece una exageración (dependencia adicional, y eso ni siquiera es para lo que está destinado). Además, probablemente también use la reflexión debajo del capó.

Michael Borgwardt
fuente
El rendimiento es fundamental, así que creo que la reflexión está fuera. Esperaba que hubiera algunas herramientas que usaran asm o cglib. Empecé a buscar en los búferes de protocolo de Google nuevamente. No recibí su comentario sobre JMS, lo estoy usando para comunicar información entre máquinas.
Shahbaz
Entendí mal eso. En cuanto al rendimiento, existe una diferencia entre que el rendimiento sea importante y esa parte específica sea crítica para el rendimiento. Haría un punto de referencia para ver cuánto importa realmente en comparación con lo que está haciendo la aplicación.
Michael Borgwardt
Ese también es mi pensamiento. O usa la reflexión (directa o indirectamente) o tiene que generar código. (O escriba el código adicional usted mismo, por supuesto).
extraneón
8

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

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

Así es como se llama

   Test test = new Test()
   Map<String, Object> map = ConvertObjectToMap(test);
DeXoN
fuente
1
No creo que la respuesta deseada repita los métodos definidos en cada objeto, suena como una reflexión.
Robert Karl
2
No sé que tus propósitos son tu método. Pero creo que este es un ejemplo muy excelente cuando desea programar con tipos genéricos de objetos
DeXoN
5

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

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

si quieres un sufijo, hazlo

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

Necesito agregar esta dependencia

<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-properties</artifactId>
</dependency>
puntería
fuente
4

Con Java 8 puedes probar esto:

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 "";
                        }
                    }));
}
Bax
fuente
3

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

mjn
fuente
3

Simplemente usando 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;
}
berardino
fuente
Genial pero propenso a StackOverflowError
Teo Choong Ping
3

Utilice BeanWrapper de juffrou- reflect. Es muy eficaz.

Así es como puede transformar un bean en un mapa:

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

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

Salud

Carlos

Martins
fuente
Lo pensé y me di cuenta de que mi solución anterior se rompe si tiene referencias circulares en su gráfico de frijoles. Así que desarrollé un método que lo hará de forma transparente y maneja las referencias circulares maravillosamente. Ahora puede convertir un bean en un mapa y viceversa con juffrou-reflect. Disfruta :)
Martins
3

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

Para obtener documentación, busque "Object-to-Map Transformer" en http://docs.spring.io/spring-integration/docs/4.0.4.RELEASE/reference/html/messaging-transformation-chapter.html

Esencialmente, atraviesa todo el gráfico del objeto accesible desde el objeto dado como entrada y produce un mapa de todos los campos de tipo / cadena primitivos 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<String> nickNames;
   // setters and getters are omitted
}

La salida será:

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

También está disponible un transformador inverso.

Jan Żankowski
fuente
2

Podrías usar el marco de Joda:

http://joda.sourceforge.net/

y aprovechar JodaProperties. Sin embargo, esto estipula que debe crear beans de una manera particular e implementar una interfaz específica. Sin embargo, entonces 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

Jon
fuente
Parece interesante, lamentablemente ya no parece que se mantenga. Además, el mapa de propiedades que devuelve es un mapa de <String, String>, por lo que no es seguro para escribir.
Shahbaz
1
Es cierto que no se ha mantenido desde 2002. Solo tenía curiosidad por saber si existía una solución no basada en la reflexión. El mapa de propiedades que devuelve es en realidad solo un mapa estándar, sin genéricos como tales ...
Jon
2

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

¿Puede refactorizar la clase en cuestión para usar un objeto de Propiedades para contener los datos reales y dejar que cada captador y configurador simplemente llame a get / set en él? Entonces tienes una estructura adecuada para lo que quieres hacer. Incluso existen métodos para guardarlos y cargarlos en forma de clave-valor.

Thorbjørn Ravn Andersen
fuente
¡No hay métodos, porque depende de ti qué es clave y qué valor! Debería ser fácil escribir un servicio que realice dicha conversión. Para una representación simple, CSV podría ser aceptable.
Martin K.
2

La mejor solución es utilizar Dozer. Solo necesita algo como esto en el archivo del asignador:

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

Y eso es todo, Dozer se encarga del resto !!!

URL de documentación de Dozer


fuente
¿Por qué requiere un archivo de mapeo? Si sabe cómo convertir, ¿por qué requerir este trabajo adicional? ¿Simplemente tome el objeto fuente, el tipo esperado?
StaxMan
Okay. Es bueno saber la razón, aunque no estoy seguro de que sea válida :)
StaxMan
2

Por supuesto, existe el medio de conversión más simple posible: ¡ninguna conversión en absoluto!

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

Luego, sus captadores y definidores regresan y establecen valores dentro y fuera del HashMap, y cuando sea el momento de convertirlo en un mapa, ¡listo! - ya es un mapa.

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

Rodney P. Barbati
fuente
2

Puede usar las propiedades del colector de filtros de flujo de 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)
            );
}
erhanasikoglu
fuente
1

Mi procesador de anotaciones JavaDude Bean 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 una 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)

Scott Stanchfield
fuente
¿Ha realizado revisiones de código? ¡Esto no parece ser un buen enfoque! ¡Cambias la ruta de herencia con anotaciones! ¿Qué pasa si el frijol ya se extiende en una clase? ¿Por qué tienes que escribir los atributos dos veces?
Martin K.
Si ya tiene una superclase, usa @Bean (superclass = XXX.class, ...) e inserta la superclase generada en el medio. Esto ha sido excelente para las revisiones de código, sin embargo, mucho menos para tamizar la placa de calderas potencialmente propensa a errores.
Scott Stanchfield
No estoy seguro de lo que quiere decir con "escribir los atributos dos veces": ¿dónde aparecen dos veces?
Scott Stanchfield
@Martin K. Si realmente no quieres usar la reflexión, incluso bajo el capó, probablemente estés obligado a hacer algún tipo de generación de código.
extraneon
1

¡Debería escribir un servicio de transformación genérico! Use genéricos para mantenerlo libre de tipos (para que pueda convertir cada objeto a key => value 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 sencillo. Lea la clave (x) y escriba primero la clave y luego cada entrada de la lista en un nuevo objeto.

¡Puede obtener los nombres de propiedad de un bean con apache commons beanutils !

Martin K.
fuente
1

Si realmente desea rendimiento, puede seguir la ruta de generación de código.

Puede hacer esto por su cuenta haciendo su propia reflexión y construyendo una mezcla en AspectJ ITD.

O puede usar Spring Roo y hacer un complemento Spring Roo . Su complemento Roo hará algo similar a lo anterior, pero estará disponible para todos los que usen Spring Roo y no es necesario que use las anotaciones en tiempo de ejecución.

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

Adam Gent
fuente
1

Otra forma posible es aquí.

BeanWrapper ofrece funcionalidad para establecer y obtener valores de propiedad (individualmente o en forma masiva), obtener descriptores de propiedad y consultar propiedades para determinar si se pueden leer o escribir.

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

Si se trata de un mapeo simple de un árbol de objetos a una lista de valores clave, donde la clave puede ser una descripción de la ruta con puntos desde el elemento raíz del objeto hasta la hoja que se está inspeccionando, es bastante obvio que una conversión de árbol a una lista de valores clave es comparable a una objeto a 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 del controlador y del 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.

Lars Wunderlich
fuente
1

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 reflections 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());
    }
}
Amit Kaneria
fuente
0

Al usar Gson,

  1. Convertir POJO objecta Json
  2. Convertir Json en mapa

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

Podemos usar la biblioteca Jackson para convertir un objeto Java en un mapa fácilmente.

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

Si lo usa en un proyecto de Android, puede agregar jackson en build.gradle de su aplicación de la siguiente manera:

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'

Implementación de muestra

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

 }

}

Salida:

{nombre = XYZ, id = 1011, habilidades = [python, java]}

Anubhav
fuente