Wie konvertiere ich ein Java-Objekt (Bean) in Schlüssel-Wert-Paare (und umgekehrt)?


91

Angenommen, ich habe ein sehr einfaches Java-Objekt, das nur einige getXXX- und setXXX-Eigenschaften hat. Dieses Objekt wird nur verwendet, um Werte zu verarbeiten, im Grunde genommen einen Datensatz oder eine typsichere (und performante) Zuordnung. Ich muss dieses Objekt oft in Schlüsselwertpaare (entweder Zeichenfolgen oder Typ Safe) umwandeln oder von Schlüsselwertpaaren in dieses Objekt konvertieren.

Was ist der beste Weg, um dies zu erreichen, außer Reflexion oder manuelles Schreiben von Code für diese Konvertierung?

Ein Beispiel könnte das Senden dieses Objekts über jms sein, ohne den ObjectMessage-Typ zu verwenden (oder eine eingehende Nachricht in den richtigen Objekttyp zu konvertieren).


java.beans.Introspector.getBeanInfo(). Es ist direkt in das JDK integriert.
Marquis von Lorne

Antworten:


52

Es gibt immer Apache Commons Beanutils, aber natürlich wird unter der Haube reflektiert


8
Außerdem ist nichts Falsches daran, dass Reflexion unter der Haube verwendet wird. Insbesondere wenn Ergebnisse zwischengespeichert werden (für die zukünftige Verwendung), ist der reflexionsbasierte Zugriff für die meisten Anwendungsfälle normalerweise schnell genug.
StaxMan

22
Um genau zu sein, ist BeanMap (Bean) der spezifische Teil der üblichen Beanutils, der den Trick macht.
Vdr

3
Ohne Rücksicht auf Leistungsaspekte habe ich festgestellt, dass die Verwendung von BeanMap () in Kombination mit Jacksons ObjectMapper aus der folgenden Antwort die besten Ergebnisse erbracht hat. BeanMap gelang es, eine vollständigere Karte meines Objekts zu erstellen, und Jackson konvertierte sie dann in eine einfache LinkedHashMap-Struktur, die Änderungen weiter unten in der Pipeline ermöglichte. objectAsMap = objectMapper.convertValue (neue BeanMap (myObject), Map.class);
Michaelr524

Funktioniert nicht mit Android, da java.beansAbhängigkeiten verwendet werden, die auf der Plattform stark eingeschränkt sind. Einzelheiten finden Sie in der entsprechenden Frage .
SqueezyMo

Eine Lösung, in der Apache Commons erwähnt wird, ist meistens die beste Lösung.
рüффп

177

Viele mögliche Lösungen, aber fügen wir noch eine hinzu. Verwenden Sie Jackson (JSON-Verarbeitungsbibliothek), um eine "json-less" -Konvertierung durchzuführen, z.

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

( dieser Blogeintrag enthält weitere Beispiele.)

Grundsätzlich können Sie alle kompatiblen Typen konvertieren: Kompatibel bedeutet, dass bei einer Konvertierung von Typ zu JSON und von diesem JSON zu Ergebnistyp die Einträge übereinstimmen würden (bei ordnungsgemäßer Konfiguration können auch nicht erkannte Typen einfach ignoriert werden).

Funktioniert gut für Fälle, die man erwarten würde, einschließlich Karten, Listen, Arrays, Grundelemente und bohnenähnliche POJOs.


2
Dies sollte die akzeptierte Antwort sein. BeanUtilsist nett, kann aber nicht mit Arrays und Enums umgehen
Manish Patel

8

Die Codegenerierung wäre der einzige andere Weg, den ich mir vorstellen kann. Persönlich hatte ich eine allgemein wiederverwendbare Reflexionslösung (es sei denn, dieser Teil des Codes ist absolut leistungskritisch). Die Verwendung von JMS klingt nach Overkill (zusätzliche Abhängigkeit, und dafür ist sie nicht einmal gedacht). Außerdem wird wahrscheinlich auch Reflexion unter der Haube verwendet.


