Come convertire un oggetto Java (bean) in coppie chiave-valore (e viceversa)?


91

Supponiamo di avere un oggetto java molto semplice che ha solo alcune proprietà getXXX e setXXX. Questo oggetto viene utilizzato solo per gestire i valori, fondamentalmente un record o una mappa indipendente dai tipi (e performante). Ho spesso bisogno di convertire questo oggetto in coppie di valori chiave (stringhe o di tipo sicuro) o convertire da coppie di valori chiave a questo oggetto.

Oltre alla riflessione o alla scrittura manuale del codice per eseguire questa conversione, qual è il modo migliore per ottenere ciò?

Un esempio potrebbe essere l'invio di questo oggetto su jms, senza utilizzare il tipo ObjectMessage (o convertire un messaggio in arrivo nel tipo di oggetto corretto).


java.beans.Introspector.getBeanInfo(). È integrato direttamente nel JDK.
Marchese di Lorne

Risposte:


52

C'è sempre apache commons beanutils ma ovviamente usa la riflessione sotto il cofano


8
Inoltre, non c'è niente di sbagliato nell'usare il riflesso, sotto il cofano. Soprattutto se i risultati vengono memorizzati nella cache (per un utilizzo futuro), l'accesso basato sulla riflessione è generalmente abbastanza veloce per la maggior parte dei casi d'uso.
StaxMan

22
Per essere precisi, BeanMap (bean) è la parte specifica dei beanutils comuni che fa il trucco.
vdr

3
Ignorando le considerazioni sulle prestazioni, ho scoperto che l'uso di BeanMap () in combinazione con ObjectMapper di Jackson dalla risposta di seguito mi ha dato i migliori risultati. BeanMap è riuscito a creare una mappa più completa del mio oggetto e poi Jackson l'ha convertita in una semplice struttura LinkedHashMap che ha permesso modifiche più avanti nella pipeline. objectAsMap = objectMapper.convertValue (new BeanMap (myObject), Map.class);
michaelr524

Non funzionerà su Android perché utilizza java.beansdipendenze fortemente limitate sulla piattaforma. Vedere la domanda correlata per i dettagli.
SqueezyMo

Una soluzione che menziona Apache Commons è il più delle volte la soluzione migliore.
рüффп

177

Molte potenziali soluzioni, ma aggiungiamone solo un'altra. Usa Jackson (libreria di elaborazione JSON) per eseguire la conversione "senza json", come:

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

( questo post di blog contiene altri esempi)

Puoi fondamentalmente convertire qualsiasi tipo compatibile: compatibile significa che se hai convertito dal tipo a JSON e da quel JSON al tipo di risultato, le voci corrisponderebbero (se configurate correttamente puoi anche ignorare quelle non riconosciute).

Funziona bene per i casi che ci si aspetterebbe, comprese mappe, elenchi, array, primitive, POJO simili a bean.


2
Questa dovrebbe essere la risposta accettata. BeanUtilsè carino, ma non può gestire array ed enumerazioni
Manish Patel

8

La generazione di codice sarebbe l'unico altro modo a cui riesco a pensare. Personalmente, avevo una soluzione di riflessione generalmente riutilizzabile (a meno che quella parte del codice non sia assolutamente critica per le prestazioni). Usare JMS suona come eccessivo (dipendenza aggiuntiva, e non è nemmeno quello per cui è pensato). Inoltre, probabilmente usa anche il riflesso sotto il cofano.


Le prestazioni sono fondamentali, quindi penso che la riflessione sia fuori luogo. Speravo che potessero esserci alcuni strumenti che usano asm o cglib. Ho ricominciato a esaminare i buffer di protocollo di Google. Non ho ricevuto il tuo commento su JMS, lo sto usando per comunicare informazioni tra macchine.
Shahbaz

L'ho capito male. Per quanto riguarda le prestazioni, c'è una differenza tra le prestazioni che sono importanti e quella parte specifica che è critica per le prestazioni. Farei un benchmark per vedere quanto conta davvero rispetto a cos'altro sta facendo l'applicazione.
Michael Borgwardt

Questo è anche il mio pensiero. O usi la riflessione (direttamente o indirettamente) o devi generare codice. (Oppure scrivi tu stesso il codice aggiuntivo, ovviamente).
extraneon

8

Questo è un metodo per convertire un oggetto Java in una mappa

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

Ecco come chiamarlo

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

1
Non credo che la risposta desiderata si ripeta sui metodi definiti su ogni oggetto, suona come un riflesso.
Robert Karl

2
Non so che i tuoi scopi siano il tuo metodo. Ma penso che questo sia un esempio molto eccellente quando vuoi programmare con tipi generici di oggetti
DeXoN

