Jak przekonwertować obiekt Java (komponent bean) na pary klucz-wartość (i odwrotnie)?


93

Powiedzmy, że mam bardzo prosty obiekt Java, który ma tylko niektóre właściwości getXXX i setXXX. Ten obiekt jest używany tylko do obsługi wartości, w zasadzie rekordu lub bezpiecznej dla typu (i wydajnej) mapy. Często muszę ukrywać ten obiekt w parach klucz-wartość (ciągi znaków lub typ bezpieczny) lub konwertować z par klucz-wartość do tego obiektu.

Oprócz odbicia lub ręcznego pisania kodu w celu wykonania tej konwersji, jaki jest najlepszy sposób na osiągnięcie tego?

Przykładem może być wysłanie tego obiektu przez jms, bez użycia typu ObjectMessage (lub konwersja przychodzącej wiadomości na odpowiedni rodzaj obiektu).


java.beans.Introspector.getBeanInfo(). Jest wbudowany w JDK.
Markiz Lorne

Odpowiedzi:


53

Zawsze są fasolki zwyczajne Apache commons, ale oczywiście wykorzystuje odbicie pod maską


8
Poza tym nie ma nic złego w zastosowaniu odbicia pod maską. Zwłaszcza jeśli wyniki są buforowane (do wykorzystania w przyszłości), dostęp oparty na odbiciu jest zwykle wystarczająco szybki w większości przypadków użycia.
StaxMan

22
Mówiąc dokładniej, BeanMap (fasola) jest specyficzną częścią pospolitych nasion fasoli, która załatwia sprawę.
vdr

3
Pomijając względy wydajności, stwierdziłem, że użycie BeanMap () w połączeniu z ObjectMapper Jacksona z odpowiedzi poniżej dało mi najlepsze wyniki. BeanMapowi udało się stworzyć bardziej kompletną mapę mojego obiektu, a następnie Jackson przekształcił ją w zwykłą strukturę LinkedHashMap, która pozwoliła na modyfikacje w dalszej części potoku. objectAsMap = objectMapper.convertValue (new BeanMap (myObject), Map.class);
michaelr524

Nie będzie działać na Androidzie, ponieważ używa on java.beanszależności mocno ograniczonych na platformie. Szczegółowe informacje można znaleźć w powiązanym pytaniu .
SqueezyMo

Rozwiązanie wspominające o Apache Commons jest w większości przypadków najlepszym rozwiązaniem.
рüффп

179

Wiele potencjalnych rozwiązań, ale dodajmy jeszcze jedno. Użyj Jackson (biblioteka przetwarzania JSON), aby wykonać konwersję „bez json”, na przykład:

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

( ten wpis na blogu zawiera więcej przykładów)

Możesz w zasadzie przekonwertować dowolne zgodne typy: zgodne, co oznacza, że ​​jeśli przekonwertujesz z typu na JSON iz tego JSON na typ wyniku, wpisy będą pasować (jeśli skonfigurowane poprawnie, możesz po prostu zignorować nierozpoznane).

Działa dobrze w przypadkach, których można by się spodziewać, w tym w mapach, listach, tablicach, prymitywach, POJO podobnych do fasoli.


2
To powinna być akceptowana odpowiedź. BeanUtilsjest fajny, ale nie radzi sobie z tablicami i wyliczeniami
Manish Patel

8

Generowanie kodu to jedyny inny sposób, jaki przychodzi mi do głowy. Osobiście dostałem z generalnie rozwiązaniem do wielokrotnego użytku (chyba że ta część kodu jest absolutnie krytyczna dla wydajności). Używanie JMS brzmi jak przesada (dodatkowa zależność, a nawet nie do tego służy). Poza tym zapewne wykorzystuje odbicia także pod maską.


Wydajność jest krytyczna, więc myślę, że refleksja nie istnieje. Miałem nadzieję, że mogą istnieć narzędzia wykorzystujące asm lub cglib. Znowu zacząłem przyglądać się buforom protokołów Google. Nie dostałem twojego komentarza na temat JMS, używam go do przekazywania informacji między maszynami.
Shahbaz

