2011-07-22 11 views
31

Tengo un objeto Java obj que tiene atributos obj.attr1, obj.attr2 etc. Los atributos son posiblemente acceder a través de un nivel extra de indirección: obj.getAttr1(), obj.getAttr2(), si no pública.Java introspección: objeto al mapa

El reto: Quiero una función que toma un objeto y devuelve un Map<String, Object>, donde las claves son cadenas "attr1", etc. "attr2" y los valores son los objetos correspondientes obj.attr1, obj.attr2. me imagino la función se invoca con algo como

  • toMap(obj),
  • o toMap(obj, "attr1", "attr3") (donde attr1attr3 y son un subconjunto de obj 's atributos),
  • o tal vez toMap(obj, "getAttr1", "getAttr3") si es necesario.

No sé mucho acerca de la introspección de Java: ¿cómo se hace eso en Java?

En este momento, tengo una implementación especializada toMap() para cada tipo de objeto que me importa, y es demasiado repetitivo.


NOTA: para aquellos que conocen Python, quiero algo así como obj.__dict__. O dict((attr, obj.__getattribute__(attr)) for attr in attr_list) para la variante de subconjunto.

+0

¿Tiene que ser recursivo? – biziclop

+0

@biziclop: no, no recursión – Radim

Respuesta

22

Puede utilizar la introspección de JavaBeans para esto. Lea sobre la clase java.beans.Introspector:

public static Map<String, Object> introspect(Object obj) throws Exception { 
    Map<String, Object> result = new HashMap<String, Object>(); 
    BeanInfo info = Introspector.getBeanInfo(obj.getClass()); 
    for (PropertyDescriptor pd : info.getPropertyDescriptors()) { 
     Method reader = pd.getReadMethod(); 
     if (reader != null) 
      result.put(pd.getName(), reader.invoke(obj)); 
    } 
    return result; 
} 

gran advertencia: Mi código se refiere a sólo los métodos getter; no encontrará campos desnudos. Para los campos, vea la respuesta altamente descafeinada. :-) (Probablemente quiera combinar los dos enfoques.)

+0

¿Cuál es la diferencia entre Introspector vs simplemente reflexión? –

+0

@Amir: la introspección usa la reflexión para hacer su trabajo, pero funciona a un nivel más alto que la reflexión. Reflection encuentra campos y métodos de nivel Java; la introspección encuentra propiedades y eventos a nivel de JavaBeans. –

+0

Todas las respuestas fueron buenas. Aceptaré este porque me resultó más útil en mi solución final (que incluye 'BeanInfo' y' java.io.Serializable'). Gracias a todos. – Radim

5

Aquí hay realmente fácil manera de hacerlo.

Use Jackson JSON lib para convertir el objeto a JSON.

Luego lea el JSON y conviértalo en un Mapa.

El mapa contendrá todo lo que desee.

Aquí es el 4 revestimiento

ObjectMapper om = new ObjectMapper(); 
StringWriter sw = new StringWriter(); 
om.writeValue(object, sw); 
Map<String, Object> map = om.readValue(sw.toString(), Map.class); 

y ganar adicional, por supuesto, es que esta es recursivo y creará mapas de mapas si necesita

+1

Eso también es demasiado indirecto cuando se puede utilizar la introspección directa de JavaBeans. –

+1

No estoy seguro de cuál es menos código. –

+0

Me gusta de esta manera porque no me tiene que importar nada sobre la reflexión o cómo el objeto está realmente estructurado. –

14

He aquí una primera aproximación, con suerte suficiente para conseguir que apuntó en la dirección correcta:

public Map<String, Object> getMap(Object o) { 
    Map<String, Object> result = new HashMap<String, Object>(); 
    Field[] declaredFields = o.getClass().getDeclaredFields(); 
    for (Field field : declaredFields) { 
     result.put(field.getName(), field.get(o)); 
    } 
    return result; 
} 
+3

+1 Creo que entre su solución y la mía, el OP debería tenerlo fuera. (El suyo trata solo con campos; el mío solo trata con métodos getter: --P) –

+0

El mío se ocupa de todo, ha :) –

+0

Probablemente sea mejor si itera por todos los campos, tanto declarados como heredados. – biziclop

44

Utilice Apache Commons BeanUtils: http://commons.apache.org/beanutils/.

Una implementación del Mapa de JavaBeans que utiliza la introspección para obtener y poner propiedades en el bean:

Map<Object, Object> introspected = new org.apache.commons.beanutils.BeanMap(object); 

Nota: a pesar de la API devuelve Map<Object, Object> (desde 1.9.0), la clase real para las llaves en el mapa devuelto es java.lang.String

+3

+1 Bien, esta es oficialmente la menor cantidad de código. –

+1

Parece que esta solución es solo un método de manejo 'put' hasta el momento :) – Andrey

+0

esto crea 'Map ', no 'Map vvondra

35

Otra forma de usuario JacksonObjectMapper es el convertValue ejemplo:

ObjectMapper m = new ObjectMapper(); 
Map<String,Object> mappedObject = m.convertValue(myObject,Map.class); 
+0

Esta respuesta resuelve de manera experta y experta la problema original ¡Buen trabajo! –

+0

¿Cuál es la dependencia gradle para Android? –

+0

no se comprobó completamente, pero debería ser algo como 'compilar 'com.fasterxml.jackson: jackson-parent: 2.7-1'' (por supuesto de [maven] (http: // search. maven.org/#artifactdetails|com.fasterxml.jackson|jackson-parent|2.7-1|pom)) – Endeios

2

Ninguno de estos trabajos para propiedades anidadas, el asignador de objetos hace un trabajo justo excepto que tienes que establecer todos los valores en todos los campos que deseas ver en el mapa e incluso entonces no puedes evitar/ignorar fácilmente las propias anotaciones de @Json en ObjectMapper básicamente omitir algunas de las propiedades. Desafortunadamente, tienes que hacer algo como lo siguiente, es solo un borrador para solo dar una idea.

/* 
    * returns fields that have getter/setters including nested fields as 
    * field0, objA.field1, objA.objB.field2, ... 
    * to take care of recursive duplicates, 
    * simply use a set<Class> to track which classes 
    * have already been traversed 
    */ 
    public static void getBeanUtilsNestedFields(String prefix, 
      Class clazz, List<String> nestedFieldNames) throws Exception { 
     PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(clazz); 
     for(PropertyDescriptor descr : descriptors){ 
      // if you want values, use: descr.getValue(attributeName) 
      if(descr.getPropertyType().getName().equals("java.lang.Class")){ 
       continue; 
      } 
      // a primitive, a CharSequence(String), Number, Date, URI, URL, Locale, Class, or corresponding array 
      // or add more like UUID or other types 
      if(!BeanUtils.isSimpleProperty(descr.getPropertyType())){ 
       Field collectionfield = clazz.getDeclaredField(descr.getName()); 
       if(collectionfield.getGenericType() instanceof ParameterizedType){ 
        ParameterizedType integerListType = (ParameterizedType) collectionfield.getGenericType(); 
        Class<?> actualClazz = (Class<?>) integerListType.getActualTypeArguments()[0]; 
        getBeanUtilsNestedFields(descr.getName(), actualClazz, nestedFieldNames); 
       } 
       else{ // or a complex custom type to get nested fields 
        getBeanUtilsNestedFields(descr.getName(), descr.getPropertyType(), nestedFieldNames); 
       } 
      } 
      else{ 
       nestedFieldNames.add(prefix.concat(".").concat(descr.getDisplayName())); 
      } 
     } 
    } 
Cuestiones relacionadas