web-dev-qa-db-fra.com

Comment convertir un objet Java (bean) en paires clé-valeur (et inversement)?

Supposons que j’ai un objet Java très simple qui n’a que quelques propriétés getXXX et setXXX. Cet objet est utilisé uniquement pour gérer des valeurs, essentiellement un enregistrement ou une mappe sécurisée par le type (et performante). J'ai souvent besoin de convertir cet objet en paires clé-valeur (soit des chaînes ou un type safe) ou de convertir des paires clé-valeur en cet objet.

Outre la réflexion ou l'écriture manuelle de code pour effectuer cette conversion, quel est le meilleur moyen d'y parvenir?

Un exemple pourrait être l'envoi de cet objet via jms, sans utiliser le type ObjectMessage (ou la conversion d'un message entrant en un type d'objet approprié).

84
Shahbaz

Il y a toujours Apache commons beanutils mais bien sûr, il utilise la réflexion sous le capot

48
Maurice Perry

Beaucoup de solutions potentielles, mais ajoutons-en une seule. Utilisez Jackson (lib. De traitement JSON) pour effectuer une conversion "sans json", comme:

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

( cette entrée de blog a encore d'autres exemples)

Vous pouvez en principe convertir tous les types compatibles: compatible, ce qui signifie que si vous convertissiez du type en JSON, et de ce JSON en type de résultat, les entrées correspondraient (si elles sont configurées correctement, elles peuvent également ignorer celles non reconnues).

Fonctionne bien dans les cas auxquels on pourrait s’attendre, notamment les cartes, les listes, les tableaux, les primitives et les POJO de type haricot.

165
StaxMan

La génération de code serait la seule autre solution à laquelle je puisse penser. Personnellement, je possédais une solution de réflexion généralement réutilisable (à moins que cette partie du code ne dépende absolument de la performance). L'utilisation de JMS ressemble à du surmenage (dépendance supplémentaire, et ce n'est même pas ce que cela signifie). En outre, il utilise probablement aussi la réflexion sous le capot.

8
Michael Borgwardt

Ceci est une méthode pour convertir un objet Java en 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;
}

C'est comment l'appeler

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

Avec Java 8, vous pouvez essayer ceci:

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

JSON , utilisant par exemple XStream + Jettison, est un format de texte simple avec des paires clé-valeur. Il est pris en charge, par exemple, par le courtier de messages Apache ActiveMQ JMS pour l'échange d'objets Java avec d'autres plates-formes/langages.

3
mjn

Lorsque vous utilisez Spring, vous pouvez également utiliser l’objet-à-mapper-le-transformateur de Spring Integration. Cela ne vaut probablement pas la peine d’ajouter Spring comme dépendance.

Pour la documentation, recherchez "Transformer d’objet à carte" sur http://docs.spring.io/spring-integration/docs/4.0.4.RELEASE/reference/html/messaging-transformation-chapter.html

Essentiellement, il parcourt l'intégralité du graphe d'objet accessible depuis l'objet spécifié en entrée et génère une carte à partir de tous les champs de type primitif/Chaîne des objets. Il peut être configuré pour produire soit:

  • une carte plate: {rootObject.someField = Joe, rootObject.leafObject.someField = Jane}, ou
  • une carte structurée: {someField = Joe, leafObject = {someField = Jane}}.

Voici un exemple de leur page:

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
}

La sortie sera: 

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

Un transformateur inversé est également disponible.

3
Jan Żankowski

Utilisez juffrou-reflect 'BeanWrapper. C'est très performant. 

Voici comment transformer un haricot en carte:

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

J'ai développé Juffrou moi-même. C'est open source, vous êtes donc libre de l'utiliser et de le modifier. Et si vous avez des questions à ce sujet, je me ferai un plaisir de vous répondre.

À votre santé

Carlos

3
Martins

En utilisant simplement la réflexion et 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;
}
3
berardino

La meilleure solution consiste à utiliser Dozer. Vous avez juste besoin de quelque chose comme ça dans le fichier mappeur:

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

Et c'est tout, Dozer s'occupe du reste !!!

URL Documentation Dozer

3
Guillo

Si vous ne souhaitez pas coder en dur les appels de chaque getter et setter, la réflexion est le seul moyen d'appeler ces méthodes (mais ce n'est pas difficile).

Pouvez-vous refactoriser la classe en question pour qu’elle utilise un objet Properties pour contenir les données réelles et laisse chaque getter et setter appeler simplement get/set dessus? Ensuite, vous avez une structure bien adaptée à ce que vous voulez faire. Il existe même des méthodes pour enregistrer et charger les fichiers dans le formulaire clé-valeur.

