Як перетворити об’єкт Java (компонент) у пари ключ-значення (і навпаки)?


92

Скажімо, у мене є дуже простий об’єкт Java, який має лише деякі властивості getXXX та setXXX. Цей об'єкт використовується лише для обробки значень, в основному запису або безпечної для типу (і продуктивності) карти. Мені часто потрібно перетворити цей об'єкт на пари значень ключа (або рядки, або ввести безпечний), або перетворити з пар значень ключа на цей об'єкт.

Окрім відображення чи написання коду вручну для цього перетворення, який найкращий спосіб цього досягти?

Прикладом може бути надсилання цього об'єкта у форматі jms без використання типу ObjectMessage (або перетворення вхідного повідомлення в потрібний тип об'єкта).


java.beans.Introspector.getBeanInfo(). Він вбудований прямо в JDK.
Маркіз

Відповіді:


52

Завжди є apache commons beanutils, але, звичайно, він використовує відображення під капотом


8
До того ж, немає нічого поганого в тому, що використовується відбиття під капотом. Особливо, якщо результати кешуються (для подальшого використання), доступ на основі відображення зазвичай досить швидкий для більшості випадків використання.
StaxMan

22
Якщо бути точнішим, BeanMap (квасоля) - це специфічна частина загальних бінутілів, яка робить трюк.
vdr

3
Ігноруючи міркування щодо продуктивності, я виявив, що використання BeanMap () у поєднанні з ObjectMapper Джексона з наведеної нижче відповіді дало мені найкращі результати. BeanMap вдалося створити більш повну карту мого об'єкта, а потім Джексон перетворив її на звичайну структуру LinkedHashMap, яка дозволила модифікувати далі по конвеєру. objectAsMap = objectMapper.convertValue (новий BeanMap (myObject), Map.class);
michaelr524

Не буде працювати на Android, оскільки він використовує java.beansзалежності, сильно обмежені на платформі. Детальніше див. Відповідне запитання .
SqueezyMo

Рішення, в якому згадується Apache Commons, у більшості випадків є найкращим рішенням.
рюффп

177

Багато потенційних рішень, але додамо ще одне. Використовуйте Джексона (обробку JSON lib), щоб виконати перетворення без json, наприклад:

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

( цей запис у блозі містить ще кілька прикладів)

В основному ви можете перетворити будь-які сумісні типи: сумісні, що означає, що якщо ви перетворили з типу на JSON і з цього JSON на тип результату, записи будуть збігатися (якщо правильно налаштовано, ви можете просто ігнорувати невизнані).

Добре працює у випадках, на які можна очікувати, включаючи Карти, Списки, масиви, примітиви, POJO, схожі на боби.


2
Це має бути прийнятою відповіддю. BeanUtilsприємно, але не може обробляти масиви та перерахування
Manish Patel

8

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


Продуктивність є критично важливою, тому я думаю, що роздуми вже поза. Я сподівався, що можуть бути деякі інструменти, які використовують asm або cglib. Я знову почав розглядати буфери протоколів Google. Я не отримав ваш коментар щодо JMS, я використовую його для передачі інформації на різних машинах.
Шахбаз,

Я неправильно зрозумів це. Що стосується продуктивності, існує різниця між продуктивністю, яка є важливою, і тією конкретною частиною, яка є критично важливою. Я б зробив орієнтир, щоб побачити, наскільки це насправді важливо порівняно з тим, що ще робить програма.
Майкл Борґвардт,

Це теж моя думка. Або ви використовуєте відображення (прямо чи опосередковано), або вам доводиться генерувати код. (Або, звичайно, напишіть зайвий код самостійно).
екстранеон

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

1
Я не думаю, що бажана відповідь повторюється над методами, визначеними для кожного об’єкта, це звучить як роздум.
Роберт Карл,

2
Я не знаю, що ваші цілі - це ваш метод. Але я думаю, що це чудовий приклад, коли ви хочете програмувати із загальними типами об'єктів
DeXoN

5

Можливо, пізно на вечірку. Ви можете використовувати Джексона і перетворити його в об'єкт Властивості. Це підходить для вкладених класів, і якщо вам потрібен ключ у значенні for abc =.

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

3

JSON , наприклад, використовуючи XStream + Jettison, - це простий текстовий формат із парами значень ключів. Це підтримується, наприклад, брокером повідомлень Apache ActiveMQ JMS для обміну об’єктами Java з іншими платформами / мовами.


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

Круто, але схильний до StackOverflowError
Teo Choong Ping

3

Використовуйте BeanWrapper juffrou- Reflection. Це дуже ефективно.

Ось як ви можете перетворити боб на карту:

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-reflection. Насолоджуйтесь :)
Martins

3

Використовуючи Spring, можна також використовувати Spring Integration object-to-map-transformer. Можливо, не варто додавати Spring як залежність саме для цього.