Źle to zrozumiałem. Jeśli chodzi o wydajność, istnieje różnica między wydajnością, która jest ważna, a tą konkretną częścią, która ma krytyczne znaczenie dla wydajności. Zrobiłbym test porównawczy, aby zobaczyć, jakie to naprawdę ma znaczenie w porównaniu z tym, co jeszcze robi aplikacja.
Michael Borgwardt

To też moja myśl. Albo używasz odbicia (bezpośrednio lub pośrednio), albo musisz wygenerować kod. (Lub oczywiście napisz dodatkowy kod samodzielnie).
extraneon

8

Jest to metoda konwersji obiektu Java na mapę

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

Oto jak to nazwać

   Test test = new Test()
   Map<String, Object> map = ConvertObjectToMap(test);

1
Nie sądzę, aby pożądana odpowiedź powtarzała się po metodach zdefiniowanych dla każdego obiektu, brzmi jak refleksja.
Robert Karl

2
Nie wiem, jakie są twoje cele, to twoja metoda. Ale myślę, że jest to bardzo dobry przykład, kiedy chcesz programować z ogólnymi typami obiektów
DeXoN

6

Pewnie spóźniony na przyjęcie. Możesz użyć Jacksona i przekonwertować go na obiekt Właściwości. Jest to odpowiednie dla klas zagnieżdżonych i jeśli chcesz mieć klucz w for abc = value.

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

jeśli chcesz jakiś przyrostek, po prostu zrób

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

trzeba dodać tę zależność

<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-properties</artifactId>
</dependency>

4

W Javie 8 możesz spróbować tego:

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 , na przykład przy użyciu XStream + Jettison, to prosty format tekstowy z parami klucz-wartość. Jest obsługiwany na przykład przez broker komunikatów Apache ActiveMQ JMS do wymiany obiektów Java z innymi platformami / językami.


3

Po prostu używając Refleksji i 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;
}

Fajny, ale podatny na StackOverflowError
Teo Choong Ping

3

Użyj BeanWrapper firmy juffrou- reflect. Jest bardzo wydajny.

Oto, jak możesz przekształcić fasolę w mapę:

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

Sam opracowałem Juffrou. Jest to oprogramowanie typu open source, więc możesz go używać i modyfikować. Jeśli masz jakieś pytania, z przyjemnością odpowiem.

Twoje zdrowie

Carlos


Pomyślałem o tym i zdałem sobie sprawę, że moje wcześniejsze rozwiązanie psuje się, jeśli masz okrągłe odniesienia na wykresie fasoli. Dlatego opracowałem metodę, która zrobi to w sposób przejrzysty i pięknie obsługuje okrągłe odniesienia. Możesz teraz przekształcić fasolę w mapę i odwrotnie za pomocą juffrou-reflect. Ciesz się :)
Martins

3

Korzystając ze Springa, można również użyć transformatora obiekt-mapa-integracji Spring Integration. Prawdopodobnie nie warto dodawać Springa jako zależności tylko w tym celu.

Aby uzyskać dokumentację, wyszukaj „Object-to-Map Transformer” na http://docs.spring.io/spring-integration/docs/4.0.4.RELEASE/reference/html/messaging-transformation-chapter.html

Zasadniczo przechodzi przez cały graf obiektu osiągalny z obiektu podanego jako dane wejściowe i tworzy mapę ze wszystkich pól typu pierwotnego / String na obiektach. Można go skonfigurować tak, aby wyświetlał:

  • płaska mapa: {rootObject.someField = Joe, rootObject.leafObject.someField = Jane} lub
  • mapa strukturalna: {someField = Joe, leafObject = {someField = Jane}}.

Oto przykład z ich strony:

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
}

Wynik będzie:

{person.name = George, person.child.name = Jenna, person.child.nickNames [0] = Bimbo. . . itp}