Die Leistung ist entscheidend, daher denke ich, dass die Reflexion aus ist. Ich hatte gehofft, dass es einige Tools geben könnte, die asm oder cglib verwenden. Ich habe wieder angefangen, die Protokollpuffer von Google zu untersuchen. Ich habe Ihren Kommentar zu JMS nicht erhalten. Ich verwende ihn, um Informationen zwischen Maschinen zu kommunizieren.
Shahbaz

Das habe ich falsch verstanden. In Bezug auf die Leistung gibt es einen Unterschied zwischen der Leistung, die wichtig ist, und dem spezifischen Teil, der leistungskritisch ist. Ich würde einen Benchmark durchführen, um zu sehen, wie wichtig es wirklich ist, verglichen mit dem, was die Anwendung sonst noch tut.
Michael Borgwardt

Das ist auch mein Gedanke. Entweder verwenden Sie Reflektion (direkt oder indirekt) oder Sie müssen Code generieren. (Oder schreiben Sie den zusätzlichen Code natürlich selbst).
extraneon

8

Dies ist eine Methode zum Konvertieren eines Java-Objekts in eine 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;
}

So nennt man es

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

1
Ich denke nicht, dass die gewünschte Antwort über die für jedes Objekt definierten Methoden iteriert, das klingt nach Reflexion.
Robert Karl

2
Ich weiß nicht, dass Ihre Absichten Ihre Methode sind. Aber ich denke, dies ist ein sehr
gutes

5

Wahrscheinlich zu spät zur Party. Sie können Jackson verwenden und in ein Eigenschaftenobjekt konvertieren. Dies ist für verschachtelte Klassen geeignet und wenn Sie den Schlüssel für for abc = value möchten.

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

Wenn Sie ein Suffix möchten, tun Sie es einfach

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

müssen diese Abhängigkeit hinzufügen

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

4

Mit Java 8 können Sie Folgendes versuchen:

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 , beispielsweise mit XStream + Jettison, ist ein einfaches Textformat mit Schlüsselwertpaaren. Es wird beispielsweise vom Apache ActiveMQ JMS-Nachrichtenbroker für den Java-Objektaustausch mit anderen Plattformen / Sprachen unterstützt.


3

Einfach mit Reflection und 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;
}

Cool, aber anfällig für StackOverflowError
Teo Choong Ping

3

Verwenden Sie Juffrou-Reflect Reflect. Es ist sehr performant.

So können Sie eine Bohne in eine Karte verwandeln:

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

Ich habe Juffrou selbst entwickelt. Es ist Open Source, Sie können es also verwenden und ändern. Und wenn Sie Fragen dazu haben, werde ich Ihnen gerne antworten.

Prost

Carlos


Ich habe darüber nachgedacht und festgestellt, dass meine frühere Lösung nicht funktioniert, wenn Sie in Ihrem Bean-Diagramm Zirkelverweise haben. Deshalb habe ich eine Methode entwickelt, die dies transparent macht und Zirkelverweise wunderbar handhabt. Sie können jetzt eine Bohne in eine Karte konvertieren und umgekehrt mit Juffrou-Reflect. Viel Spaß :)
Martins

3

Bei Verwendung von Spring kann auch der Objektintegrationstransformator von Spring Integration verwendet werden. Es lohnt sich wahrscheinlich nicht, Spring nur dafür als Abhängigkeit hinzuzufügen.

Suchen Sie zur Dokumentation nach "Object-to-Map Transformer" auf http://docs.spring.io/spring-integration/docs/4.0.4.RELEASE/reference/html/messaging-transformation-chapter.html

Im Wesentlichen durchläuft es den gesamten Objektgraphen, der von dem als Eingabe angegebenen Objekt aus erreichbar ist, und erstellt eine Karte aus allen primitiven Typ- / Zeichenfolgenfeldern auf den Objekten. Es kann so konfiguriert werden, dass Folgendes ausgegeben wird:

  • eine flache Karte: {rootObject.someField = Joe, rootObject.leafObject.someField = Jane} oder
  • eine strukturierte Karte: {someField = Joe, leafObject = {someField = Jane}}.

Hier ist ein Beispiel von ihrer Seite:

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
}

Ausgabe wird sein:

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

Ein Rücktransformator ist ebenfalls erhältlich.


2

Sie können das Joda-Framework verwenden:

