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
etattr3
sont un sous-ensemble des attributs deobj
), - , 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.
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.)
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
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);
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;
}
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
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()));
}
}
}