Как преобразовать объект Java (bean) в пары ключ-значение (и наоборот)?

93

Скажем, у меня есть очень простой объект java, который имеет только некоторые свойства getXXX и setXXX. Этот объект используется только для обработки значений, в основном записи или типобезопасной (и производительной) карты. Мне часто нужно преобразовать этот объект в пары ключ-значение (строковые или типобезопасные) или преобразовать из пар ключ-значение в этот объект.

Как лучше всего добиться этого, кроме отражения или написания кода вручную для этого преобразования?

Примером может быть отправка этого объекта через jms без использования типа ObjectMessage (или преобразование входящего сообщения в объект правильного типа).

Шахбаз
источник
java.beans.Introspector.getBeanInfo(). Он встроен прямо в JDK.
Marquis of Lorne

Ответы:

53

Всегда есть beanutils apache commons, но, конечно, он использует отражение под капотом

Морис Перри
источник
8
Кроме того, нет ничего плохого в том, что под капотом используется отражение. Особенно если результаты кэшируются (для использования в будущем), доступ на основе отражения обычно достаточно быстр для большинства случаев использования.
StaxMan
22
Если быть точным, BeanMap (bean) - это особая часть общих beanutils, которая делает свое дело.
vdr
3
Не обращая внимания на соображения производительности, я обнаружил, что использование BeanMap () в сочетании с ObjectMapper Джексона из приведенного ниже ответа дало мне лучшие результаты. BeanMap удалось создать более полную карту моего объекта, а затем Джексон преобразовал ее в простую структуру LinkedHashMap, которая позволила вносить изменения в дальнейшем по конвейеру. objectAsMap = objectMapper.convertValue (новый BeanMap (myObject), Map.class);
michaelr524
Не будет работать на Android, поскольку он использует java.beansзависимости, сильно ограниченные платформой. Подробнее см. Соответствующий вопрос .
SqueezyMo 06
Решение с упоминанием Apache Commons в большинстве случаев является лучшим решением.
рüффп
179

Возможных решений много, но давайте добавим еще одно. Используйте Jackson (библиотека обработки JSON) для преобразования "без json", например:

ObjectMapper m = new ObjectMapper();
Map<String,Object> props = m.convertValue(myBean, Map.class);
MyBean anotherBean = m.convertValue(props, MyBean.class);

этой записи блога есть еще несколько примеров)

Вы можете в основном преобразовать любые совместимые типы: совместимые, что означает, что если вы преобразовали из типа в JSON, а из этого JSON в тип результата, записи будут совпадать (при правильной настройке также можно просто игнорировать нераспознанные).

Хорошо работает для ожидаемых случаев, включая карты, списки, массивы, примитивы, объекты POJO типа bean.

StaxMan
источник
2
Это должен быть принятый ответ. BeanUtilsхорошо, но не может обрабатывать массивы и перечисления
Маниш Патель
8

Генерация кода была бы единственным другим способом, о котором я могу думать. Лично я получил обычно многоразовое решение для отражения (если только эта часть кода не является абсолютно критичной для производительности). Использование JMS звучит как излишество (дополнительная зависимость, и это даже не то, для чего она предназначена). Кроме того, он, вероятно, также использует отражение под капотом.

Майкл Боргвардт
источник
Производительность имеет решающее значение, поэтому я думаю, что размышления отсутствуют. Я надеялся, что могут быть инструменты, использующие asm или cglib. Я снова начал смотреть на буферы протокола Google. Я не получил вашего комментария о JMS, я использую его для передачи информации между машинами.
Shahbaz
Я неправильно это понял. Что касается производительности, есть разница между производительностью и критичностью этой конкретной части. Я бы сделал тест, чтобы увидеть, насколько это действительно важно по сравнению с тем, что еще делает приложение.
Майкл Боргвардт,
Это тоже моя мысль. Либо вы используете отражение (прямо или косвенно), либо вам нужно сгенерировать код. (Или, конечно, напишите дополнительный код самостоятельно).
extraneon
8

Это метод преобразования объекта Java в карту

public static Map<String, Object> ConvertObjectToMap(Object obj) throws 
    IllegalAccessException, 
    IllegalArgumentException, 
    InvocationTargetException {
        Class<?> pomclass = obj.getClass();
        pomclass = obj.getClass();
        Method[] methods = obj.getClass().getMethods();


        Map<String, Object> map = new HashMap<String, Object>();
        for (Method m : methods) {
           if (m.getName().startsWith("get") && !m.getName().startsWith("getClass")) {
              Object value = (Object) m.invoke(obj);
              map.put(m.getName().substring(3), (Object) value);
           }
        }
    return map;
}