http://joda.sourceforge.net/

und nutzen Sie JodaProperties. Dies setzt jedoch voraus, dass Sie Beans auf eine bestimmte Weise erstellen und eine bestimmte Schnittstelle implementieren. Sie können dann jedoch eine Eigenschaftskarte einer bestimmten Klasse ohne Reflexion zurückgeben. Beispielcode ist hier:

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


Sieht interessant aus, sieht aber leider nicht mehr so ​​aus. Außerdem ist die zurückgegebene Eigenschaftskarte eine Karte von <String, String>, also nicht sicher.
Shahbaz

1
Es stimmt, es wurde seit 2002 nicht mehr gepflegt. Ich war nur neugierig, ob es eine nicht auf Reflexion basierende Lösung gibt. Die zurückgegebene Eigenschaftskarte ist eigentlich nur eine Standardkarte, keine Generika als solche ...
Jon

2

Wenn Sie nicht Aufrufe an jeden Getter und Setter fest codieren möchten, ist Reflektion die einzige Möglichkeit, diese Methoden aufzurufen (dies ist jedoch nicht schwierig).

Können Sie die betreffende Klasse so umgestalten, dass sie ein Eigenschaftenobjekt verwendet, um die tatsächlichen Daten zu speichern, und jeden Getter und Setter einfach get / set aufrufen lassen? Dann haben Sie eine Struktur, die gut zu dem passt, was Sie tun möchten. Es gibt sogar Methoden, um sie im Schlüsselwertformular zu speichern und zu laden.


Es gibt keine Methoden, denn es hängt von Ihnen ab, was ein Schlüssel und was ein Wert ist! Es sollte einfach sein, einen Dienst zu schreiben, der eine solche Konvertierung durchführt. Für eine einfache Darstellung kann CSV akzeptabel sein.
Martin K.

2

Die beste Lösung ist die Verwendung von Dozer. Sie brauchen nur so etwas in der Mapper-Datei:

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

Und das war's, Dozer kümmert sich um den Rest !!!

Dozer-Dokumentations-URL


Warum ist eine Zuordnungsdatei erforderlich? Wenn es weiß, wie man konvertiert, warum diese zusätzliche Arbeit erfordern - nehmen Sie einfach das Quellobjekt, den erwarteten Typ?
StaxMan

OK. Es ist gut, den Grund zu kennen, obwohl ich nicht sicher bin, ob es gültig ist :)
StaxMan

2

Es gibt natürlich das absolut einfachste Konvertierungsmittel - überhaupt keine Konvertierung!

Anstatt private Variablen zu verwenden, die in der Klasse definiert sind, muss die Klasse nur eine HashMap enthalten, in der die Werte für die Instanz gespeichert sind.

Dann kehren Ihre Getter und Setter zurück und setzen Werte in und aus der HashMap, und wenn es Zeit ist, sie in eine Karte umzuwandeln, voila! - Es ist bereits eine Karte.

Mit ein wenig AOP-Zauberei können Sie sogar die Inflexibilität einer Bean beibehalten, indem Sie weiterhin Getter und Setter verwenden, die für jeden Wertnamen spezifisch sind, ohne die einzelnen Getter und Setter tatsächlich schreiben zu müssen.


2

Sie können die Java 8 Stream Filter Collector-Eigenschaften verwenden.

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

Mein JavaDude Bean Annotation Processor generiert dazu Code.

http://javadude.googlecode.com

Beispielsweise:

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

Das Obige generiert PersonGen der Oberklasse, das eine createPropertyMap () -Methode enthält, die eine Map für alle mit @Bean definierten Eigenschaften generiert.

(Beachten Sie, dass ich die API für die nächste Version geringfügig ändere. Das Annotation-Attribut lautet defineCreatePropertyMap = true.)


Haben Sie Code-Reviews durchgeführt? Dies scheint kein guter Ansatz zu sein! Sie ändern den Vererbungspfad mit Anmerkungen! Was ist, wenn sich die Bohne bereits in einer Klasse erstreckt?! Warum müssen Sie die Attribute zweimal schreiben?
Martin K.

