Подтвердить что ты не робот

Интроспекция Java: объект к карте

У меня есть объект 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),
  • или, возможно, toMap(obj, "getAttr1", "getAttr3") при необходимости.

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

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


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

4b9b3361

Ответ 1

Для этого вы можете использовать интроспекцию 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;
}

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

Ответ 2

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

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

Ответ 3

Использование 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

Ответ 4

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

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

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

Используйте Jackson JSON для преобразования объекта в 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);

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

Ответ 6

Ни одна из этих работ для вложенных свойств, object mapper не выполняет справедливую работу, за исключением того, что вы должны установить все значения во всех полях, которые хотите видеть на карте, и даже тогда вы не можете легко избежать/игнорировать собственные @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()));
            }
        }
    }

Ответ 7

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 Date/Time API, есть некоторые проблемы, которые необходимо улучшить например:

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