5

Probabilmente in ritardo alla festa. Puoi usare Jackson e convertirlo in un oggetto Properties. Questo è adatto per le classi annidate e se si desidera la chiave nel valore for abc =.

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

se vuoi un suffisso, fallo e basta

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

è necessario aggiungere questa dipendenza

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

4

Con Java 8 puoi provare questo:

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 , ad esempio utilizzando XStream + Jettison, è un semplice formato di testo con coppie di valori chiave. È supportato ad esempio dal broker di messaggi JMS Apache ActiveMQ per lo scambio di oggetti Java con altre piattaforme / linguaggi.


3

Semplicemente usando la riflessione e 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;
}

Fantastico ma incline a StackOverflowError
Teo Choong Ping

3

Usa il BeanWrapper di juffrou- reflection. È molto performante.

Ecco come trasformare un fagiolo in una mappa:

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

Ho sviluppato Juffrou da solo. È open source, quindi sei libero di usarlo e modificarlo. E se hai domande in merito, sarò più che felice di rispondere.

Saluti

Carlos


Ci ho pensato e mi sono reso conto che la mia soluzione precedente non funziona se hai riferimenti circolari nel tuo grafico a fagiolo. Quindi ho sviluppato un metodo che lo farà in modo trasparente e gestirà magnificamente i riferimenti circolari. Ora puoi convertire un fagiolo in una mappa e viceversa con juffrou-riflettere. Buon divertimento :)
Martins

3

Quando si utilizza Spring, è possibile utilizzare anche il trasformatore da oggetto a mappa di Spring Integration. Probabilmente non vale la pena aggiungere Spring come dipendenza solo per questo.

Per la documentazione, cerca "Object-to-Map Transformer" su http://docs.spring.io/spring-integration/docs/4.0.4.RELEASE/reference/html/messaging-transformation-chapter.html

Essenzialmente, attraversa l'intero oggetto grafico raggiungibile dall'oggetto dato come input e produce una mappa da tutti i campi di tipo / stringa primitivi sugli oggetti. Può essere configurato per emettere:

  • una mappa piatta: {rootObject.someField = Joe, rootObject.leafObject.someField = Jane} o
  • una mappa strutturata: {someField = Joe, leafObject = {someField = Jane}}.

Ecco un esempio dalla loro pagina:

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
}

L'output sarà:

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

È disponibile anche un trasformatore inverso.


2

Puoi usare il framework Joda:

http://joda.sourceforge.net/

e approfitta di JodaProperties. Tuttavia, ciò stabilisce che si creino i bean in un modo particolare e si implementi un'interfaccia specifica. Tuttavia, ti consente di restituire una mappa delle proprietà da una classe specifica, senza riflessioni. Il codice di esempio è qui:

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


Sembra interessante, purtroppo non sembra più essere mantenuto. Inoltre, la mappa delle proprietà che restituisce è una mappa di <String, String>, quindi non indipendente dai caratteri.
Shahbaz

1
È vero, non è stato mantenuto dal 2002. Ero solo curioso di sapere se esistesse una soluzione basata sulla non riflessione. La mappa delle proprietà che restituisce è in realtà solo una mappa standard, senza farmaci generici in quanto tali ...
Jon

2

Se non vuoi codificare le chiamate a ciascun getter e setter, la riflessione è l'unico modo per chiamare questi metodi (ma non è difficile).

È possibile eseguire il refactoring della classe in questione per utilizzare un oggetto Properties per contenere i dati effettivi e consentire a ogni getter e setter di chiamare get / set su di esso? Allora hai una struttura adatta a quello che vuoi fare. Esistono anche metodi per salvarli e caricarli nel modulo valore-chiave.


Non ci sono metodi, perché dipende da te cos'è una chiave e cos'è un valore! Dovrebbe essere facile scrivere un servizio che faccia una tale conversione. Per una semplice rappresentazione CSV potrebbe essere accettabile.
Martin K.

2

La soluzione migliore è usare Dozer. Hai solo bisogno di qualcosa di simile nel file mapper:

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

E questo è tutto, Dozer si occupa del resto !!!

URL della documentazione di Dozer


Perché richiede un file di mappatura? Se sa come convertire, perché richiedere questo lavoro extra: basta prendere l'oggetto sorgente, il tipo previsto?
StaxMan

Ok. È bello conoscere il motivo, anche se non sono sicuro che sia valido :)
StaxMan

2

Ovviamente c'è il mezzo di conversione più semplice in assoluto possibile: nessuna conversione!

invece di utilizzare variabili private definite nella classe, fare in modo che la classe contenga solo una HashMap che memorizza i valori per l'istanza.

