Как преобразовать объект Java (бин) в пары ключ-значение (и наоборот)?
Скажем, у меня есть очень простой объект Java, который имеет только некоторые свойства getXXX и setXXX. Этот объект используется только для обработки значений, в основном это запись или карта с типобезопасным (и производительным) отображением. Мне часто приходится конвертировать этот объект в пары ключ-значение (строки или безопасный тип) или преобразовывать пары ключ-значение в этот объект.
Что может быть лучше для достижения этой цели, кроме рефлексии или написания вручную кода для этого преобразования?
Примером может быть отправка этого объекта через jms без использования типа ObjectMessage (или преобразование входящего сообщения в правильный тип объекта).
24 ответа
Всегда есть apache http://commons.apache.org/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 в тип результата, записи будут совпадать (при правильной настройке можно просто игнорировать нераспознанные).
Работает хорошо для случаев, которые можно ожидать, включая Карты, Списки, массивы, примитивы, бобовые POJO.
Это метод для преобразования объекта 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);
Генерация кода была бы единственным способом, о котором я могу думать. Лично я получил решение для многократного использования отражений (если только эта часть кода не критична для производительности). Использование JMS звучит как излишнее (дополнительная зависимость, и это даже не то, для чего она предназначена). Кроме того, он, вероятно, использует отражение под капотом.
Наверное, опоздал на вечеринку. Вы можете использовать Джексона и преобразовать его в объект 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>
При использовании 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 = George, person.child.name = Дженна, person.child.nickNames[0]= Бимбо.,, так далее}
Обратный трансформатор также доступен.
С 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 "";
}
}));
}
Просто используя рефлексию и 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;
}
Используйте beanWrapper от juffrou- рефлекса. Это очень эффективно.
Вот как вы можете преобразовать бин в карту:
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. Это открытый исходный код, так что вы можете использовать его и изменять. И если у вас есть какие-либо вопросы по этому поводу, я буду более чем рад ответить.
ура
Carlos
Вы можете использовать свойства сборщика фильтра потока 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)
);
}
Лучшее решение - использовать Dozer. Вам просто нужно что-то вроде этого в файле Mapper:
<mapping map-id="myTestMapping">
<class-a>org.dozer.vo.map.SomeComplexType</class-a>
<class-b>java.util.Map</class-b>
</mapping>
И все, Дозер позаботится об остальном!!!
JSON, например, использующий XStream + Jettison, представляет собой простой текстовый формат с парами ключ-значение. Он поддерживается, например, брокером сообщений Apache ActiveMQ JMS для обмена объектами Java с другими платформами / языками.
Если вы не хотите жестко кодировать вызовы для каждого метода получения и установки, отражение является единственным способом вызова этих методов (но это не сложно).
Можете ли вы провести рефакторинг рассматриваемого класса, чтобы использовать объект Properties для хранения фактических данных, и позволить каждому получателю и установщику просто вызывать get /set для него? Тогда у вас есть структура, хорошо подходящая для того, что вы хотите сделать. Есть даже методы для сохранения и загрузки их в форме ключ-значение.
Конечно, существует абсолютное простейшее средство преобразования - никакого преобразования вообще!
вместо использования частных переменных, определенных в классе, сделайте так, чтобы класс содержал только HashMap, в котором хранятся значения для экземпляра.
Тогда ваши геттеры и сеттеры возвращают и устанавливают значения в и из HashMap, и когда пришло время преобразовать его в карту, вуаля! - это уже карта.
Приложив немного AOP wizardry, вы можете даже поддерживать негибкость, присущую bean-компоненту, позволяя вам по-прежнему использовать геттеры и сеттеры, специфичные для каждого имени значения, без необходимости фактически писать отдельные геттеры и сеттеры.
Добавить библиотеку Джексона
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.6.3</version>
</dependency>
Затем вы можете создать карту с помощью Object mapper.
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> map = objectMapper.convertValue(
yourJavaObject,
new TypeReference<Map<String, Object>>() {}
);
Вы можете использовать фреймворк Joda:
и воспользоваться JodaProperties. Однако это означает, что вы создаете bean-компоненты определенным образом и реализуете определенный интерфейс. Тем не менее, он позволяет вам возвращать карту свойств из определенного класса без отражения. Пример кода здесь:
http://pbin.oogly.co.uk/listings/viewlistingdetail/0e78eb6c76d071b4e22bbcac748c57
Вы должны написать общий сервис преобразования! Используйте дженерики, чтобы сохранить тип свободным (чтобы вы могли конвертировать каждый объект в ключ => значение и обратно).
В каком поле должен быть ключ? Получите это поле из компонента и добавьте любое другое непереходное значение в карту значений.
Обратный путь довольно прост. Прочитайте ключ (x) и напишите сначала ключ, а затем каждую запись в списке обратно в новый объект.
Вы можете получить имена свойств bean-компонента с помощью apache commons beanutils!
Если вы действительно хотите производительность, вы можете пойти по пути генерации кода.
Вы можете сделать это на себе, сделав свое собственное отражение и создав микширование 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");
Мой JavaDude Bean Annotation Processor генерирует код для этого.
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(), который генерирует Map для всех свойств, определенных с помощью @Bean.
(Обратите внимание, что я слегка изменяю API для следующей версии - атрибут аннотации будет defineCreatePropertyMap=true)
Используя Gson,
- Конвертировать POJO
object
Джсону Конвертировать Json в карту
retMap = new Gson().fromJson(new Gson().toJson(object), new TypeToken<HashMap<String, Object>>() {}.getType() );
С помощью библиотеки Джексона я смог найти все свойства класса типа 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());
}
}
Если речь идет о сопоставлении простого списка объектов со списком значений ключа, где ключ может быть точечным описанием пути от корневого элемента объекта до проверяемого листа, то вполне очевидно, что преобразование дерева в список значений ключей сопоставимо с объект для сопоставления XML. Каждый элемент в документе XML имеет определенную позицию и может быть преобразован в путь. Поэтому я взял XStream в качестве базового и стабильного инструмента преобразования и заменил части иерархического драйвера и модуля записи собственной реализацией. XStream также поставляется с базовым механизмом отслеживания пути, который - в сочетании с двумя другими - строго приводит к решению, подходящему для данной задачи.
Мы можем использовать библиотеку Джексона, чтобы легко преобразовать объект 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]}