Как это называть

   Test test = new Test()
   Map<String, Object> map = ConvertObjectToMap(test);
DeXoN
источник
1
Я не думаю, что желаемый ответ повторяется по методам, определенным для каждого объекта, звучит как отражение.
Роберт Карл,
2
Я не знаю, что ваши цели - это ваш метод. Но я думаю, что это очень отличный пример, когда вы хотите программировать с помощью общих типов объектов
DeXoN
6

Наверное, опоздал на вечеринку. Вы можете использовать Jackson и преобразовать его в объект Properties. Это подходит для вложенных классов, и если вам нужен ключ для abc = value.

JavaPropsMapper mapper = new JavaPropsMapper();
Properties properties = mapper.writeValueAsProperties(sct);
Map<Object, Object> map = properties;

если вам нужен суффикс, просто сделайте

SerializationConfig config = mapper.getSerializationConfig()
                .withRootName("suffix");
mapper.setConfig(config);

нужно добавить эту зависимость

<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-properties</artifactId>
</dependency>
острота
источник
4

С Java 8 вы можете попробовать следующее:

public Map<String, Object> toKeyValuePairs(Object instance) {
    return Arrays.stream(Bean.class.getDeclaredMethods())
            .collect(Collectors.toMap(
                    Method::getName,
                    m -> {
                        try {
                            Object result = m.invoke(instance);
                            return result != null ? result : "";
                        } catch (Exception e) {
                            return "";
                        }
                    }));
}
Bax
источник
3

JSON , например с использованием XStream + Jettison, представляет собой простой текстовый формат с парами ключ-значение. Он поддерживается, например, брокером сообщений JMS Apache ActiveMQ для обмена объектами Java с другими платформами / языками.

mjn
источник
3

Просто используя отражение и Groovy:

def Map toMap(object) {             
return object?.properties.findAll{ (it.key != 'class') }.collectEntries {
            it.value == null || it.value instanceof Serializable ? [it.key, it.value] : [it.key,   toMap(it.value)]
    }   
}

def toObject(map, obj) {        
    map.each {
        def field = obj.class.getDeclaredField(it.key)
        if (it.value != null) {
            if (field.getType().equals(it.value.class)){
                obj."$it.key" = it.value
            }else if (it.value instanceof Map){
                def objectFieldValue = obj."$it.key"
                def fieldValue = (objectFieldValue == null) ? field.getType().newInstance() : objectFieldValue
                obj."$it.key" = toObject(it.value,fieldValue) 
            }
        }
    }
    return obj;
}
Berardino
источник
Круто, но склонно к StackOverflowError
Тео Чунг Пинг
3

Используйте BeanWrapper juffrou- Reflection. Это очень эффективно.

Вот как вы можете превратить bean-компонент в карту:

public static Map<String, Object> getBeanMap(Object bean) {
    Map<String, Object> beanMap = new HashMap<String, Object>();
    BeanWrapper beanWrapper = new BeanWrapper(BeanWrapperContext.create(bean.getClass()));
    for(String propertyName : beanWrapper.getPropertyNames())
        beanMap.put(propertyName, beanWrapper.getValue(propertyName));
    return beanMap;
}

Я сам разработал Джуффро. Это открытый исходный код, поэтому вы можете использовать его и изменять. И если у вас есть какие-либо вопросы по этому поводу, я буду более чем счастлив ответить.

Ура

Карлос

Мартинс
источник
Я подумал об этом и понял, что мое предыдущее решение не работает, если у вас есть циклические ссылки в вашем bean-графе. Поэтому я разработал метод, который будет делать это прозрачно и красиво обрабатывать круговые ссылки. Теперь вы можете преобразовать компонент в карту и наоборот с помощью juffrou-reflection. Enjoy :)
Мартинс
3

При использовании Spring можно также использовать преобразователь объектов-карт Spring Integration. Вероятно, не стоит добавлять Spring в качестве зависимости только для этого.

Чтобы найти документацию, выполните поиск по запросу «Преобразователь объектов в карту» на http://docs.spring.io/spring-integration/docs/4.0.4.RELEASE/reference/html/messaging-transformation-chapter.html.

По сути, он просматривает весь граф объектов, доступный из объекта, указанного в качестве входных данных, и создает карту из всех полей примитивного типа / String на объектах. Его можно настроить для вывода:

  • плоская карта: {rootObject.someField = Joe, rootObject.leafObject.someField = Jane} или
  • структурированная карта: {someField = Joe, leafObject = {someField = Jane}}.

Вот пример с их страницы:

public class Parent{
    private Child child;
    private String name; 
    // setters and getters are omitted
}

public class Child{
   private String name; 
   private List<String> nickNames;
   // setters and getters are omitted
}

Результат будет:

