Làm thế nào để chuyển đổi một đối tượng Java (bean) thành các cặp khóa-giá trị (và ngược lại)?


92

Giả sử tôi có một đối tượng java rất đơn giản chỉ có một số thuộc tính getXXX và setXXX. Đối tượng này chỉ được sử dụng để xử lý các giá trị, về cơ bản là một bản ghi hoặc một bản đồ loại an toàn (và hiệu suất). Tôi thường cần giấu đối tượng này thành các cặp giá trị khóa (chuỗi hoặc loại an toàn) hoặc chuyển đổi từ các cặp giá trị khóa sang đối tượng này.

Ngoài việc phản ánh hoặc viết mã theo cách thủ công để thực hiện chuyển đổi này, cách tốt nhất để đạt được điều này là gì?

Một ví dụ có thể đang gửi đối tượng này qua jms mà không sử dụng kiểu ObjectMessage (hoặc chuyển đổi một tin nhắn đến thành đúng loại đối tượng).


java.beans.Introspector.getBeanInfo(). Nó được tích hợp ngay vào JDK.
Marquis of Lorne,

Câu trả lời:


52

Luôn luôn có beanutils dấu phẩy apache nhưng tất nhiên nó sử dụng phản chiếu dưới mui xe


8
Bên cạnh đó, không có gì sai khi phản chiếu được sử dụng, dưới mui xe. Đặc biệt nếu kết quả được lưu trong bộ nhớ cache (để sử dụng trong tương lai), thì truy cập dựa trên phản chiếu thường đủ nhanh cho hầu hết các trường hợp sử dụng.
StaxMan

22
Nói một cách chính xác, BeanMap (bean) là một phần cụ thể của các beanutils thông thường thực hiện thủ thuật này.
vdr

3
Bỏ qua các cân nhắc về hiệu suất, tôi thấy rằng việc sử dụng BeanMap () kết hợp với ObjectMapper của Jackson từ câu trả lời bên dưới đã cho tôi kết quả tốt nhất. BeanMap đã thành công trong việc tạo một bản đồ hoàn chỉnh hơn về đối tượng của tôi và sau đó Jackson chuyển đổi nó thành cấu trúc LinkedHashMap đơn giản cho phép các sửa đổi sâu hơn trong quá trình thực hiện. objectAsMap = objectMapper.convertValue (BeanMap mới (myObject), Map.class);
michaelr524

Sẽ không hoạt động trên Android vì nó sử dụng các java.beansphụ thuộc bị hạn chế nghiêm trọng trên nền tảng. Xem câu hỏi liên quan để biết chi tiết.
SqueezyMo

Một giải pháp đề cập đến Apache Commons thường là giải pháp tốt nhất.
рüффп

177

Rất nhiều giải pháp tiềm năng, nhưng hãy chỉ thêm một giải pháp nữa. Sử dụng Jackson (JSON xử lý lib) để thực hiện chuyển đổi "json-less", như:

ObjectMapper m = new ObjectMapper();
Map<String,Object> props = m.convertValue(myBean, Map.class);
MyBean anotherBean = m.convertValue(props, MyBean.class);

( mục blog này có thêm một số ví dụ)

Về cơ bản, bạn có thể chuyển đổi bất kỳ kiểu tương thích nào: tương thích nghĩa là nếu bạn đã chuyển đổi từ kiểu sang JSON và từ JSON đó sang kiểu kết quả, các mục nhập sẽ khớp (nếu được định cấu hình đúng cách cũng có thể bỏ qua những mục không được nhận dạng).

Hoạt động tốt cho các trường hợp mà người ta mong đợi, bao gồm Bản đồ, Danh sách, mảng, nguyên thủy, POJO giống như bean.


2
Đây phải là câu trả lời được chấp nhận. BeanUtilslà tốt đẹp, nhưng không thể xử lý mảng và enums
Manish Patel

8

Tạo mã sẽ là cách khác duy nhất tôi có thể nghĩ đến. Về mặt cá nhân, tôi đã có một giải pháp phản ánh có thể tái sử dụng thường xuyên (trừ khi phần đó của mã hoàn toàn quan trọng về hiệu suất). Sử dụng JMS nghe có vẻ như quá mức cần thiết (phụ thuộc bổ sung và đó thậm chí không phải là ý nghĩa của nó). Bên cạnh đó, nó có thể sử dụng phản xạ cũng như dưới mui xe.


