2014-09-02 7 views
7

Ich bin bei Java Bean-Map Umwandlung fest. Es gibt viele Ressourcen im Internet, aber leider behandeln alle das Konvertieren einfacher Beans nach Maps. Meine sind ein bisschen umfangreicher.Abflachen Java Bean auf eine Karte

Es gibt vereinfachtes Beispiel:

public class MyBean { 

    private String firstName; 
    private String lastName; 
    private MyHomeAddress homeAddress; 
    private int age; 

    // getters & setters 

} 

Mein Punkt ist Map<String, Object> herzustellen, die in diesem Fall für die folgenden Bedingungen erfüllt ist:

map.containsKey("firstName") 
map.containsKey("lastName") 
map.containsKey("homeAddress.street") // street is String 
map.containsKey("homeAddress.number") // number is int 
map.containsKey("homeAddress.city") // city is String 
map.containsKey("homeAddress.zipcode") // zipcode is String 
map.containsKey("age") 

Ich habe versucht, Apache Commons BeanUtils verwenden. Beide Ansätze BeanUtils#describe(Object) und BeanMap(Object) erzeugen eine Karte, deren "tiefes Niveau" 1 ist (ich meine, dass es nur "homeAddress" Schlüssel gibt, die MyHomeAddress Objekt als Wert halten). Meine Methode sollte die Objekte tiefer und tiefer eingeben, bis sie einen primitiven Typ (oder String) treffen, dann sollte sie aufhören zu graben und einen Schlüssel, d. H. "order.customer.contactInfo.home", einfügen.

Also, meine Frage ist: Wie kann es leicht erlaufen (oder gibt es bereits bestehendes Projekt, das mir, das zu tun erlauben würde) getan werden?

Update

Ich habe Radiodef erweitert Antwort auch Sammlungen, Karten Arrays und Aufzählungen enthalten:

private static boolean isValue(Object value) { 
    final Class<?> clazz = value.getClass(); 
    if (value == null || 
     valueClasses.contains(clazz) || 
     Collection.class.isAssignableFrom(clazz) || 
     Map.class.isAssignableFrom(clazz) || 
     value.getClass().isArray() || 
     value.getClass().isEnum()) { 
    return true; 
    } 
    return false; 
} 
+0

Wahrscheinlich meinst du nicht "primitiv", da 'String' kein primitives Element ist (es erweitert' Object'). Sie benötigen also eine Möglichkeit, dem Algorithmus mitzuteilen, welche Klassen durchlaufen werden sollen und welche Werte als Werte verwendet werden sollen. Daher wird es wahrscheinlich keine Möglichkeit geben, dies ohne irgendeine Art von Konfiguration zu tun (möglicherweise unter Verwendung von Anmerkungen). – Tonio

+1

Dies kann mit Reflektion und Rekursion gemacht werden, Sie müssen es fast sicher selbst schreiben. Beachten Sie, dass die Frage im Moment geschlossen wird, da die Abfrage von Bibliotheksempfehlungen nicht in der Liste enthalten ist. – Radiodef

+0

Tonio, Radiodef - danke für deine Vorschläge, ich habe meinen Post bearbeitet. –

Antwort

5

Hier ist eine einfache reflektierende/rekursive Beispiel.

Sie sollten sich bewusst sein, dass es einige Probleme mit einer Umwandlung der Art und Weise tun Sie gefragt haben:

  • Map Schlüssel muss eindeutig sein.
  • Java ermöglicht Klassen, ihren privaten Feldern den gleichen Namen wie einem privaten Feld zu geben, das einer geerbten Klasse gehört.

Dieses Beispiel bezieht sich nicht auf diejenigen, weil ich nicht sicher bin, wie Sie für sie erklären wollen (wenn Sie tun). Wenn Ihre Beans von etwas anderem als Object erben, müssen Sie Ihre Idee ein wenig ändern. In diesem Beispiel werden nur die Felder der Unterklasse berücksichtigt.

Mit anderen Worten, wenn Sie

public class SubBean extends Bean { 

dieses Beispiel haben nur zurückgeben Felder aus SubBean.

Java läßt uns diese wahnsinnige Sache tun:

package com.company.util; 
public class Bean { 
    private int value; 
} 

package com.company.misc; 
public class Bean extends com.company.util.Bean { 
    private int value; 
} 

Nicht, dass irgendjemand das tun soll, aber es ist ein Problem, wenn Sie String als Schlüssel verwenden mögen.

ist hier teh codez:

import java.lang.reflect.*; 
import java.util.*; 

public final class BeanFlattener { 
    private BeanFlattener() {} 

