Как преобразовать объект Java (bean) в пары ключ-значение (и наоборот)?
скажем, у меня есть очень простой объект java, который имеет только некоторые свойства getXXX и setXXX. Этот объект используется только для обработки значений, в основном записи или типобезопасной (и производительной) карты. Мне часто нужно скрыть этот объект для пар значений ключей (либо строк, либо типа safe) или преобразовать из пар значений ключей в этот объект.
кроме отражения или ручного написания кода для этого преобразования, каков наилучший способ достичь этого?
пример может быть отправка этого объекта через jms без использования типа ObjectMessage (или преобразования входящего сообщения в нужный тип объекта).
21 ответов:
всегда есть apache commons beanutils но, конечно, он использует отражение под капотом
много возможных решений, но давайте добавим еще один. Используйте Джексон (JSON processing lib) для выполнения преобразования "json-less", например:
ObjectMapper m = new ObjectMapper(); Map<String,Object> props = m.convertValue(myBean, Map.class); MyBean anotherBean = m.convertValue(props, MyBean.class);(эта запись в блоге есть еще несколько примеров)
вы можете в основном конвертировать любые совместимые типы: совместимость означает, что если вы конвертировали из типа в JSON, а из этого JSON в тип результата, записи будут совпадать (если они настроены правильно, также можно просто игнорировать нераспознанные те.)
хорошо работает для случаев, которые можно было бы ожидать, включая карты, списки, массивы, примитивы, бобовые POJOs.
генерация кода была бы единственным другим способом, который я могу придумать. Лично я получил с обычно многоразовым решением отражения (если эта часть кода не является абсолютно критичной для производительности). Использование JMS звучит как излишество (дополнительная зависимость, и это даже не то, для чего она предназначена). Кроме того, он, вероятно, использует отражение также Под капотом.
Это метод преобразования объекта 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);
С 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 ""; } })); }
JSON, например, с помощью XStream + Jettison, это простой текстовый формат с парами ключевых значений. Он поддерживается, например, брокером сообщений Apache ActiveMQ JMS для обмена объектами Java с другими платформами / языками.
лучшим решением является использование бульдозера. Вам просто нужно что-то вроде этого в файл картографа:
<mapping map-id="myTestMapping"> <class-a>org.dozer.vo.map.SomeComplexType</class-a> <class-b>java.util.Map</class-b> </mapping>и это все, бульдозер заботится об остальном!!!
просто используя отражение и заводной :
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; }
использовать juffrou-reflectBeanWrapper. Это очень эффективно.
вот как вы можете преобразовать боб в карту:
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; }Я сам разработал Juffrou. Это с открытым исходным кодом, так что вы можете использовать его и изменять. И если у вас есть какие-либо вопросы по этому поводу, я буду более чем счастлив ответить.
Ура
Карлос
при использовании весны, можно также использовать объект-к-карту-трансформер интеграции весны. Вероятно, не стоит добавлять весну как зависимость только для этого.
для документации выполните поиск "Object-to-Map Transformer" на http://docs.spring.io/spring-integration/docs/4.0.4.RELEASE/reference/html/messaging-transformation-chapter.html
по существу, он пересекает весь граф объектов, достижимый из объекта, заданного в качестве входных данных, и создает карту из всех примитивных полей типа / строки на объектах. Он может быть настроен для вывода либо:
- плоская карта: {rootObject.someField=Джо, 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 }выход будет:
{человек.имя=Джордж, человек.ребенка.имя=Дженна, человек.ребенок.Ники[0]=Бимбо . . . и т. д.}
обратный трансформатор также доступна.
вы можете использовать фреймворк Joda:
и воспользоваться JodaProperties. Это предусматривает, что вы создаете бобы определенным образом, однако, и реализуете определенный интерфейс. Однако это позволяет вам возвращать карту свойств из определенного класса без отражения. Пример кода является здесь:
http://pbin.oogly.co.uk/listings/viewlistingdetail/0e78eb6c76d071b4e22bbcac748c57
Если вы не хотите жестко кодировать вызовы для каждого геттера и сеттера, отражение-единственный способ вызвать эти методы (но это не сложно).
вы можете выполнить рефакторинг класса использовать свойства объекта для хранения данных, и пусть каждый геттер и сеттер просто позвонить получить/установить на нем? Тогда у вас есть структура хорошо подходит для того, что вы хотите сделать. Есть даже способы сохранить и загрузить их в форме ключ-значение.
есть, конечно, абсолютные простейшие средства преобразования возможно-нет преобразования вообще!
вместо использования частных переменных, определенных в классе, сделать класс содержит только хэш-карту, которая хранит значения для экземпляра.
затем ваши геттеры и сеттеры возвращают и устанавливают значения В и из HashMap, и когда пришло время преобразовать его в карту, вуаля! - это уже карта.
с немного AOP волшебства, вы могли бы даже поддерживайте негибкость, присущую компоненту, позволяя вам по-прежнему использовать геттеры и сеттеры, специфичные для каждого имени значений, без необходимости фактически писать отдельные геттеры и сеттеры.
вы можете использовать свойства коллектора фильтра потока 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) ); }
мой процессор аннотаций 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)
вы должны написать общий сервис преобразования! Используйте дженерики, чтобы сохранить его тип свободным (так что вы можете конвертировать каждый объект в key=>value и обратно).
какое поле должно быть ключевым? Получите это поле из компонента и добавьте любое другое непереходное значение в карту значений.
обратный путь довольно легко. Прочитайте ключ (x) и запишите сначала ключ, а затем каждую запись списка обратно в новый объект.
вы можете получить имена свойств объекта с apache commons beanutils!
Если вы действительно хотите производительность, вы можете пойти по пути генерации кода.
вы можете сделать это на своем, сделав свое собственное отражение и создав mixin AspectJ ITD.
или вы можете использовать Spring Roo и сделает Spring Roo Addon. Ваш аддон Roo будет делать что-то подобное выше, но будет доступен всем, кто использует Spring Roo, и Вам не нужно использовать аннотации времени выполнения.
Я сделал и то и другое. Люди гадят на Spring Roo но это действительно самая полная генерация кода для Java.
еще один возможный способ здесь.
BeanWrapper предлагает функциональность для установки и получения значений свойств (по отдельности или навалом), получить дескрипторы свойств и запросить свойства, чтобы определить, являются ли они доступны для чтения или записи.
Company c = new Company(); BeanWrapper bwComp = BeanWrapperImpl(c); bwComp.setPropertyValue("name", "your Company");
Если речь идет о простом дереве объектов для сопоставления списка значений ключа, где ключ может быть пунктирным описанием пути от корневого элемента объекта до проверяемого листа, довольно очевидно, что преобразование дерева в список значений ключа сопоставимо с сопоставлением объекта с xml. Каждый элемент в XML-документе имеет определенную позицию и может быть преобразован в путь. Поэтому я взял XStream как основной и стабильный инструмент преобразования и заменил иерархический драйвер и писатель расстается с собственной реализацией. XStream также поставляется с базовым механизмом отслеживания пути, который в сочетании с двумя другими строго приводит к решению, подходящему для этой задачи.
С помощью библиотеки Джексона я смог найти все свойства класса типа String/integer / double и соответствующие значения в классе карты. (без использования reflections 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()); } }
вероятно, поздно на вечеринку. Вы можете использовать Jackson и преобразовать его в объект свойств. Это подходит для вложенных классов и если вы хотите ключ в for a.b.c=значение.
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>
Comments