web-dev-qa-db-fra.com

Java introspection: objet à mapper

J'ai un Java objet obj 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 non publique.

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 "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 connais pas grand-chose à l'introspection de Java: comment faites-vous cela en Java?

En ce moment, j'ai une implémentation spécialisée de toMap() pour chaque type d'objet qui m'intéresse, et c'est trop de passe-partout.


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.

38
Radim

Vous pouvez utiliser l'introspection JavaBeans pour cela. Lisez sur le Java.beans.Introspector classe:

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 concerne que les méthodes getter; il ne trouvera pas de champs nus. Pour les champs, voir la réponse de hautement caféiné. :-) (Vous voudrez probablement combiner les deux approches.)

27
Chris Jester-Young

Une autre façon d’utiliser JacksonObjectMapper est le convertValue ex:

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

Utilisez Apache Commons BeanUtils: http://commons.Apache.org/beanutils/ .

Une implémentation de Map for 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); 

Remarque: malgré le fait que l'API renvoie Map<Object, Object> (depuis 1.9.0), la classe réelle des clés dans la carte renvoyée est Java.lang.String

46
Andrey

Voici une approximation approximative, espérons-le assez pour vous aider à pointer 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;
}
15
highlycaffeinated

Voici un moyen vraiment facile de le faire.

Utilisez Jackson JSON lib pour convertir l'objet en JSON.

Lisez ensuite le JSON et convertissez-le en carte.

La carte contiendra tout ce que vous voulez.

Voici le 4 liner

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

Et le gain supplémentaire est bien sûr que ceci est récursif et créera des cartes de cartes s'il le faut

5
Amir Raminfar

Rien de tout cela ne fonctionne pour les propriétés imbriquées, le mappeur d'objet fait un travail équitable, sauf que vous devez définir toutes les valeurs sur tous les champs que vous souhaitez voir dans la carte et même dans ce cas, vous ne pouvez pas éviter/ignorer les propres annotations @Json des objets dans ObjectMapper des propriétés. Donc, malheureusement, vous devez faire quelque chose comme ce qui suit, ce n'est qu'un brouillon pour simplement 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()));
            }
        }
    }
3
kisna

dépendances maven

    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jsr310</artifactId>
    </dependency>

....

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

pour l'API nouvelle date/heure JSR310, certains problèmes doivent être améliorés, par exemple:

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.Test;

import Java.sql.Timestamp;
import Java.time.LocalDateTime;
import Java.time.ZoneId;
import Java.util.Map;

@Data
@NoArgsConstructor
public class QueryConditionBuilder
{
    LocalDateTime startTime;
    LocalDateTime endTime;
    Long nodeId;
    Long fsId;
    Long memId;
    Long ifCardId;

    private QueryConditionBuilder(QueryConditionBuilder.Builder builder) {
        setStartTime(builder.startTime);
        setEndTime(builder.endTime);
        setNodeId(builder.nodeId);
        setFsId(builder.fsId);
        setMemId(builder.memId);
        setIfCardId(builder.ifCardId);
    }

    public static QueryConditionBuilder.Builder newBuilder() {
        return new QueryConditionBuilder.Builder();
    }

    public static QueryConditionBuilder newEmptyBuilder() {
        return new QueryConditionBuilder.Builder().build();
    }


    public Map<String,Object> toFilter()
    {
        Map<String,Object> filter = new ObjectMapper().convertValue(this,Map.class);
        System.out.printf("查询条件:%s\n", JSON.toJSONString(filter));
        return filter;
    }

    public static final class Builder {
        private LocalDateTime startTime;
        private LocalDateTime endTime;
        private Long nodeId = null;
        private Long fsId = null;
        private Long memId =null;
        private Long ifCardId = null;

        private Builder() {
        }

        public QueryConditionBuilder.Builder withStartTime(LocalDateTime val) {
            startTime = val;
            return this;
        }

        public QueryConditionBuilder.Builder withEndTime(LocalDateTime val) {
            endTime = val;
            return this;
        }

        public QueryConditionBuilder.Builder withNodeId(Long val) {
            nodeId = val;
            return this;
        }

        public QueryConditionBuilder.Builder withFsId(Long val) {
            fsId = val;
            return this;
        }

        public QueryConditionBuilder.Builder withMemId(Long val) {
            memId = val;
            return this;
        }

        public QueryConditionBuilder.Builder withIfCardId(Long val) {
            ifCardId = val;
            return this;
        }

        public QueryConditionBuilder build() {
            return new QueryConditionBuilder(this);
        }
    }

    @Test
    public void test()
    {     
        LocalDateTime now = LocalDateTime.now(ZoneId.of("+8"));
        LocalDateTime yesterday = now.plusHours(-24);

        Map<String, Object> condition = QueryConditionBuilder.newBuilder()
                .withStartTime(yesterday)
                .withEndTime(now)
                .build().toFilter();

        System.out.println(condition);
    }
}

attend (pseudo-code):

查询条件:{"startTime":{"2019-07-15T20:43:15"},"endTime":{"2019-07-16T20:43:15"}
{startTime={2019-07-15T20:43:15}, endTime={"2019-07-16T20:43:15"}, nodeId=null, fsId=null, memId=null, ifCardId=null}

à la place, je les ai:

查询条件:{"startTime":{"dayOfMonth":15,"dayOfWeek":"MONDAY","dayOfYear":196,"hour":20,"minute":38,"month":"JULY","monthValue":7,"nano":263000000,"year":2019,"second":12,"chronology":{"id":"ISO","calendarType":"iso8601"}},"endTime":{"dayOfMonth":16,"dayOfWeek":"TUESDAY","dayOfYear":197,"hour":20,"minute":38,"month":"JULY","monthValue":7,"nano":263000000,"year":2019,"second":12,"chronology":{"id":"ISO","calendarType":"iso8601"}}}
{startTime={dayOfMonth=15, dayOfWeek=MONDAY, dayOfYear=196, hour=20, minute=38, month=JULY, monthValue=7, nano=263000000, year=2019, second=12, chronology={id=ISO, calendarType=iso8601}}, endTime={dayOfMonth=16, dayOfWeek=TUESDAY, dayOfYear=197, hour=20, minute=38, month=JULY, monthValue=7, nano=263000000, year=2019, second=12, chronology={id=ISO, calendarType=iso8601}}, nodeId=null, fsId=null, memId=null, ifCardId=null}

après quelques recherches, une astuce efficace a été trouvée,

ObjectMapper mapper = new ObjectMapper();
JavaTimeModule module = new JavaTimeModule();
//https://github.com/networknt/light-4j/issues/82
mapper.registerModule(module);
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
Map<String,Object> filter = mapper.convertValue(this,Map.class);
System.out.printf("查询条件:%s\n", JSON.toJSONString(filter));
return filter;

production:

查询条件:{"startTime":"2019-07-15T21:29:13.711","endTime":"2019-07-16T21:29:13.711"}
{startTime=2019-07-15T21:29:13.711, endTime=2019-07-16T21:29:13.711, nodeId=null, fsId=null, memId=null, ifCardId=null}

J'ai utilisé le code ci-dessus pour une requête dynamique dans MyBatis
par exemple.

 /***
     * 查询文件系统使用率
     * @param condition
     * @return
     */
    LinkedList<SnmpFileSystemUsage> queryFileSystemUsage(Map<String,Object> condition);

    List<SnmpFileSystemUsage> fooBar()
    { 
       return snmpBaseMapper.queryFileSystemUsage(QueryConditionBuilder
                .newBuilder()
                .withNodeId(nodeId)
                .build()
                .toFilter());
    }
0
passedbylove