Dostępny jest również transformator zwrotny.


2

Możesz użyć frameworka Joda:

http://joda.sourceforge.net/

i skorzystaj z JodaProperties. Jednak wymaga to tworzenia fasoli w określony sposób i zaimplementowania określonego interfejsu. Pozwala to jednak zwrócić mapę właściwości z określonej klasy bez refleksji. Przykładowy kod jest tutaj:

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


Wygląda interesująco, niestety nie wygląda na to, że jest już konserwowany. Poza tym mapa właściwości, którą zwraca, jest mapą <String, String>, więc nie należy wpisywać safe.
Shahbaz

1
To prawda, że ​​nie był utrzymywany od 2002 roku. Byłem po prostu ciekawy, czy istnieje rozwiązanie oparte na braku refleksji. Mapa właściwości, którą zwraca, jest w rzeczywistości tylko standardową mapą, bez typów ogólnych jako takich ...
Jon

2

Jeśli nie chcesz zakodować na stałe wywołań każdego gettera i settera, odbicie jest jedynym sposobem wywołania tych metod (ale nie jest to trudne).

Czy możesz refaktoryzować klasę, o której mowa, tak, aby używała obiektu Properties do przechowywania rzeczywistych danych i pozwalała każdemu pobierającemu i ustawiającemu wywoływać po prostu get / set? Wtedy masz strukturę dobrze dopasowaną do tego, co chcesz robić. Istnieją nawet metody zapisywania i ładowania ich w postaci klucza-wartości.


Nie ma metod, bo od Ciebie zależy, co jest kluczem, a co wartością! Napisanie usługi, która dokonuje takiej konwersji powinno być łatwe. W przypadku prostej reprezentacji CSV może być akceptowalny.
Martin K.

2

Najlepszym rozwiązaniem jest użycie Dozera. Potrzebujesz tylko czegoś takiego w pliku mapowania:

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

I to wszystko, Dozer zajmie się resztą !!!

Adres URL dokumentacji Dozera


Dlaczego wymaga pliku mapowania? Jeśli wie, jak konwertować, po co wymagać tej dodatkowej pracy - po prostu weź obiekt źródłowy, oczekiwany typ?
StaxMan

Ok. Dobrze jest znać powód, chociaż nie jestem pewien, czy jest ważny :)
StaxMan

2

Oczywiście istnieje absolutnie najprostszy możliwy sposób nawrócenia - żadnego nawrócenia!

zamiast używać prywatnych zmiennych zdefiniowanych w klasie, spraw, aby klasa zawierała tylko HashMap, który przechowuje wartości dla instancji.

Następnie twoje gettery i settery zwracają i ustawiają wartości do i z HashMap, a kiedy nadejdzie czas, aby przekonwertować je na mapę, voila! - to już mapa.

Przy odrobinie magii AOP, można by nawet zachować sztywność nieodłączną od fasoli, pozwalając nadal używać metod pobierających i ustawiających specyficznych dla każdej nazwy wartości, bez konieczności faktycznego pisania poszczególnych metod pobierających i ustawiających.


2

Możesz użyć właściwości kolektora filtru strumienia 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

Mój procesor adnotacji JavaDude Bean generuje kod, aby to zrobić.

http://javadude.googlecode.com

Na przykład:

@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 {}

Powyższe generuje PersonGen z nadklasy, która zawiera metodę createPropertyMap (), która generuje Mapę dla wszystkich właściwości zdefiniowanych przy użyciu @Bean.

(Zwróć uwagę, że nieznacznie zmieniam interfejs API dla następnej wersji - atrybut adnotacji będzie miał wartość defineCreatePropertyMap = true)


Czy zrobiłeś już recenzje kodu? To nie wydaje się być dobrym podejściem! Możesz zmienić ścieżkę dziedziczenia za pomocą adnotacji! Co jeśli fasola wydłuży się już na zajęciach ?! Dlaczego musisz dwa razy napisać atrybuty?
Martin K.