Wenn Sie bereits eine Oberklasse haben, verwenden Sie @Bean (Superklasse = XXX.Klasse, ...) und fügen die generierte Oberklasse dazwischen ein. Dies war großartig für Codeüberprüfungen - viel weniger potenziell fehleranfällige Boilerplate zum Sieben.
Scott Stanchfield

Sie sind sich nicht sicher, was Sie unter "Schreiben Sie die Attribute zweimal" verstehen - wo werden sie zweimal angezeigt?
Scott Stanchfield

@Martin K. Wenn Sie wirklich keine Reflexion verwenden möchten, auch nicht unter der Haube, dann sind Sie wahrscheinlich gezwungen, eine Art Codegenerierung durchzuführen.
extraneon

1

Sie sollten einen generischen Transformationsdienst schreiben! Verwenden Sie Generika, um den Typ frei zu halten (damit Sie jedes Objekt in key => value und back konvertieren können).

Welches Feld sollte der Schlüssel sein? Holen Sie sich dieses Feld aus der Bean und fügen Sie einen anderen nicht vorübergehenden Wert in eine Wertezuordnung ein.

Der Rückweg ist ziemlich einfach. Lesen Sie den Schlüssel (x) und schreiben Sie zuerst den Schlüssel und dann jeden Listeneintrag zurück in ein neues Objekt.

Sie können die Eigenschaftsnamen einer Bean mit den Apache Commons Beanutils erhalten !


1

Wenn Sie wirklich wirklich Leistung wollen, können Sie den Weg der Codegenerierung gehen.

Sie können dies auf eigene Faust tun, indem Sie Ihre eigenen Überlegungen anstellen und eine Mischung aus AspectJ ITD erstellen.

Oder Sie können Spring Roo verwenden und ein Spring Roo Addon erstellen . Ihr Roo-Addon wird etwas Ähnliches wie oben tun, steht jedoch jedem zur Verfügung, der Spring Roo verwendet, und Sie müssen keine Laufzeitanmerkungen verwenden.

Ich habe beides getan. Die Leute scheißen auf Spring Roo, aber es ist wirklich die umfassendste Codegenerierung für Java.


1

Ein anderer möglicher Weg ist hier.

Der BeanWrapper bietet Funktionen zum Festlegen und Abrufen von Eigenschaftswerten (einzeln oder in großen Mengen), zum Abrufen von Eigenschaftsbeschreibungen und zum Abfragen von Eigenschaften, um festzustellen, ob sie lesbar oder beschreibbar sind.

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

1

Wenn es sich um eine einfache Zuordnung von Objektbaum zu Schlüsselwertliste handelt, bei der der Schlüssel eine gepunktete Pfadbeschreibung vom Stammelement des Objekts zum zu prüfenden Blatt sein kann, ist es ziemlich offensichtlich, dass eine Baumkonvertierung in eine Schlüsselwertliste mit einer vergleichbar ist Objekt zu XML-Zuordnung. Jedes Element in einem XML-Dokument hat eine definierte Position und kann in einen Pfad konvertiert werden. Daher habe ich XStream als grundlegendes und stabiles Konvertierungswerkzeug verwendet und die hierarchischen Treiber- und Writer-Teile durch eine eigene Implementierung ersetzt. XStream verfügt außerdem über einen grundlegenden Pfadverfolgungsmechanismus, der in Kombination mit den beiden anderen streng dazu führt, dass eine Lösung für die Aufgabe geeignet ist.


1

Mit Hilfe der Jackson-Bibliothek konnte ich alle Klasseneigenschaften vom Typ String / integer / double und die entsprechenden Werte in einer Map-Klasse finden. ( ohne Reflexionen API zu verwenden! )

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

Durch die Verwendung von Gson,

  1. Konvertieren Sie POJO objectin Json
  2. Konvertiere Json in Map

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

0

Wir können die Jackson-Bibliothek verwenden, um ein Java-Objekt einfach in eine Map zu konvertieren.

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

Bei Verwendung in einem Android-Projekt können Sie Jackson wie folgt in build.gradle Ihrer App einfügen:

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'

Beispielimplementierung

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

 }

}

Ausgabe:

{name = XYZ, id = 1011, Skills = [Python, Java]}

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.