Hiệu suất là rất quan trọng vì vậy tôi nghĩ rằng sự phản ánh là không có. Tôi đã hy vọng rằng có thể có một số công cụ sử dụng asm hoặc cglib. Tôi đã bắt đầu xem xét lại bộ đệm giao thức của Google. Tôi không nhận được nhận xét của bạn về JMS, tôi đang sử dụng nó để truyền thông tin giữa các máy.
Shahbaz

Tôi đã hiểu sai điều đó. Đối với hiệu suất, có sự khác biệt giữa hiệu suất là quan trọng và phần cụ thể là quan trọng về hiệu suất. Tôi sẽ làm một điểm chuẩn để xem nó thực sự quan trọng như thế nào so với những gì ứng dụng khác đang làm.
Michael Borgwardt

Đó cũng là suy nghĩ của tôi. Bạn sử dụng sự phản chiếu (trực tiếp hoặc gián tiếp) hoặc bạn phải tạo mã. (Tất nhiên là hoặc tự viết mã phụ).
extraneon

8

Đây là một phương pháp để chuyển đổi một đối tượng Java thành một Bản đồ

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

Đây là cách gọi nó

   Test test = new Test()
   Map<String, Object> map = ConvertObjectToMap(test);

1
Tôi không nghĩ rằng câu trả lời mong muốn lặp lại qua các phương thức được xác định trên mỗi đối tượng, nghe giống như sự phản ánh.
Robert Karl

2
Tôi không biết mục đích của bạn là phương pháp của bạn. Nhưng tôi nghĩ rằng đây là ví dụ rất tuyệt vời khi bạn muốn lập trình với các kiểu generic của các đối tượng
DeXoN

5

Có lẽ đến muộn bữa tiệc. Bạn có thể sử dụng Jackson và chuyển đổi nó thành một đối tượng Thuộc tính. Điều này phù hợp với các lớp lồng nhau và nếu bạn muốn khóa trong giá trị for abc =.

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

nếu bạn muốn một số hậu tố, thì chỉ cần làm

SerializationConfig config = mapper.getSerializationConfig()
                .withRootName("suffix");
mapper.setConfig(config);

cần thêm sự phụ thuộc này

<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-properties</artifactId>
</dependency>

4

Với Java 8, bạn có thể thử điều này:

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

3

JSON , ví dụ sử dụng XStream + Jettison, là một định dạng văn bản đơn giản với các cặp giá trị khóa. Ví dụ, nó được hỗ trợ bởi nhà môi giới tin nhắn Apache ActiveMQ JMS để trao đổi đối tượng Java với các nền tảng / ngôn ngữ khác.


3

Đơn giản chỉ cần sử dụng phản chiếu và 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;
}

Mát nhưng dễ bị StackOverflowError
Teo Choong Ping

3

Sử dụng phản xạ juffrou reaction. Nó rất hiệu quả.

Đây là cách bạn có thể biến đổi một hạt đậu thành một bản đồ:

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

Tôi đã tự phát triển Juffrou. Nó là mã nguồn mở, vì vậy bạn có thể tự do sử dụng và sửa đổi. Và nếu bạn có bất kỳ câu hỏi nào liên quan đến nó, tôi rất sẵn lòng trả lời.

Chúc mừng

Carlos


Tôi đã nghĩ về nó và nhận ra rằng giải pháp trước đó của tôi sẽ bị hỏng nếu bạn có các tham chiếu vòng tròn trong biểu đồ bean của bạn. Vì vậy, tôi đã phát triển một phương pháp sẽ làm điều đó một cách minh bạch và xử lý các tham chiếu vòng tròn một cách đẹp mắt. Giờ đây, bạn có thể chuyển đổi một hạt đậu thành một bản đồ và ngược lại với phản xạ tự động. Thưởng thức :)
Martins

3

Khi sử dụng Spring, người ta cũng có thể sử dụng Spring Integration object-to-map-biến. Nó có lẽ không đáng để thêm Spring như một phụ thuộc chỉ cho điều này.

Để có tài liệu, hãy tìm kiếm "Object-to-Map Transformer" trên http://docs.spring.io/spring-integration/docs/4.0.4.RELEASE/reference/html/messaging-transformation-chapter.html

Về cơ bản, nó duyệt qua toàn bộ đồ thị đối tượng có thể truy cập được từ đối tượng được cung cấp làm đầu vào và tạo ra một bản đồ từ tất cả các trường kiểu / Chuỗi nguyên thủy trên các đối tượng. Nó có thể được cấu hình để xuất ra:

  • một bản đồ phẳng: {rootObject.someField = Joe, rootObject.leafObject.someField = Jane} hoặc
  • một bản đồ có cấu trúc: {someField = Joe, leafObject = {someField = Jane}}.