    public static Map<String, Object> deepToMap(Object bean) 
    throws IllegalAccessException { 
     Map<String, Object> map = new LinkedHashMap<>(); 

     putValues(bean, map, null); 

     return map; 
    } 

    private static void putValues(
     Object bean, Map<String, Object> map, String prefix 
    ) throws IllegalAccessException { 
     Class<?> cls = bean.getClass(); 

     for(Field field : cls.getDeclaredFields()) { 
      field.setAccessible(true); 

      Object value = field.get(bean); 
      String key; 
      if(prefix == null) { 
       key = field.getName(); 
      } else { 
       key = prefix + "." + field.getName(); 
      } 

      if(isValue(value)) { 
       map.put(key, value); 
      } else { 
       putValues(value, map, key); 
      } 
     } 
    } 

    private static final Set<Class<?>> valueClasses = (
     Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
      Object.class, String.class, Boolean.class, 
      Character.class, Byte.class, Short.class, 
      Integer.class, Long.class, Float.class, 
      Double.class 
     ))) 
    ); 

    private static boolean isValue(Object value) { 
     return value == null || valueClasses.contains(value.getClass()); 
    } 
} 
+0

Danke, diese Implementierung funktioniert wie ein Zauber. Ich habe 'isValue (Object)' Methode erweitert, um auch andere Klassen einzuschließen, die in meinem Projekt benötigt wurden (siehe mein Beitrag). –

+0

Vielen Dank für das tolle Stück Code @Radiodef. Ich habe einige Komponententests für diesen Code geschrieben, und ich plane, es in einer API zu verwenden, ich habe mich gefragt, ob jemand mit einem Fehler in diesem Code konfrontiert wird? – AngelThread

+1

@AngelThread Ich schaue hier nochmal kurz auf meinen Code und das einzige offensichtliche Problem, das ich sehe, ist, dass es einen 'StackOverflowError' oder' OutOfMemoryError' verursacht, wenn es ein Objekt mit einem Zirkelverweis passiert, zum Beispiel 'class A {B b; } 'und' Klasse B {A a; } '. Solch ein Objekt wird vielleicht nicht als Bean betrachtet, aber wenn ich diese Methode nochmal schreiben würde, würde ich solche Fälle behandeln. Ich denke, Sie hätten eine 'Set Werte;' und jedes Mal, wenn Sie ein Feld hinzufügen, das kein Werttyp ist, würden Sie zuerst die 'Set' überprüfen, um zu sehen, ob der Wert bereits hinzugefügt wurde. Wenn es in der Menge ist, rekrutieren Sie nicht. – Radiodef

1

Sie immer die Jackson Json Processor nutzen könnten.Wie folgt:

import com.fasterxml.jackson.databind.ObjectMapper; 
//... 
ObjectMapper objectMapper = new ObjectMapper(); 
//... 
@SuppressWarnings("unchecked") 
Map<String, Object> map = objectMapper.convertValue(pojo, Map.class); 

wo pojo ist einige Java-Bean. Sie können einige nette Anmerkungen zur Bean verwenden, um die Serialisierung zu steuern.

Sie können den ObjectMapper erneut verwenden.

+0

ConvertValue() -Methode flacht innere Objekte nicht ab, zum Beispiel habe ich eine Cat-Klasse, die ein Attribut hat, das Freund genannt wird, und dieses Attribut ist auch Cat-Typ. Wenn ich die Instanz der Cat-Klasse mit der convertValue-Methode konvertiere, wird es wie folgt aussehen. {friend = {friend = null, childCount = 2, name = gelb}, childCount = 1, name = red} – AngelThread

+0

@AngelThread Die Glättung wird nicht durchgeführt, aber innere Objekte können je nach Typ konvertiert werden (z. B. innere Karten) . Und es ist möglich, Jackson so zu konfigurieren, dass auch eine Map-Hierarchie erstellt wird, aber das liegt außerhalb des Bereichs dieser Frage. – dlaidlaw

+0

Vielen Dank für den weiteren Beitrag zu diesem Thema, ich habe versucht, eine Seite dieses Codes zu zeigen. – AngelThread