Introspection Java: objet à mapper

J'ai un objet Java obj, ce qui a des attributs obj.attr1, obj.attr2 etc. Les attributs sont éventuellement accessibles via un niveau supplémentaire d'indirection: obj.getAttr1(), obj.getAttr2(), si ce n'est pas public.

Le défi: je veux une fonction qui prend un objet, et retourne un Map<String, Object>, où les clés sont des chaînes de caractères "attr1", "attr2" etc. et les valeurs sont les objets correspondants obj.attr1, obj.attr2. J'imagine que la fonction serait invoquée avec quelque chose comme

  • toMap(obj),
  • ou toMap(obj, "attr1", "attr3") (où attr1 et attr3 sont un sous-ensemble des attributs de obj),
  • , ou peut-être toMap(obj, "getAttr1", "getAttr3"), si nécessaire.

Je ne sais pas grand-chose sur L'introspection de Java: Comment faites-vous cela en Java?

En ce moment, j'ai une implémentation toMap() spécialisée pour chaque type d'objet qui me tient à cœur, et c'est trop standard.


NOTE: pour ceux qui connaissent Python, je veux quelque chose comme obj.__dict__. Ou dict((attr, obj.__getattribute__(attr)) for attr in attr_list) pour la variante de sous-ensemble.

33
demandé sur Radim 2011-07-23 01:11:11

6 réponses

Vous pouvez utiliser JavaBeans introspection pour cela. Lire sur la classe 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;
}

Grande mise en garde: Mon code ne traite que des méthodes getter; il ne trouvera pas de champs nus. Pour les champs, voir la réponse de highlycaffeinated. :- ) (Vous voudrez probablement combiner les deux approches.)

24
répondu Chris Jester-Young 2011-07-22 21:20:07

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

Une implémentation de Map pour JavaBeans qui utilise l'introspection pour obtenir et mettre des propriétés dans le bean:

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

Note: malgré le fait que L'API retourne Map<Object, Object> (depuis 1.9.0), la classe réelle pour les clés dans la carte retournée est java.lang.String

45
répondu Andrey 2016-03-30 05:34:56

Une Autre façon de l'utilisateur JacksonObjectMapper est le convertValue ex:

 ObjectMapper m = new ObjectMapper();
 Map<String,Object> mappedObject = m.convertValue(myObject,Map.class);
40
répondu Endeios 2016-09-28 09:51:24

Voici une approximation approximative, espérons-le assez pour vous orienter dans la bonne direction:

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;
}
14
répondu highlycaffeinated 2011-07-22 21:18:33

Voici une façon vraiment facile de le faire.

UtilisezJackson JSON lib pour convertir L'objet en JSON.

Ensuite, lisez le JSON et convertissez-le en une carte.

La carte contiendra tout ce que vous voulez.

Voici la doublure 4

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

Et la victoire supplémentaire est bien sûr que c'est récursif et créera des cartes de cartes si nécessaire

5
répondu Amir Raminfar 2011-07-22 21:23:29

Aucun de ceux-ci ne fonctionne pour les propriétés imbriquées, object mapper fait un travail équitable, sauf que vous devez définir toutes les valeurs sur tous les champs que vous voulez voir dans map et même alors vous ne pouvez pas éviter/ignorer les objets propres @JSON annotations facilement dans ObjectMapper ignorer certaines des propriétés. Donc, malheureusement, vous devez faire quelque chose comme ce qui suit, ce n'est qu'un brouillon pour donner une idée.

/*
     * 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()));
            }
        }
    }
2
répondu kisna 2015-10-16 01:47:27