Đây là một ví dụ từ trang của họ:

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
}

Đầu ra sẽ là:

{person.name = George, person.child.name = Jenna, person.child.nickNames [0] = Bimbo. . . Vân vân}

Một biến áp đảo ngược cũng có sẵn.


2

Bạn có thể sử dụng khuôn khổ Joda:

http://joda.sourceforge.net/

và tận dụng lợi thế của JodaProperties. Tuy nhiên, điều này quy định rằng bạn tạo bean theo một cách cụ thể và triển khai một giao diện cụ thể. Tuy nhiên, nó cho phép bạn trả về bản đồ thuộc tính từ một lớp cụ thể mà không cần phản ánh. Mã mẫu ở đây:

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


Trông thú vị, tiếc là nó không còn được duy trì nữa. Bên cạnh đó, bản đồ thuộc tính mà nó trả về là bản đồ của <Chuỗi, Chuỗi>, vì vậy không phải nhập an toàn.
Shahbaz

1
Đúng, nó đã không được duy trì kể từ năm 2002. Tôi chỉ tò mò không biết liệu một giải pháp dựa trên phản xạ có tồn tại hay không. Bản đồ tài sản mà nó trả về thực sự chỉ là một bản đồ tiêu chuẩn, không có số liệu chung chung như vậy ...
Jon

2

Nếu bạn không muốn gọi mã cứng đến từng getter và setter, thì phản ánh là cách duy nhất để gọi các phương thức này (nhưng không khó).

Bạn có thể cấu trúc lại lớp được đề cập để sử dụng đối tượng Thuộc tính để giữ dữ liệu thực tế và để mỗi getter và setter chỉ gọi get / set trên đó không? Sau đó, bạn có một cấu trúc phù hợp cho những gì bạn muốn làm. Thậm chí còn có các phương pháp để lưu và tải chúng ở dạng khóa-giá trị.


Không có phương pháp nào cả, vì nó phụ thuộc vào bạn đâu là khóa và đâu là giá trị! Sẽ dễ dàng để viết một dịch vụ thực hiện chuyển đổi như vậy. Đối với một CSV đại diện đơn giản có thể được chấp nhận.
Martin K.

2

Giải pháp tốt nhất là sử dụng Dozer. Bạn chỉ cần một cái gì đó như thế này trong tệp ánh xạ:

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

Và thế là xong, Dozer lo phần còn lại !!!

URL tài liệu Dozer


Tại sao nó yêu cầu tệp ánh xạ? Nếu nó biết cách chuyển đổi, tại sao lại yêu cầu công việc bổ sung này - chỉ lấy đối tượng nguồn, loại mong đợi?
StaxMan

Đồng ý. Đó là tốt để biết lý do, mặc dù tôi không chắc chắn rằng nó :) hợp lệ
StaxMan

2

Tất nhiên, có phương tiện chuyển đổi đơn giản nhất tuyệt đối có thể - không có chuyển đổi nào cả!

thay vì sử dụng các biến private được định nghĩa trong lớp, hãy làm cho lớp chỉ chứa một HashMap lưu trữ các giá trị cho phiên bản.

Sau đó, getters và setters của bạn trả về và đặt các giá trị vào và ra khỏi HashMap, và khi đã đến lúc chuyển nó thành bản đồ, thì đấy! - nó đã là một bản đồ.

Với một chút thuật sĩ AOP, bạn thậm chí có thể duy trì tính không linh hoạt vốn có trong bean bằng cách cho phép bạn vẫn sử dụng getters và setters cụ thể cho từng tên giá trị mà không cần phải thực sự viết các getters và setters riêng lẻ.


2

Bạn có thể sử dụng các thuộc tính bộ sưu tập bộ lọc luồng 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)
            );
}

1

Bộ xử lý chú thích JavaDude Bean của tôi tạo mã để thực hiện việc này.

http://javadude.googlecode.com

Ví dụ:

@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 {}

Ở trên tạo ra lớp siêu cấp PersonGen bao gồm một phương thức createPropertyMap () tạo một Bản đồ cho tất cả các thuộc tính được xác định bằng @Bean.

(Lưu ý rằng tôi đang thay đổi API một chút cho phiên bản tiếp theo - thuộc tính chú thích sẽ là defineCreatePropertyMap = true)


