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

92

Disons 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 carte de type sécurisé (et performante). J'ai souvent besoin de convertir cet objet en paires clé-valeur (soit des chaînes ou de type safe) ou de convertir des paires clé-valeur en cet objet.

Autre que 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 convertir un message entrant dans le bon type d'objet).

Shahbaz
la source
java.beans.Introspector.getBeanInfo(). Il est intégré directement au JDK.
Marquis de Lorne

Réponses:

52

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

Maurice Perry
la source
8
De plus, il n'y a rien de mal à utiliser la réflexion, sous le capot. Surtout si les résultats sont mis en cache (pour une utilisation future), l'accès basé sur la réflexion est généralement assez rapide pour la plupart des cas d'utilisation.
StaxMan
22
Pour être précis, BeanMap (bean) est la partie spécifique des beanutils communs qui fait l'affaire.
vdr
3
Sans tenir compte des considérations de performances, j'ai trouvé que l'utilisation de BeanMap () en combinaison avec ObjectMapper de Jackson dans la réponse ci-dessous m'a donné les meilleurs résultats. BeanMap a réussi à créer une carte plus complète de mon objet, puis Jackson l'a convertie en une structure LinkedHashMap simple qui a permis des modifications plus loin dans le pipeline. objectAsMap = objectMapper.convertValue (nouveau BeanMap (monObjet), Map.class);
michaelr524
Ne fonctionnera pas sur Android car il utilise des java.beansdépendances extrêmement limitées sur la plate-forme. Voir la question associée pour plus de détails.
SqueezyMo
Une solution mentionnant Apache Commons est la plupart du temps la meilleure solution.
рüффп
177

Beaucoup de solutions potentielles, mais ajoutons juste une de plus. Utilisez Jackson (bibliothèque 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 quelques exemples supplémentaires)

Vous pouvez fondamentalement convertir tous les types compatibles: compatibles, ce qui signifie que si vous avez converti 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 les non reconnues).

Fonctionne bien pour les cas attendus, y compris les cartes, les listes, les tableaux, les primitives, les POJO de type bean.

StaxMan
la source
2
Cela devrait être la réponse acceptée. BeanUtilsest bien, mais ne peut pas gérer les tableaux et les énumérations
Manish Patel
8

La génération de code serait le seul autre moyen auquel je puisse penser. Personnellement, j'avais obtenu une solution de réflexion généralement réutilisable (à moins que cette partie du code ne soit absolument critique pour les performances). L'utilisation de JMS semble exagérée (dépendance supplémentaire, et ce n'est même pas ce à quoi elle sert). De plus, il utilise probablement aussi la réflexion sous le capot.

Michael Borgwardt
la source
La performance est essentielle, donc je pense que la réflexion est terminée. J'espérais qu'il pourrait y avoir des outils qui utilisent asm ou cglib. J'ai recommencé à examiner les tampons de protocole de Google. Je n'ai pas reçu votre commentaire sur JMS, je l'utilise pour communiquer des informations entre les machines.
Shahbaz
J'ai mal compris cela. En ce qui concerne les performances, il y a une différence entre les performances qui sont importantes et cette partie spécifique est critique pour les performances. Je ferais un benchmark pour voir à quel point cela compte vraiment par rapport à ce que fait l'application.
Michael Borgwardt
C'est aussi ma pensée. Soit vous utilisez la réflexion (directement ou indirectement), soit vous devez générer du code. (Ou écrivez vous-même le code supplémentaire, bien sûr).
extraneon
8

Il s'agit d'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;
}

Voici comment l'appeler

   Test test = new Test()
   Map<String, Object> map = ConvertObjectToMap(test);
DeXoN
la source
1
Je ne pense pas que la réponse souhaitée répète les méthodes définies sur chaque objet, cela ressemble à une réflexion.
Robert Karl
2
Je ne sais pas que vos buts sont votre méthode. Mais je pense que c'est un très excellent exemple lorsque vous voulez programmer avec des types d'objets génériques
DeXoN
5

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

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

si vous voulez un suffixe, alors faites simplement

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>
pointerne
la source
4

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 "";
                        }
                    }));
}
Bax
la source
3

JSON , par exemple en utilisant XStream + Jettison, est un format de texte simple avec des paires valeur / clé. 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.

mjn
la source
3

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;
}
berardino
la source
Cool mais enclin à StackOverflowError
Teo Choong Ping
3

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

Voici comment transformer un bean 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 serai plus qu'heureux de vous répondre.

À votre santé

Carlos

Martins
la source
J'y ai réfléchi et j'ai réalisé que ma solution précédente se cassait si vous aviez des références circulaires dans votre graphe de haricots. J'ai donc développé une méthode qui fera cela de manière transparente et gère magnifiquement les références circulaires. Vous pouvez désormais convertir un bean en map et vice-versa avec juffrou-reflect. Enjoy :)
Martins
3

Lorsque vous utilisez Spring, vous pouvez également utiliser Spring Integration object-to-map-transformer. Cela ne vaut probablement pas la peine d'ajouter Spring en tant que dépendance juste pour cela.

Pour la documentation, recherchez «Object-to-Map Transformer» sur http://docs.spring.io/spring-integration/docs/4.0.4.RELEASE/reference/html/messaging-transformation-chapter.html

Essentiellement, il parcourt le graphe d'objets entier accessible à partir de l'objet donné en entrée et produit une carte à partir de tous les champs de type primitif / chaîne sur les objets. Il peut être configuré pour générer 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 inverseur est également disponible.