{person.name = Джордж, person.child.name = Дженна, person.child.nickNames [0] = Бимбо. . . так далее}

Также доступен обратный трансформатор.

Ян Ланковский
источник
2

Вы можете использовать фреймворк Joda:

http://joda.sourceforge.net/

и воспользуйтесь JodaProperties. Однако это оговаривает, что вы создаете bean-компоненты определенным образом и реализуете определенный интерфейс. Тем не менее, он позволяет вам возвращать карту свойств из определенного класса без отражения. Пример кода здесь:

http://pbin.oogly.co.uk/listings/viewlistingdetail/0e78eb6c76d071b4e22bbcac748c57

Джон
источник
Выглядит интересно, но, к сожалению, больше не поддерживается. Кроме того, возвращаемая им карта свойств представляет собой карту <String, String>, поэтому тип не безопасен.
Shahbaz
1
Правда, он не поддерживался с 2002 года. Мне просто было любопытно, существует ли решение, не основанное на отражении. Карта свойств, которую он возвращает, на самом деле является просто стандартной картой, без универсальных шаблонов как таковых ...
Джон,
2

Если вы не хотите жестко кодировать вызовы каждого получателя и сеттера, отражение - единственный способ вызвать эти методы (но это несложно).

Можете ли вы реорганизовать рассматриваемый класс, чтобы использовать объект Properties для хранения фактических данных, и позволить каждому получателю и сеттеру просто вызывать для него get / set? Тогда у вас есть структура, хорошо подходящая для того, чем вы хотите заниматься. Существуют даже способы их сохранения и загрузки в форме "ключ-значение".

Торбьёрн Равн Андерсен
источник
Нет никаких методов, потому что от вас зависит, что является ключом, а что значение! Написать сервис, выполняющий такое преобразование, должно быть легко. Для простого представления может быть приемлемым CSV.
Мартин К.
2

Лучшее решение - использовать Dozer. Вам просто нужно что-то вроде этого в файле mapper:

<mapping map-id="myTestMapping">
  <class-a>org.dozer.vo.map.SomeComplexType</class-a>
  <class-b>java.util.Map</class-b>
</mapping> 

И все, обо всем остальном Dozer позаботится !!!

URL-адрес документации по бульдозеру


источник
Зачем нужен файл сопоставления? Если он знает, как конвертировать, зачем нужна эта дополнительная работа - просто взять исходный объект ожидаемого типа?
StaxMan
ОК. Приятно знать причину, хотя я не уверен, что она верна :)
StaxMan
2

Конечно, существует самый простой способ преобразования - никакого преобразования!

вместо использования частных переменных, определенных в классе, сделайте так, чтобы класс содержал только HashMap, в котором хранятся значения для экземпляра.

Затем ваши геттеры и сеттеры возвращаются и устанавливают значения в HashMap и из него, и когда приходит время преобразовать его в карту, вуаля! - это уже карта.

Приложив немного «волшебства» АОП, вы даже сможете сохранить негибкость, присущую bean-компоненту, позволив вам по-прежнему использовать геттеры и сеттеры, специфичные для каждого имени значения, без необходимости фактически писать отдельные геттеры и сеттеры.

Родни П. Барбати
источник
2

Вы можете использовать свойства сборщика фильтров потока java 8,

public Map<String, Object> objectToMap(Object obj) {
    return Arrays.stream(YourBean.class.getDeclaredMethods())
            .filter(p -> !p.getName().startsWith("set"))
            .filter(p -> !p.getName().startsWith("getClass"))
            .filter(p -> !p.getName().startsWith("setClass"))
            .collect(Collectors.toMap(
                    d -> d.getName().substring(3),
                    m -> {
                        try {
                            Object result = m.invoke(obj);
                            return result;
                        } catch (Exception e) {
                            return "";
                        }
                    }, (p1, p2) -> p1)
            );
}
Erhanasikoglu
источник
1

Мой процессор аннотаций JavaDude Bean генерирует код для этого.

http://javadude.googlecode.com

Например:

@Bean(
  createPropertyMap=true,
  properties={
    @Property(name="name"),
    @Property(name="phone", bound=true),
    @Property(name="friend", type=Person.class, kind=PropertyKind.LIST)
  }
)
public class Person extends PersonGen {}

Вышеупомянутое генерирует суперкласс PersonGen, который включает метод createPropertyMap (), который генерирует карту для всех свойств, определенных с помощью @Bean.

(Обратите внимание, что я немного меняю API для следующей версии - атрибут аннотации будет defineCreatePropertyMap = true)