Bạn đã thực hiện đánh giá mã chưa? Đây dường như không phải là một cách tiếp cận tốt! Bạn thay đổi đường dẫn kế thừa bằng chú thích! Điều gì sẽ xảy ra nếu hạt đậu đã mở rộng tại một lớp ?! Tại sao bạn phải viết các thuộc tính hai lần?
Martin K.

Nếu bạn đã có một lớp cha, bạn sử dụng @Bean (superclass = XXX.class, ...) và nó sẽ chèn lớp cha đã tạo vào giữa. Điều này rất tốt cho việc đánh giá mã - mặc dù vậy, bảng soạn sẵn ít có khả năng xảy ra lỗi hơn nhiều để sàng lọc.
Scott Stanchfield

Không chắc bạn có nghĩa là gì khi "viết các thuộc tính hai lần" - nơi chúng hiển thị hai lần?
Scott Stanchfield

@Martin K. Nếu bạn thực sự không muốn sử dụng phản chiếu, ngay cả khi ẩn, thì có lẽ bạn buộc phải thực hiện một số loại tạo mã.
extraneon

1

Bạn nên viết một Dịch vụ chuyển đổi chung! Sử dụng generic để giữ cho nó không bị gõ (vì vậy bạn có thể chuyển đổi mọi đối tượng thành key => value và quay lại).

Trường nào nên là chìa khóa? Lấy trường đó từ bean và nối bất kỳ giá trị không phải nhất thời nào khác vào bản đồ giá trị.

Đường về là khá dễ dàng. Đọc khóa (x) và ghi khóa đầu tiên và sau đó mọi mục nhập danh sách trở lại một đối tượng mới.

Bạn có thể lấy tên thuộc tính của bean bằng beanutils dấu phẩy apache !


1

Nếu bạn thực sự muốn hiệu suất, bạn có thể đi theo lộ trình tạo mã.

Bạn có thể tự làm điều này bằng cách tự phản ánh và xây dựng một mixin AspectJ ITD.

Hoặc bạn có thể sử dụng Spring Roo và tạo Spring Roo Addon . Bổ trợ Roo của bạn sẽ làm điều gì đó tương tự như ở trên nhưng sẽ có sẵn cho tất cả những người sử dụng Spring Roo và bạn không cần phải sử dụng Runtime Annotations.

Tôi đã làm cả hai. Mọi người tào lao về Spring Roo nhưng nó thực sự là thế hệ mã toàn diện nhất cho Java.


1

Một cách khả thi khác là ở đây.

BeanWrapper cung cấp chức năng đặt và nhận các giá trị thuộc tính (riêng lẻ hoặc hàng loạt), lấy các bộ mô tả thuộc tính và truy vấn các thuộc tính để xác định xem chúng có thể đọc được hay có thể ghi được hay không.

Company c = new Company();
 BeanWrapper bwComp = BeanWrapperImpl(c);
 bwComp.setPropertyValue("name", "your Company");

1

Nếu nói đến một cây đối tượng đơn giản để ánh xạ danh sách giá trị khóa, trong đó khóa có thể là mô tả đường dẫn chấm từ phần tử gốc của đối tượng đến phần lá đang được kiểm tra, thì rõ ràng là việc chuyển đổi cây thành danh sách khóa-giá trị có thể so sánh với một đối tượng để ánh xạ xml. Mỗi phần tử trong tài liệu XML có một vị trí xác định và có thể được chuyển đổi thành một đường dẫn. Do đó, tôi đã sử dụng XStream như một công cụ chuyển đổi cơ bản và ổn định, đồng thời thay thế các phần trình điều khiển và trình viết phân cấp bằng một triển khai riêng. XStream cũng đi kèm với cơ chế theo dõi đường dẫn cơ bản - được kết hợp với hai cơ chế kia - dẫn đến giải pháp phù hợp cho nhiệm vụ.


1

Với sự trợ giúp của thư viện Jackson, tôi có thể tìm thấy tất cả các thuộc tính lớp của kiểu String / integer / double và các giá trị tương ứng trong một lớp Bản đồ. ( mà không sử dụng api phản xạ! )

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

0

Bằng cách sử dụng Gson,

  1. Chuyển đổi POJO objectsang Json
  2. Chuyển Json sang bản đồ

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

0

Chúng ta có thể sử dụng thư viện Jackson để chuyển đổi một đối tượng Java thành một Bản đồ một cách dễ dàng.

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

Nếu sử dụng trong một dự án Android, bạn có thể thêm jackson trong build.gradle của ứng dụng như sau:

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'

Thực hiện mẫu

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

 }

}

Đầu ra:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.