Jeśli masz już nadklasę, używasz @Bean (superklasa = XXX.class, ...) i wstawia między nimi wygenerowaną nadklasę. Jest to świetne rozwiązanie w przypadku przeglądów kodu - jest to jednak o wiele mniej podatne na błędy schematy do przesiewania.
Scott Stanchfield,

Nie jestem pewien, co masz na myśli, mówiąc „zapisz atrybuty dwa razy” - gdzie pojawiają się one dwukrotnie?
Scott Stanchfield,

@Martin K. Jeśli naprawdę nie chcesz używać refleksji, nawet pod maską, prawdopodobnie jesteś zmuszony do generowania kodu.
extraneon

1

Powinieneś napisać ogólną usługę transformacji! Użyj typów ogólnych, aby zachować swobodny typ (dzięki czemu możesz przekonwertować każdy obiekt na klucz => wartość iz powrotem).

Jakie pole powinno być kluczowe? Pobierz to pole z fasoli i dołącz dowolną inną wartość trwałą do mapy wartości.

Droga powrotna jest całkiem łatwa. Przeczytaj klucz (x) i napisz najpierw klucz, a następnie każdą pozycję listy z powrotem do nowego obiektu.

Możesz uzyskać nazwy właściwości fasoli za pomocą apache commons beanutils !


1

Jeśli naprawdę zależy Ci na wydajności, możesz skorzystać z trasy generowania kodu.

Możesz to zrobić samodzielnie, wykonując własne przemyślenia i budując mieszankę AspectJ ITD.

Lub możesz użyć Spring Roo i stworzyć dodatek Spring Roo . Twój dodatek Roo zrobi coś podobnego do powyższego, ale będzie dostępny dla każdego, kto używa Spring Roo i nie musisz używać adnotacji w czasie wykonywania.

Zrobiłem jedno i drugie. Ludzie bzdurają na Spring Roo, ale tak naprawdę jest to najbardziej wszechstronne generowanie kodu dla Java.


1

Jest jeszcze jeden możliwy sposób.

BeanWrapper oferuje funkcje ustawiania i pobierania wartości właściwości (indywidualnie lub zbiorczo), pobierania deskryptorów właściwości oraz wysyłania zapytań o właściwości w celu określenia, czy są one czytelne, czy zapisywalne.

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

1

Jeśli chodzi o mapowanie prostego drzewa obiektów na listę wartości kluczy, gdzie klucz może być kropkowanym opisem ścieżki od elementu głównego obiektu do liścia poddawanego inspekcji, jest raczej oczywiste, że konwersja drzewa na listę klucz-wartość jest porównywalna do obiekt do mapowania XML. Każdy element w dokumencie XML ma zdefiniowaną pozycję i można go przekształcić w ścieżkę. Dlatego wziąłem XStream jako podstawowe i stabilne narzędzie do konwersji i zastąpiłem hierarchiczne części sterownika i pisarza własną implementacją. XStream jest również wyposażony w podstawowy mechanizm śledzenia ścieżki, który - w połączeniu z pozostałymi dwoma - ściśle prowadzi do rozwiązania odpowiedniego do zadania.


1

Z pomocą biblioteki Jacksona udało mi się znaleźć wszystkie właściwości klas typu String / integer / double i odpowiednie wartości w klasie Map. ( bez używania interfejsu API odbić! )

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

Korzystając z Gson,

  1. Konwertuj POJO objectna Json
  2. Konwertuj Json na Map

        retMap = new Gson().fromJson(new Gson().toJson(object), 
                new TypeToken<HashMap<String, Object>>() {}.getType()
        );
    

0

Możemy użyć biblioteki Jacksona, aby łatwo przekonwertować obiekt Java na mapę.

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

Jeśli używasz w projekcie systemu Android, możesz dodać jackson w pliku build.gradle swojej aplikacji w następujący sposób:

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'

Przykładowe wdrożenie

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

 }

}

Wynik:

{nazwa = XYZ, id = 1011, skills = [python, java]}

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.