2014-09-02 21 views
7

Tôi bị kẹt khi chuyển đổi Java Bean thành Map. Có rất nhiều tài nguyên trên internet, nhưng tiếc là tất cả họ đều chuyển đổi các hạt đơn giản thành Maps. Những người thân của tôi rộng hơn một chút.Làm phẳng Java Bean thành Bản đồ

Có ví dụ đơn giản:

public class MyBean { 

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

    // getters & setters 

} 

Quan điểm của tôi là để sản xuất Map<String, Object> đó, trong trường hợp này, là đúng với điều kiện sau:

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

Tôi đã cố gắng sử dụng Apache Commons BeanUtils. Cả hai cách tiếp cận BeanUtils#describe(Object)BeanMap(Object) tạo Bản đồ có "mức độ sâu" là 1 (nghĩa là chỉ có "homeAddress" khóa, giữ đối tượng MyHomeAddress làm giá trị). Phương pháp của tôi nên nhập các đối tượng sâu hơn và sâu hơn cho đến khi nó đáp ứng một kiểu nguyên thủy (hoặc Chuỗi), sau đó nó sẽ ngừng đào và chèn khóa, tức là "order.customer.contactInfo.home".

Vì vậy, câu hỏi của tôi là: làm thế nào nó có thể được easliy thực hiện (hoặc là đã có dự án hiện tại mà sẽ cho phép tôi làm điều đó)?

cập nhật

tôi đã mở rộng Radiodef câu trả lời bao gồm cũng Collections, Maps Mảng và Enums:

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

Bạn có thể không có nghĩa là "nguyên thủy", vì 'Chuỗi' không phải là nguyên thủy (nó mở rộng' Đối tượng'). Vì vậy, bạn cần một cách để nói thuật toán mà các lớp học đi qua, và cái nào để lấy làm giá trị, vì vậy có lẽ sẽ không phải là cách để làm điều này mà không có một số loại cấu hình (có thể sử dụng chú thích). – Tonio

+1

Điều này có thể được thực hiện với sự phản chiếu và đệ quy, bạn gần như chắc chắn phải tự viết nó. Lưu ý rằng tại thời điểm này câu hỏi sẽ bị đóng vì yêu cầu đề xuất thư viện là không có chủ đề. – Radiodef

+0

Tonio, Radiodef - cảm ơn đề xuất của bạn, tôi đã chỉnh sửa bài đăng của mình. –

Trả lời

5

Đây là một đơn giản phản chiếu/example đệ quy.

Bạn nên biết rằng có một số vấn đề với việc chuyển đổi theo cách mà bạn đã hỏi:

  • phím Map phải là duy nhất.
  • Java cho phép các lớp đặt tên cho các trường riêng tư của chúng có cùng tên với trường riêng tư thuộc sở hữu của lớp được kế thừa.

Ví dụ này không giải quyết vấn đề này bởi vì tôi không chắc chắn cách bạn muốn giải thích cho họ (nếu bạn làm). Nếu đậu của bạn kế thừa từ một cái gì đó khác hơn là Object, bạn sẽ cần phải thay đổi ý tưởng của bạn một chút. Ví dụ này chỉ xem xét các trường của lớp con.

Nói cách khác, nếu bạn có

public class SubBean extends Bean { 

ví dụ này sẽ chỉ trả lại các lĩnh vực từ SubBean.

Java cho phép chúng ta làm điều điên rồ này:

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

Không phải là ai nên làm điều đó, nhưng đó là một vấn đề nếu bạn muốn sử dụng String như các phím.

Đây là 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

Cảm ơn bạn, triển khai này hoạt động giống như một sự quyến rũ. Tôi đã mở rộng phương thức 'isValue (Object)' để cũng bao gồm một lớp khác cần thiết trong dự án của tôi (xem bài viết của tôi). –

+0

Cảm ơn bạn đã viết mã tuyệt vời @Radiodef. Tôi đã viết một số bài kiểm tra đơn vị cho đoạn mã này, và tôi định sử dụng nó trong một Api, tôi đã tự hỏi rằng có ai phải đối mặt với một số lỗi trong đoạn mã này không? – AngelThread

+1

@AngelThread Chỉ cần liếc qua mã của tôi ở đây một lần nữa và vấn đề hiển nhiên duy nhất tôi thấy là nó sẽ gây ra 'StackOverflowError' hoặc' OutOfMemoryError' nếu nó truyền một đối tượng chứa tham chiếu vòng tròn, ví dụ 'class A {B b; } 'và' lớp B {A a; } '. Một đối tượng như vậy có thể không được coi là một loại đậu, nhưng nếu tôi viết lại phương pháp này, tôi sẽ xử lý các trường hợp như thế. Tôi nghĩ bạn sẽ có 'Giá trị 'và mỗi lần bạn thêm trường không phải là loại giá trị trước tiên bạn sẽ kiểm tra' Đặt' để xem liệu giá trị đã được thêm chưa. Nếu nó nằm trong bộ này, bạn không được tái chế. – Radiodef

1

Bạn luôn có thể sử dụng Jackson Json Processor.Như thế này:

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

nơi pojo là một số hạt Java. Bạn có thể sử dụng một số chú thích đẹp trên bean để điều khiển tuần tự hóa.

Bạn có thể sử dụng lại ObjectMapper.

+0

phương thức convertValue() không làm phẳng các đối tượng bên trong, ví dụ tôi có một lớp Cat có thuộc tính được gọi là friend và thuộc tính này cũng là kiểu Cat. Nếu tôi chuyển đổi cá thể của lớp Cat bằng phương thức convertValue, nó sẽ giống như sau. {friend = {friend = null, childCount = 2, name = yellow}, childCount = 1, name = red} – AngelThread

+0

@AngelThread Flattening không được thực hiện, nhưng các đối tượng bên trong có thể được chuyển đổi tùy thuộc vào loại của chúng (Ví dụ: bản đồ bên trong) . Và có thể cấu hình Jackson để tạo ra một hệ thống phân cấp Bản đồ, nhưng điều đó nằm ngoài phạm vi của câu hỏi này. – dlaidlaw

+0

Cảm ơn bạn đã đóng góp thêm về điều này, tôi đã cố gắng hiển thị một phần của đoạn mã này. – AngelThread

Các vấn đề liên quan