2014-09-02 57 views
7

我被困在將Java Bean轉換爲Map。互聯網上有很多資源,但不幸的是,他們都把簡單的豆轉換爲地圖。我的一些更廣泛。將Java Bean拼合成地圖

有簡單的例子:

public class MyBean { 

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

    // getters & setters 

} 

我的觀點是產生Map<String, Object>,在這種情況下,對於下列情況屬實:

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

我曾嘗試使用Apache Commons BeanUtils。兩種方法BeanUtils#describe(Object)BeanMap(Object)都會產生一個「深度」爲1的映射(我的意思是隻有"homeAddress"鍵,將MyHomeAddress對象作爲值)。我的方法應該更深入地輸入對象,直到遇到原始類型(或字符串),然後它應該停止挖掘並插入密鑰,即"order.customer.contactInfo.home"

所以,我的問題是:如何輕鬆完成(或者是否有已經存在的項目可以讓我這麼做)?

更新

我已經擴大Radiodef答案也包括集合,映射數組和枚舉:

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

您可能並不是指「原始」,因爲「字符串」不是原語(它擴展了「對象」)。所以你需要一種方法來告訴算法哪些類需要通過,哪些需要作爲值,所以如果沒有某種配置(可能使用註釋),可能不會有這樣的方法。 – Tonio 2014-09-02 17:10:10

+1

這可以通過反射和遞歸來完成,你幾乎肯定必須自己編寫它。請注意,此時此問題將會關閉,因爲要求提供圖書館建議的內容不屬於主題。 – Radiodef 2014-09-02 19:52:48

+0

Tonio,Radiodef - 感謝您的建議,我編輯了我的文章。 – 2014-09-02 20:13:41

回答

5

下面是一個簡單的反射/遞歸的例子。

你應該知道,有一些問題,做一個轉換你問的方式:

  • 地圖鍵必須是唯一的。
  • Java允許類將其私有字段命名爲與繼承類擁有的私有字段相同的名稱。

這個例子沒有解決這些問題,因爲我不確定你想如何解釋它們(如果你這樣做)。如果你的豆繼承了Object之外的其他東西,那麼你需要稍微改變一下你的想法。這個例子只考慮子類的字段。

換句話說,如果你有

public class SubBean extends Bean { 

這個例子將只從SubBean返回領域。

Java允許我們做這個瘋狂的事情:

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

不是任何人都應該這樣做,但如果你想使用String作爲鍵這是一個問題。

這裏是德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

謝謝,這個實現像一個魅力。我已經擴展了'isValue(Object)'方法來包含另一個在我的項目中需要的類(參見我的文章)。 – 2014-09-03 10:16:21

+0

謝謝你的一段代碼@Radiodef。我爲這段代碼編寫了一些單元測試,並計劃在Api中使用它,我想知道是否有人在這個代碼中遇到一些錯誤? – AngelThread 2017-04-15 16:16:14

+1

@AngelThread再次瀏覽我的代碼,我發現唯一明顯的問題是,如果它傳遞了一個包含循環引用的對象,例如'class A {B b;},它將導致一個'StackOverflowError'或'OutOfMemoryError'。 }'和'class B {A a; }'。這樣的對象可能不被認爲是一個bean,但如果我再次寫這個方法,我會處理這種情況。我想你會有一個'Set values;'並且每當你添加一個不是值類型的字段時,你首先檢查'Set'來看看這個值是否已經被添加。如果它在集合中,則不會遞歸。 – Radiodef 2017-04-15 16:24:43

1

你總是可以使用Jackson Json Processor。像這樣:

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

其中pojo是一些Java bean。你可以在bean上使用一些很好的註釋來控制序列化。

您可以重新使用ObjectMapper。

+0

convertValue()方法不會展平內部對象,例如我有一個具有屬性的Cat類稱爲friend,並且此屬性也是Cat類型。如果我用convertValue方法轉換Cat類的實例,它將如下所示。 {friend = {friend = null,childCount = 2,name = yellow},childCount = 1,name = red} – AngelThread 2017-04-14 08:28:25

+0

@AngelThread不執行展平操作,但可以根據內部對象的類型轉換內部對象(例如,inner maps) 。也可以配置Jackson創建一個Map層次結構,但這不在此問題的範圍之內。 – dlaidlaw 2017-04-14 21:15:02

+0

謝謝你對此的進一步貢獻,我試圖展示這段代碼的一面。 – AngelThread 2017-04-15 16:20:15