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é).
Il y a toujours Apache commons beanutils mais bien sûr, il utilise la réflexion sous le capot
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.
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.
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);
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 "";
}
}));
}
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.
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:
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.
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
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;
}
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 !!!
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.
Vous pouvez utiliser le framework Joda:
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
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)
);
}
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)
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());
}
}
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 !
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");
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>
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.
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.
En utilisant Gson,
object
en JsonConvertir Json en carte
retMap = new Gson().fromJson(new Gson().toJson(object),
new TypeToken<HashMap<String, Object>>() {}.getType()
);