Скотт Стэнчфилд
источник
Вы делали код-ревью? Это не кажется хорошим подходом! Вы меняете путь наследования с помощью аннотаций! Что делать, если bean-компонент распространяется уже в классе ?! Зачем вам нужно писать атрибуты дважды?
Мартин К.
Если у вас уже есть суперкласс, вы используете @Bean (superclass = XXX.class, ...), и он вставляет сгенерированный суперкласс между ними. Это отлично подходит для обзоров кода - гораздо меньше потенциально подверженного ошибкам шаблона, который нужно отсеивать.
Скотт Стэнчфилд,
Не уверены, что вы имеете в виду под «напишите атрибуты дважды» - где они появляются дважды?
Скотт Стэнчфилд,
@Martin K. Если вы действительно не хотите использовать отражение даже под капотом, то вы, вероятно, вынуждены выполнять какую-то генерацию кода.
extraneon
1

Вам следует написать универсальную службу преобразования! Используйте дженерики, чтобы он оставался свободным от типа (чтобы вы могли преобразовать каждый объект в key => value и обратно).

Какое поле должно быть ключевым? Получите это поле из bean-компонента и добавьте любое другое непреходящее значение в карту значений.

Обратный путь довольно прост. Прочтите ключ (x) и запишите сначала ключ, а затем каждую запись списка обратно в новый объект.

Вы можете получить имена свойств bean-компонента с помощью beanutils apache commons !

Мартин К.
источник
1

Если вам действительно нужна производительность, вы можете пойти по пути генерации кода.

Вы можете сделать это самостоятельно, выполнив собственное отражение и создав миксин AspectJ ITD.

Или вы можете использовать Spring Roo и создать надстройку Spring Roo . Ваш аддон Roo будет делать что-то подобное вышеупомянутому, но будет доступен всем, кто использует Spring Roo, и вам не нужно использовать аннотации времени выполнения.

Я сделал и то, и другое. Люди гадят на Spring Roo, но на самом деле это наиболее полная генерация кода для Java.

Адам Гент
источник
1

Еще один возможный способ здесь.

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

Company c = new Company();
 BeanWrapper bwComp = BeanWrapperImpl(c);
 bwComp.setPropertyValue("name", "your Company");
Venky
источник
1

Если дело доходит до простого сопоставления дерева объектов со списком значений ключа, где ключ может быть описанием пути, разделенного точками, от корневого элемента объекта до проверяемого листа, довольно очевидно, что преобразование дерева в список значений ключей сопоставимо с объект для сопоставления XML. Каждый элемент в XML-документе имеет определенную позицию и может быть преобразован в путь. Поэтому я взял XStream в качестве основного и стабильного инструмента преобразования и заменил иерархические драйверы и компоненты записи собственной реализацией. XStream также поставляется с базовым механизмом отслеживания пути, который в сочетании с двумя другими строго приводит к решению, подходящему для данной задачи.

Ларс Вундерлих
источник
1

С помощью библиотеки Джексона я смог найти все свойства класса типа String / integer / double и соответствующие значения в классе Map. ( без использования api отражений! )

TestClass testObject = new TestClass();
com.fasterxml.jackson.databind.ObjectMapper m = new com.fasterxml.jackson.databind.ObjectMapper();

Map<String,Object> props = m.convertValue(testObject, Map.class);

for(Map.Entry<String, Object> entry : props.entrySet()){
    if(entry.getValue() instanceof String || entry.getValue() instanceof Integer || entry.getValue() instanceof Double){
        System.out.println(entry.getKey() + "-->" + entry.getValue());
    }
}
Амит Канерия
источник
0

Используя Gson,

  1. Конвертировать POJO objectв Json
  2. Преобразовать Json в карту

        retMap = new Gson().fromJson(new Gson().toJson(object), 
                new TypeToken<HashMap<String, Object>>() {}.getType()
        );
    
Прабс
источник
0

Мы можем использовать библиотеку Джексона, чтобы легко преобразовать объект Java в карту.

<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.6.3</version>
</dependency>

При использовании в проекте Android вы можете добавить jackson в build.gradle вашего приложения следующим образом:

implementation 'com.fasterxml.jackson.core:jackson-core:2.9.8'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.9.8'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8'

Пример реализации

public class Employee {

    private String name;
    private int id;
    private List<String> skillSet;

    // getters setters
}

public class ObjectToMap {

 public static void main(String[] args) {

    ObjectMapper objectMapper = new ObjectMapper();

    Employee emp = new Employee();
    emp.setName("XYZ");
    emp.setId(1011);
    emp.setSkillSet(Arrays.asList("python","java"));

    // object -> Map
    Map<String, Object> map = objectMapper.convertValue(emp, 
    Map.class);
    System.out.println(map);

 }

}

Выход:

{имя = XYZ, id = 1011, навыки = [python, java]}

Анубхав
источник