Самоанализ Java: объект для сопоставления - PullRequest
37 голосов
/ 23 июля 2011

У меня есть Java-объект obj, который имеет атрибуты obj.attr1, obj.attr2 и т. Д. К атрибутам, возможно, обращаются через дополнительный уровень косвенности: obj.getAttr1(), obj.getAttr2(), если не общедоступный.

Задача : я хочу функцию, которая принимает объект и возвращает Map<String, Object>, где ключи - это строки "attr1", "attr2" и т. Д., А значения - соответствующие объекты obj.attr1, obj.attr2.Я предполагаю, что функция будет вызываться с чем-то вроде

  • toMap(obj),
  • или toMap(obj, "attr1", "attr3") (где attr1 и attr3 являются подмножеством obj 's атрибуты),
  • или, возможно, toMap(obj, "getAttr1", "getAttr3"), если необходимо.

Я не очень разбираюсь в самоанализе Java: как вы это делаете в Java?

Прямо сейчас, у меня есть специализированная реализация toMap() для каждого интересующего меня типа объекта, и это слишком много шаблонов.


ПРИМЕЧАНИЕ : дляте, кто знает Python, хотят что-то вроде obj.__dict__.Или dict((attr, obj.__getattribute__(attr)) for attr in attr_list) для варианта подмножества.

Ответы [ 7 ]

47 голосов
/ 21 ноября 2014

Другой способ для пользователя JacksonObjectMapper - это convertValue ex:

 ObjectMapper m = new ObjectMapper();
 Map<String,Object> mappedObject = m.convertValue(myObject,Map.class);
46 голосов
/ 23 июля 2011

Используйте Apache Commons BeanUtils: http://commons.apache.org/beanutils/.

Реализация Map для JavaBeans, которая использует самоанализ для получения и помещения свойств в bean-компонент:

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

Примечание: несмотря на то, что API возвращает Map<Object, Object> (начиная с 1.9.0), фактический класс для ключей в возвращенной карте равен java.lang.String

27 голосов
/ 23 июля 2011

Для этого вы можете использовать самоанализ JavaBeans.Читайте о 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;
}

Большое предостережение: Мой код касается только методов получения;он не найдет голых полей.Для полей, смотрите ответ высоко кофеина.:-) (Возможно, вы захотите объединить два подхода.)

15 голосов
/ 23 июля 2011

Вот приблизительное приближение, надеюсь, достаточно, чтобы вы указали в правильном направлении:

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;
}
5 голосов
/ 23 июля 2011

Вот действительно простой способ сделать это.

Используйте Джексона JSON lib для преобразования объекта в JSON.

Затем прочитайтеJSON и преобразовать его в карту.

Карта будет содержать все, что вы хотите.

Вот 4 вкладыша

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

И дополнительный выигрыш, конечно, в том, что это рекурсивно и создаст карты карт, если потребуется

3 голосов
/ 16 октября 2015

Ничто из этого не работает для вложенных свойств, средство отображения объектов делает хорошую работу, за исключением того, что вам нужно установить все значения во всех полях, которые вы хотите видеть на карте, и даже в этом случае вы не можете легко избежать / игнорировать собственные аннотации @Json в ObjectMapper. в основном пропустить некоторые свойства. Так что, к сожалению, вы должны сделать что-то вроде следующего, это всего лишь черновик, чтобы просто дать идею.

/*
     * 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()));
            }
        }
    }
0 голосов
/ 16 июля 2019

зависимостей 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);

для JSR310 New API Date / Time, есть некоторые проблемы, которые необходимо улучшить например:

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

ожидает (псевдо-код):

查询条件:{"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}

Вместо этого я получил это:

查询条件:{"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}

после нескольких исследований был найден эффективный трюк,

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;

выход:

查询条件:{"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}

я использовал приведенный выше код для динамического запроса в MyBatis например.

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

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