Jan Żankowski
la source
2

Vous pouvez utiliser le framework Joda:

http://joda.sourceforge.net/

et profitez de JodaProperties. Cela stipule cependant que vous créez des beans d'une manière particulière et implémentez une interface spécifique. Il vous permet cependant de renvoyer une mappe de propriétés à partir d'une classe spécifique, sans réflexion. Un exemple de code est ici:

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

Jon
la source
Cela semble intéressant, malheureusement, il ne semble plus être maintenu. En outre, le mappage de propriétés qu'il renvoie est un mappage de <String, String>, donc pas de type sécurisé.
Shahbaz
1
Certes, elle n'a pas été maintenue depuis 2002. J'étais simplement curieuse de savoir s'il existait une solution sans réflexion. La carte des propriétés qu'elle renvoie n'est en fait qu'une carte standard, pas de génériques en tant que tels ...
Jon
2

Si vous ne voulez pas coder en dur les appels à 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 utiliser un objet Properties pour contenir les données réelles, et laisser chaque getter et setter simplement appeler get / set dessus? Ensuite, vous avez une structure bien adaptée à ce que vous voulez faire. Il existe même des méthodes pour les enregistrer et les charger sous forme de valeur-clé.

Thorbjørn Ravn Andersen
la source
Il n'y a pas de méthode, car cela dépend de vous ce qu'est une clé et quelle est la valeur! Il devrait être facile d'écrire un service qui effectue une telle conversion. Pour une simple représentation, CSV peut être acceptable.
Martin K.
2

La meilleure solution est d'utiliser Dozer. Vous avez juste besoin de quelque chose comme ça dans le fichier mapper:

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

Et voilà, Dozer s'occupe du reste !!!

URL de documentation du bulldozer


la source
Pourquoi nécessite-t-il un fichier de mappage? S'il sait comment convertir, pourquoi exiger ce travail supplémentaire - prenez juste l'objet source, le type attendu?
StaxMan
D'accord. Il est bon de connaître la raison, même si je ne suis pas sûr que ce soit valide :)
StaxMan
2

Il existe bien sûr le moyen de conversion 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 contienne uniquement un HashMap qui stocke les valeurs de l'instance.

Ensuite, vos getters et setters retournent 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 maintenir l'inflexibilité inhérente à un bean en vous permettant de continuer à utiliser des getters et des setters spécifiques à chaque nom de valeurs, sans avoir à écrire les getters et setters individuels.

Rodney P. Barbati
la source
2

Vous pouvez utiliser les propriétés du collecteur de filtre 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)
            );
}
Erhanasikoglu
la source
1

Mon processeur d'annotation JavaDude Bean génère du code pour ce 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 la superclasse PersonGen qui inclut une méthode createPropertyMap () qui génère une carte pour toutes les propriétés définies à l'aide de @Bean.

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

Scott Stanchfield
la source
Avez-vous fait des revues de code? Cela ne semble pas être une bonne approche! Vous modifiez le chemin d'héritage avec des annotations! Et si le haricot s'étend déjà dans une classe?! Pourquoi devez-vous écrire les attributs deux fois?
Martin K.
Si vous avez déjà une superclasse, vous utilisez @Bean (superclass = XXX.class, ...) et il insère la superclasse générée entre les deux. Cela a été idéal pour les révisions de code - beaucoup moins de passe-partout potentiellement source d'erreur à passer au crible.
Scott Stanchfield le
Vous ne savez pas ce que vous entendez par «écrivez les attributs deux fois» - où apparaissent-ils deux fois?
Scott Stanchfield
@Martin K. Si vous ne voulez vraiment pas utiliser la réflexion, même sous le capot, vous êtes probablement obligé de faire une sorte de génération de code.
extraneon
1

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

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

Le chemin du 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é d'un bean avec les beanutils apache commons !

Martin K.
la source
1

Si vous voulez vraiment des performances, vous pouvez opter pour la génération de code.

Vous pouvez le faire sur votre propre en faisant votre propre réflexion et en construisant un mixin AspectJ ITD.

Ou vous pouvez utiliser Spring Roo et créer un complément Spring Roo . Votre addon Roo fera quelque chose de similaire à ce qui précède 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 chient sur Spring Roo mais c'est vraiment la génération de code la plus complète pour Java.

Adam Gent
la source
1

Un autre moyen possible est ici.

Le 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 des 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");
Venky
la source
1

S'il s'agit d'un simple mappage d'arborescence d'objets vers une liste de valeurs clés, où la clé peut être une description de chemin en pointillé de l'élément racine de l'objet à la feuille inspectée, il est plutôt évident qu'une conversion d'arbre en liste clé-valeur est comparable à un objet au mappage 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 un outil de conversion basique et stable et j'ai remplacé les parties du pilote hiérarchique et de l'écrivain par une propre implémentation. XStream est également livré avec un mécanisme de suivi de chemin de base qui, combiné aux deux autres, conduit strictement à une solution adaptée à la tâche.

Lars Wunderlich
la source
1

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 respectives dans une classe Map. ( sans utiliser l'API Reflections! )

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());
    }
}
Amit Kaneria
la source
0

En utilisant Gson,

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

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

Nous pouvons utiliser la bibliothèque Jackson pour convertir facilement un objet Java en Map.

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

Si vous utilisez dans un projet Android, vous pouvez ajouter jackson dans build.gradle de votre application comme suit:

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'

Exemple d'implémentation

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

 }

}

Production:

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

Anubhav
la source