Il existe bien sûr le moyen de conversion le plus simple et le plus simple possible - pas de conversion du tout! 

au lieu d'utiliser des variables privées définies dans la classe, faites en sorte que la classe ne contienne qu'un HashMap qui stocke les valeurs de l'instance.

Ensuite, vos getters et setters reviennent et définissent des valeurs dans et hors du HashMap, et quand il est temps de le convertir en carte, le tour est joué! - c'est déjà une carte.

Avec un peu de magie AOP, vous pouvez même conserver l’inflexibilité inhérente à un grain en vous permettant d’utiliser toujours des accesseurs et des setters spécifiques à chaque nom de valeur, sans avoir à écrire réellement les accesseurs individuels.

2
Rodney P. Barbati

Vous pouvez utiliser le framework Joda:

http://joda.sourceforge.net/

et profitez de JodaProperties. Cela stipule toutefois que vous créez des beans d'une manière particulière et implémentez une interface spécifique. Cependant, il vous permet toutefois de renvoyer une carte de propriétés d'une classe spécifique, sans réflexion. Le code de l'échantillon est ici:

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

2
Jon

Vous pouvez utiliser les propriétés du collecteur de filtres de flux 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)
            );
}
2
erhanasikoglu

Mon processeur d'annotation de bean JavaDude génère un code pour le faire. 

http://javadude.googlecode.com

Par exemple:

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

Ce qui précède génère une super-classe PersonGen comprenant une méthode createPropertyMap () qui génère une mappe pour toutes les propriétés définies à l'aide de @Bean.

(Notez que je modifie légèrement l'API pour la version suivante - l'attribut d'annotation sera defineCreatePropertyMap = true)

1
Scott Stanchfield

Avec l'aide de la bibliothèque Jackson, j'ai pu trouver toutes les propriétés de classe de type String/integer/double et les valeurs correspondantes dans une classe Map. ( sans utiliser les réflexions 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());
    }
}
1
Amit Kaneria

Vous devriez écrire un service de transformation générique! Utilisez des génériques pour que le type reste libre (vous pouvez ainsi convertir chaque objet en clé => valeur et retour).

Quel champ devrait être la clé? Extrayez ce champ du bean et ajoutez toute autre valeur non transitoire dans une mappe de valeurs. 

Le retour est assez facile. Lisez la clé (x) et écrivez d’abord la clé, puis chaque entrée de la liste dans un nouvel objet. 

Vous pouvez obtenir les noms de propriétés d'un haricot avec le Apache commons beanutils !

1
Martin K.

Un autre moyen possible est ici.

BeanWrapper offre des fonctionnalités pour définir et obtenir des valeurs de propriété (individuellement Ou en bloc), obtenir des descripteurs de propriété et interroger les propriétés pour déterminer si elles sont lisibles ou inscriptibles.

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

Probablement en retard à la fête. Vous pouvez utiliser Jackson et le convertir en objet Properties. Cela convient aux classes imbriquées et si vous voulez que la clé soit dans la valeur for.b.c =.

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

si vous voulez un suffixe, alors faites juste

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

besoin d'ajouter cette dépendance

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

Si vous voulez vraiment la performance, vous pouvez choisir la génération de code.

Vous pouvez le faire vous-même en faisant votre propre réflexion et en créant un mélange AspectJ ITD.

Ou vous pouvez utiliser Spring Roo et faire un Spring Roo Addon . Votre addon Roo fera quelque chose de similaire à ce qui est décrit ci-dessus mais sera disponible pour tous ceux qui utilisent Spring Roo et vous n'avez pas à utiliser les annotations d'exécution.

J'ai fait les deux. Les gens craignent Spring Roo, mais c’est vraiment la génération de code la plus complète pour Java.

1
Adam Gent

S'il s'agit d'un mappage simple d'une arborescence d'objets à une liste de valeurs clés, où clé peut être une description de chemin d'accès en pointillé allant de l'élément racine de l'objet à la feuille inspectée, il est assez évident qu'une conversion d'arborescence en une liste de valeurs clés est comparable à une mappage d'objet en XML. Chaque élément d'un document XML a une position définie et peut être converti en chemin. Par conséquent, j’ai pris XStream comme outil de conversion de base stable, et j’ai remplacé les composants de pilote et d’écrivain hiérarchiques par une implémentation propre. XStream est également livré avec un mécanisme de base de suivi de chemin qui, combiné aux deux autres, conduit strictement à une solution adaptée à la tâche.

1
Lars Wunderlich

En utilisant Gson,

  1. Convertir POJO object en Json
  2. Convertir Json en carte

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