Quindi i tuoi getter e setter tornano e impostano i valori dentro e fuori HashMap, e quando è il momento di convertirlo in una mappa, voilà! - è già una mappa.

Con un po 'di magia AOP, potresti persino mantenere la rigidità insita in un bean permettendoti di usare ancora getter e setter specifici per ogni nome di valore, senza dover effettivamente scrivere i singoli getter e setter.


2

È possibile utilizzare le proprietà del raccoglitore del filtro di flusso 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

Il mio JavaDude Bean Annotation Processor genera il codice per farlo.

http://javadude.googlecode.com

Per esempio:

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

Quanto sopra genera la superclasse PersonGen che include un metodo createPropertyMap () che genera una mappa per tutte le proprietà definite usando @Bean.

(Nota che sto cambiando leggermente l'API per la prossima versione: l'attributo di annotazione sarà defineCreatePropertyMap = true)


Hai eseguito revisioni del codice? Questo non sembra essere un buon approccio! Cambia il percorso di ereditarietà con le annotazioni! E se il fagiolo si estende già a una lezione ?! Perché devi scrivere gli attributi due volte?
Martin K.

Se hai già una superclasse, usi @Bean (superclass = XXX.class, ...) e inserisce la superclasse generata nel mezzo. Questo è stato ottimo per le revisioni del codice, ma molto meno potenzialmente soggetto a errori da setacciare.
Scott Stanchfield

Non sei sicuro di cosa intendi per "scrivi due volte gli attributi" - dove vengono visualizzati due volte?
Scott Stanchfield

@Martin K. Se davvero non vuoi usare la riflessione, anche sotto il cofano, allora probabilmente sei costretto a fare una sorta di generazione di codice.
extraneon

1

Dovresti scrivere un servizio di trasformazione generico! Usa i generici per mantenerlo libero (così puoi convertire ogni oggetto in chiave => valore e viceversa).

Quale campo dovrebbe essere la chiave? Ottieni quel campo dal bean e aggiungi qualsiasi altro valore non transitorio in una mappa di valori.

La via del ritorno è abbastanza facile. Leggere la chiave (x) e scrivere prima la chiave e poi ogni voce dell'elenco in un nuovo oggetto.

Puoi ottenere i nomi delle proprietà di un bean con apache commons beanutils !


1

Se vuoi davvero le prestazioni puoi seguire la strada della generazione del codice.

Puoi farlo da solo facendo la tua riflessione e costruendo un mix in AspectJ ITD.

Oppure puoi usare Spring Roo e creare un Addon Spring Roo . Il tuo componente aggiuntivo Roo farà qualcosa di simile a quanto sopra ma sarà disponibile per tutti coloro che utilizzano Spring Roo e non è necessario utilizzare le annotazioni di runtime.

Ho fatto entrambe le cose. La gente fa schifo su Spring Roo ma è davvero la generazione di codice più completa per Java.


1

Un altro modo possibile è qui.

BeanWrapper offre funzionalità per impostare e ottenere valori di proprietà (singolarmente o in blocco), ottenere descrittori di proprietà e eseguire query sulle proprietà per determinare se sono leggibili o scrivibili.

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

1

Se si tratta di un semplice albero di oggetti per la mappatura di un elenco di valori chiave, dove la chiave potrebbe essere una descrizione del percorso punteggiata dall'elemento radice dell'oggetto alla foglia che viene ispezionata, è piuttosto ovvio che una conversione dell'albero in un elenco di valori-chiave è paragonabile a un oggetto per la mappatura xml. Ogni elemento all'interno di un documento XML ha una posizione definita e può essere convertito in un percorso. Pertanto ho preso XStream come strumento di conversione di base e stabile e ho sostituito il driver gerarchico e le parti del writer con un'implementazione propria. XStream è inoltre dotato di un meccanismo di tracciamento del percorso di base che, combinato con gli altri due, porta strettamente a una soluzione adatta all'attività.


1

Con l'aiuto della libreria Jackson, sono stato in grado di trovare tutte le proprietà della classe di tipo String / integer / double e i rispettivi valori in una classe Map. ( senza usare le riflessioni 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

Utilizzando Gson,

  1. Converti POJO objectin Json
  2. Converti Json in mappa

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

0

Possiamo usare la libreria Jackson per convertire facilmente un oggetto Java in una mappa.

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

Se utilizzi in un progetto Android, puoi aggiungere jackson nel file build.gradle della tua app come segue:

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'

Implementazione di esempio

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

 }

}

Produzione:

{nome = XYZ, id = 1011, competenze = [python, java]}

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.