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

Diciamo che ho un object java molto semplice che ha solo alcune proprietà getXXX e setXXX. Questo object è usato solo per gestire i valori, in pratica una mappa o una mappa sicura del tipo (e performante). Spesso ho bisogno di coprire questo object con coppie di valori chiave (stringhe o tipo safe) o convertire da coppie di valori chiave a questo object.

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

Un esempio potrebbe essere l’invio di questo object su jms, senza utilizzare il tipo ObjectMessage (o la conversione di un messaggio in arrivo nel giusto tipo di object).

Ci sono sempre dei bean-bean comuni, ma ovviamente usa la riflessione sotto il cofano

Un sacco di soluzioni potenziali, ma aggiungiamo solo un altro. Usa Jackson (lib di elaborazione JSON) per eseguire la conversione “json-less”, come:

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

( questo post sul blog ha alcuni altri esempi)

In pratica puoi convertire qualsiasi tipo compatibile: compatibile nel senso che se hai convertito da tipo a JSON e da quel JSON a tipo di risultato, le voci corrispondevano (se configurato correttamente può anche ignorare quelle non riconosciute).

Funziona bene per i casi che ci si aspetterebbe, tra cui mappe, elenchi, matrici, primitivi, bean-like POJO.

La generazione del codice sarebbe l’unico altro modo in cui riesca a pensare. Personalmente, ho avuto una soluzione di riflessione generalmente riutilizzabile (a meno che quella parte del codice sia assolutamente critica per le prestazioni). L’uso di JMS suona come un eccesso (dipendenza aggiuntiva, e non è nemmeno quello per cui è pensato). Inoltre, probabilmente usa il riflesso anche sotto il cofano.

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

 public static Map ConvertObjectToMap(Object obj) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { Class< ?> pomclass = obj.getClass(); pomclass = obj.getClass(); Method[] methods = obj.getClass().getMethods(); Map map = new HashMap(); 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; } 

Questo è come chiamarlo

  Test test = new Test() Map map = ConvertObjectToMap(test); 

Con Java 8 puoi provare questo:

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

JSON , per esempio usando XStream + Jettison, è un semplice formato di testo con coppie di valori chiave. È supportato ad esempio dal broker di messaggi JMS di Apache ActiveMQ per lo scambio di oggetti Java con altre piattaforms / lingue.

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

  org.dozer.vo.map.SomeComplexType java.util.Map  

E questo è tutto, Dozer si prende cura di tutto il resto !!!

URL di documentazione del bulldozer

Semplicemente usando reflection 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; } 

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

Ecco come trasformare un bean in una mappa:

 public static Map getBeanMap(Object bean) { Map beanMap = new HashMap(); BeanWrapper beanWrapper = new BeanWrapper(BeanWrapperContext.create(bean.getClass())); for(String propertyName : beanWrapper.getPropertyNames()) beanMap.put(propertyName, beanWrapper.getValue(propertyName)); return beanMap; } 

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

Saluti

Carlos

Quando si usa Spring, si può usare anche l’object Spring Integration object-to-map-transformsr. Probabilmente non vale la pena aggiungere Spring come dipendenza solo per questo.

Per la documentazione, cercare “Trasformatore da object a mappa” su http://docs.spring.io/spring-integration/docs/4.0.4.RELEASE/reference/html/messaging-transformation-chapter.html

Essenzialmente, attraversa l’intero grafo dell’object raggiungibile dall’object dato come input e produce una mappa da tutti i campi di tipo primitivo / String sugli oggetti. Può essere configurato per emettere:

  • una mappa piana: {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 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 reversibile.

Potresti usare il framework Joda:

http://joda.sourceforge.net/

e approfittare di JodaProperties. Ciò presuppone comunque di creare bean in un modo particolare e di implementare un’interfaccia specifica. Tuttavia, ti consente di restituire una mappa delle proprietà da una class specifica, senza riflessione. Il codice di esempio è qui:

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

Se non si desidera effettuare l’hardcode delle chiamate a ciascun getter e setter, la reflection è l’unico modo per chiamare questi metodi (ma non è difficile).

Puoi refactoring la class in questione per utilizzare un object Properties per contenere i dati effettivi e lasciare che ogni getter e setter richiamino semplicemente get / set su di esso? Allora hai una struttura ben adattata per quello che vuoi fare. Esistono anche metodi per salvarli e caricarli nel modulo valore-chiave.

C’è ovviamente il modo più semplice di conversione ansible – nessuna conversione!

invece di utilizzare variabili private definite nella class, rendere la class contenente solo una HashMap che memorizza i valori per l’istanza.

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

Con un po ‘di magia AOP, potresti persino mantenere l’inflessibilità intrinseca in un bean, consentendo di utilizzare ancora getter e setter specifici per ciascun nome di valori, senza dover scrivere effettivamente i singoli getter e setter.

È ansible utilizzare le proprietà del collector del filtro dello stream di java 8,

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

Il mio processore di annotazione bean di JavaDude genera codice per fare ciò.

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 superclass 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 annotation sarà defineCreatePropertyMap = true)

Dovresti scrivere un servizio di trasformazione generico! Usa i generici per mantenerlo privo di caratteri (così puoi convertire ogni object in chiave => valore e ritorno).

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 della lista di nuovo su un nuovo object.

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

Se davvero vuoi prestazioni, puoi seguire il percorso di generazione del codice.

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

Oppure puoi usare Spring Roo e creare un Addon Roo Spring . Il tuo addon Roo farà qualcosa di simile a quanto sopra ma sarà disponibile per tutti coloro che usano Spring Roo e non devi usare 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.

Un altro modo ansible è qui.

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

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

Se si tratta di una semplice struttura ad oggetti per mappare la lista valori chiave, dove la chiave potrebbe essere una descrizione di percorso tratteggiata dall’elemento radice dell’object alla foglia che viene ispezionata, è piuttosto ovvio che una conversione ad albero in una lista valori-chiave è paragonabile ad una object al mapping 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 uno strumento di conversione di base e stabile e ho sostituito le parti gerarchiche di driver e scrittore con una propria implementazione. XStream viene anche fornito con un meccanismo di tracciamento di base che, combinato con gli altri due, porta strettamente a una soluzione adeguata all’attività.

Con l’aiuto della libreria di Jackson, sono stato in grado di trovare tutte le proprietà di class di tipo String / intero / doppio e rispettivi valori in una class Map. ( senza usare le riflessioni api! )

 TestClass testObject = new TestClass(); com.fasterxml.jackson.databind.ObjectMapper m = new com.fasterxml.jackson.databind.ObjectMapper(); Map props = m.convertValue(testObject, Map.class); for(Map.Entry entry : props.entrySet()){ if(entry.getValue() instanceof String || entry.getValue() instanceof Integer || entry.getValue() instanceof Double){ System.out.println(entry.getKey() + "-->" + entry.getValue()); } } 

Probabilmente in ritardo per la festa. Puoi usare Jackson e convertirlo in un object Proprietà. Questo è adatto per le classi annidate e se si desidera la chiave nel valore per abc =.

 JavaPropsMapper mapper = new JavaPropsMapper(); Properties properties = mapper.writeValueAsProperties(sct); Map map = properties; 

se vuoi un suffisso, fallo e basta

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

bisogno di aggiungere questa dipendenza

  com.fasterxml.jackson.dataformat jackson-dataformat-properties