Для отримання документації шукайте "Трансформатор об’єкта до карти" на веб- сайті http://docs.spring.io/spring-integration/docs/4.0.4.RELEASE/reference/html/messaging-transformation-chapter.html

По суті, він перетинає весь графік об’єкта, до якого можна дістатися з об’єкта, заданого як вхідні дані, і створює карту з усіх полів примітивного типу / рядка на об’єктах. Його можна налаштувати на вихід:

  • плоска карта: {rootObject.someField = Джо, rootObject.leafObject.someField = Джейн}, або
  • структурована карта: {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. Це передбачає, що ви створюєте компоненти певним чином і впроваджуєте певний інтерфейс. Однак це дозволяє вам повернути карту властивостей із певного класу без роздумів. Зразок коду знаходиться тут:

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


Виглядає цікаво, на жаль, схоже, це більше не підтримується. Крім того, карта властивостей, яку вона повертає, є картою <String, String>, тому не вводити безпечно.
Шахбаз,

1
Правда, це не зберігалося з 2002 року. Мені було просто цікаво, чи існує рішення, засноване на нерефлексії. Карта властивостей, яку вона повертає, насправді є лише стандартною картою, ніяких дженериків як таких ...
Джон,

2

Якщо ви не хочете жорстко кодувати дзвінки кожному отримувачеві та установщику, відображення - це єдиний спосіб викликати ці методи (але це не важко).

Чи можете ви переформатувати відповідний клас, щоб використовувати об’єкт Properties для зберігання фактичних даних, і дозволити кожному getter і setter просто викликати get / set на ньому? Тоді у вас є структура, яка добре підходить для того, що ви хочете зробити. Існують навіть методи збереження та завантаження їх у формі ключ-значення.


Методів не існує, бо від вас залежить, що є ключовим, а що цінним! Написати службу, яка робить таке перетворення, має бути легко. Для простого подання CSV може бути прийнятним.
Мартін К.

2

Найкраще рішення - використовувати бульдозер. Вам просто потрібно щось подібне у файлі mapper:

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

І все, Дозер подбає про решту !!!

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


Чому для цього потрібен файл зіставлення? Якщо він знає, як конвертувати, навіщо потрібна така додаткова робота - просто візьміть вихідний об'єкт, очікуваний тип?
StaxMan

Гаразд. Приємно знати причину, хоча я не впевнений, що вона дійсна :)
StaxMan

2

Звичайно, існує найпростіший можливий спосіб перетворення - взагалі ніякого перетворення!

замість використання приватних змінних, визначених у класі, зробіть, щоб клас містив лише HashMap, який зберігає значення для екземпляра.

Тоді ваші геттери та сеттери повертають і встановлюють значення в і з HashMap, і коли настає час перетворити його на карту, вуаля! - це вже карта.

З невеликою кількістю майстра AOP ви навіть можете зберегти гнучкість, притаманну компоненту, дозволяючи все одно використовувати геттери та сетери, специфічні для кожного імені значень, без необхідності писати окремі геттери та сетери.


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

1

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

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 (суперклас = XXX.class, ...), і він вставляє згенерований суперклас між ними. Це було чудово для оглядів коду - проте набагато менше потенційно схильних до помилок шаблонів для просіювання.
Скотт Стенчфілд,

Не знаєте, що ви маєте на увазі під фразою "два рази пишіть" - де вони відображаються двічі?
Скотт Стенчфілд,

@Martin K. Якщо ви дійсно не хочете використовувати рефлексію, навіть під капотом, то ви, мабуть, змушені зробити якусь генерацію коду.
екстранеон

1

Вам слід написати загальну Службу перетворень! Використовуйте загальні засоби, щоб забезпечити його вільний тип (щоб ви могли перетворити кожен об’єкт на ключ => значення і назад).

Яке поле повинно бути ключовим? Отримайте це поле з компонента та додайте будь-яке інше непостійне значення на карті значень.

Шлях назад досить простий. Клавіша читання (x) і запишіть спочатку ключ, а потім кожен запис списку назад до нового об’єкта.

Ви можете отримати назви властивостей квасолі за допомогою apache commons beanutils !


1

Якщо ви дійсно бажаєте продуктивності, ви можете піти шляхом генерації коду.

Ви можете зробити це на своєму, виконавши власні роздуми та побудувавши суміш AspectJ ITD.

Або ви можете використовувати Spring Roo і створити Spring Roo Addon . Ваш аддон Roo зробить щось подібне до вищезазначеного, але буде доступний кожному, хто використовує Spring Roo, і вам не потрібно використовувати анотації виконання.

Я зробив і те, і інше. Люди гадять на Spring Roo, але насправді це найповніше покоління коду для Java.


1

Тут є ще один можливий шлях.

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

Company c = new Company();
 BeanWrapper bwComp = BeanWrapperImpl(c);
 bwComp.setPropertyValue("name", "your Company");

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, ви можете додати Джексона в 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);

 }

}

Вихід:

{name = XYZ, id = 1011, навички = [python, java]}

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.