Bagaimana cara mengonversi objek Java (kacang) menjadi pasangan nilai kunci (dan sebaliknya)?


93

Katakanlah saya memiliki objek java yang sangat sederhana yang hanya memiliki beberapa properti getXXX dan setXXX. Objek ini digunakan hanya untuk menangani nilai, pada dasarnya adalah catatan atau peta tipe-aman (dan performant). Saya sering perlu menyembunyikan objek ini menjadi pasangan nilai kunci (baik string atau tipe aman) atau mengonversi dari pasangan nilai kunci ke objek ini.

Selain refleksi atau menulis kode secara manual untuk melakukan konversi ini, apa cara terbaik untuk mencapai ini?

Contoh mungkin mengirim objek ini melalui jms, tanpa menggunakan tipe ObjectMessage (atau mengonversi pesan masuk ke objek yang tepat).


java.beans.Introspector.getBeanInfo(). Itu dibangun langsung ke JDK.
Marquis dari Lorne

Jawaban:


53

Selalu ada kacang apache commons tapi tentu saja itu menggunakan refleksi di bawah tenda


8
Selain itu, tidak ada yang salah tentang refleksi yang digunakan, di bawah tenda. Terutama jika hasil disimpan dalam cache (untuk penggunaan di masa mendatang), akses berbasis refleksi biasanya cukup cepat untuk sebagian besar kasus penggunaan.
StaxMan

22
Tepatnya, BeanMap (kacang) adalah bagian spesifik dari kacang tanah biasa yang melakukan trik tersebut.
vdr

3
Mengabaikan pertimbangan kinerja, saya telah menemukan bahwa menggunakan BeanMap () dalam kombinasi dengan ObjectMapper Jackson dari jawaban di bawah memberi saya hasil terbaik. BeanMap berhasil membuat peta yang lebih lengkap dari objek saya dan kemudian Jackson mengubahnya menjadi struktur LinkedHashMap biasa yang memungkinkan modifikasi lebih jauh ke dalam pipa. objectAsMap = objectMapper.convertValue (BeanMap baru (myObject), Map.class);
michaelr524

Tidak akan berfungsi di Android karena menggunakan java.beansdependensi yang sangat terbatas pada platform. Lihat pertanyaan terkait untuk detailnya.
SqueezyMo

Solusi yang menyebutkan Apache Commons seringkali merupakan solusi terbaik.
рüффп

179

Banyak solusi potensial, tapi mari tambahkan satu lagi. Gunakan Jackson (lib pemrosesan JSON) untuk melakukan konversi "tanpa json", seperti:

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

( entri blog ini memiliki beberapa contoh lagi)

Anda pada dasarnya dapat mengonversi semua jenis yang kompatibel: artinya jika Anda memang mengonversi dari jenis ke JSON, dan dari JSON itu ke jenis hasil, entri akan cocok (jika dikonfigurasi dengan benar juga dapat mengabaikan yang tidak dikenal).

Berfungsi dengan baik untuk kasus yang diharapkan, termasuk Peta, Daftar, array, primitif, POJO seperti kacang.


2
Ini harus menjadi jawaban yang diterima. BeanUtilsbagus, tetapi tidak dapat menangani array dan enum
Manish Patel

8

Pembuatan kode akan menjadi satu-satunya cara lain yang dapat saya pikirkan. Secara pribadi, saya mendapatkan solusi refleksi yang umumnya dapat digunakan kembali (kecuali bagian kode itu benar-benar kritis terhadap kinerja). Menggunakan JMS terdengar seperti berlebihan (ketergantungan tambahan, dan itu bukan maksudnya). Selain itu, mungkin juga menggunakan refleksi di bawah kapnya.


Performa sangat penting jadi saya pikir refleksi sudah keluar. Saya berharap mungkin ada beberapa alat yang menggunakan asm atau cglib. Saya sudah mulai melihat buffer protokol Google lagi. Saya tidak mendapatkan komentar Anda tentang JMS, saya menggunakannya untuk mengkomunikasikan informasi di seluruh mesin.
Shahbaz

Saya salah paham tentang itu. Mengenai kinerja, ada perbedaan antara kinerja yang penting dan bagian spesifik itu menjadi kinerja-kritis. Saya akan melakukan benchmark untuk melihat seberapa penting hal itu dibandingkan dengan apa lagi yang dilakukan aplikasi.
Michael Borgwardt

Itu juga pikiranku. Entah Anda menggunakan refleksi (secara langsung atau tidak langsung) atau Anda harus membuat kode. (Atau tulis kode ekstra sendiri, tentunya).
extraneon

8

Ini adalah metode untuk mengubah objek Java menjadi Peta

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

Ini adalah bagaimana menyebutnya

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

1
Saya tidak berpikir jawaban yang diinginkan mengulangi metode yang ditentukan pada setiap objek, terdengar seperti refleksi.
Robert Karl

2
Saya tidak tahu tujuan Anda adalah metode Anda. Tetapi saya pikir ini adalah contoh yang sangat bagus ketika Anda ingin memprogram dengan jenis objek generik
DeXoN

6

Mungkin terlambat ke pesta. Anda dapat menggunakan Jackson dan mengubahnya menjadi objek Properties. Ini cocok untuk kelas bersarang dan jika Anda menginginkan kunci di for abc = value.

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

jika Anda menginginkan sufiks, lakukan saja

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

perlu menambahkan ketergantungan ini

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

4

Dengan Java 8 Anda dapat mencoba ini:

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 , misalnya menggunakan XStream + Jettison, adalah format teks sederhana dengan pasangan nilai kunci. Ini didukung misalnya oleh broker pesan Apache ActiveMQ JMS untuk pertukaran objek Java dengan platform / bahasa lain.


3

Cukup menggunakan refleksi dan 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;
}

Keren tapi rentan terhadap StackOverflowError
Teo Choong Ping

3

Gunakan BeanWrapper juffrou-reflect . Ini sangat bagus.

Berikut adalah cara mengubah kacang menjadi peta:

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

Saya mengembangkan Juffrou sendiri. Ini open source, jadi Anda bebas menggunakannya dan memodifikasi. Dan jika Anda memiliki pertanyaan tentang itu, saya akan dengan senang hati menjawabnya.

Bersulang

Carlos


Saya memikirkannya dan menyadari bahwa solusi saya sebelumnya rusak jika Anda memiliki referensi melingkar di grafik kacang Anda. Jadi saya mengembangkan metode yang akan melakukannya secara transparan dan menangani referensi melingkar dengan indah. Anda sekarang dapat mengubah kacang menjadi peta dan sebaliknya dengan juffrou-reflect. Nikmati :)
Martins

3

Saat menggunakan Spring, seseorang juga dapat menggunakan trafo objek-ke-peta-integrasi Spring. Mungkin tidak ada gunanya menambahkan Spring sebagai dependensi hanya untuk ini.

Untuk dokumentasi, cari "Object-to-Map Transformer" di http://docs.spring.io/spring-integration/docs/4.0.4.RELEASE/reference/html/messaging-transformation-chapter.html

Pada dasarnya, ini melintasi seluruh grafik objek yang dapat dijangkau dari objek yang diberikan sebagai input, dan menghasilkan peta dari semua bidang tipe / String primitif pada objek. Ini dapat dikonfigurasi untuk menghasilkan:

  • peta datar: {rootObject.someField = Joe, rootObject.leafObject.someField = Jane}, atau
  • peta terstruktur: {someField = Joe, leafObject = {someField = Jane}}.

Berikut contoh dari halaman mereka:

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
}

Outputnya adalah:

{person.name = George, person.child.name = Jenna, person.child.nickNames [0] = Bimbo. . . etc}

Trafo balik juga tersedia.


2

Anda bisa menggunakan kerangka Joda:

http://joda.sourceforge.net/

dan memanfaatkan JodaProperties. Ini memang menetapkan bahwa Anda membuat kacang dengan cara tertentu, dan menerapkan antarmuka tertentu. Namun, itu memungkinkan Anda untuk mengembalikan peta properti dari kelas tertentu, tanpa refleksi. Kode sampel ada di sini:

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


Kelihatannya menarik, sayangnya sepertinya sudah tidak dipertahankan lagi. Selain itu, peta properti yang dikembalikannya adalah peta <String, String>, jadi bukan tipe safe.
Shahbaz

1
Benar, itu belum dipertahankan sejak 2002. Saya hanya ingin tahu apakah ada solusi berbasis non-refleksi. Peta properti yang dikembalikan sebenarnya hanyalah peta standar, tidak ada yang generik seperti ...
Jon

2

Jika Anda tidak ingin melakukan panggilan hardcode ke setiap pengambil dan penyetel, refleksi adalah satu-satunya cara untuk memanggil metode ini (tetapi tidak sulit).

Dapatkah Anda merefaktor kelas yang dimaksud untuk menggunakan objek Properties untuk menyimpan data aktual, dan membiarkan setiap pengambil dan penyetel hanya memanggil get / set di atasnya? Kemudian Anda memiliki struktur yang sesuai untuk apa yang ingin Anda lakukan. Bahkan ada metode untuk menyimpan dan memuatnya dalam bentuk nilai kunci.


Tidak ada metode, karena tergantung pada Anda apa itu kunci dan apa nilainya! Seharusnya mudah untuk menulis layanan yang melakukan konversi seperti itu. Untuk representasi sederhana CSV mungkin bisa diterima.
Martin K.

2

Solusi terbaik adalah dengan menggunakan Dozer. Anda hanya membutuhkan sesuatu seperti ini di file mapper:

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

Dan hanya itu, Dozer akan mengurus sisanya !!!

URL Dokumentasi Dozer


Mengapa itu membutuhkan file pemetaan? Jika tahu cara mengonversi, mengapa memerlukan kerja ekstra ini - ambil saja objek sumber, tipe yang diharapkan?
StaxMan

Baik. Senang mengetahui alasannya, meskipun saya tidak yakin itu valid :)
StaxMan

2

Tentu saja ada cara konversi paling sederhana yang mungkin - tidak ada konversi sama sekali!

daripada menggunakan variabel privat yang ditentukan di kelas, buat kelas hanya berisi HashMap yang menyimpan nilai untuk instance.

Kemudian pengambil dan penyetel Anda kembali dan menetapkan nilai ke dalam dan ke luar HashMap, dan ketika saatnya untuk mengubahnya menjadi peta, voila! - itu sudah menjadi peta.

Dengan sedikit sihir AOP, Anda bahkan dapat mempertahankan ketidakfleksibelan yang melekat pada kacang dengan memungkinkan Anda untuk tetap menggunakan getter dan setter yang spesifik untuk setiap nama nilai, tanpa harus benar-benar menulis getter dan setter individual.


2

Anda dapat menggunakan properti pengumpul filter aliran 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

Prosesor Anotasi Kacang JavaDude saya menghasilkan kode untuk melakukan ini.

http://javadude.googlecode.com

Sebagai contoh:

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

Di atas menghasilkan superclass PersonGen yang menyertakan metode createPropertyMap () yang menghasilkan Peta untuk semua properti yang ditentukan menggunakan @Bean.

(Perhatikan bahwa saya mengubah API sedikit untuk versi berikutnya - atribut anotasi akan menjadi defineCreatePropertyMap = true)


Sudahkah Anda melakukan peninjauan kode? Ini sepertinya bukan pendekatan yang bagus! Anda mengubah jalur warisan dengan anotasi! Bagaimana jika kacang sudah meluas di kelas ?! Mengapa Anda harus menulis atribut dua kali?
Martin K.

Jika Anda sudah memiliki superclass, Anda menggunakan @Bean (superclass = XXX.class, ...) dan ini akan memasukkan inbetween superclass yang dihasilkan. Ini bagus untuk tinjauan kode - apalagi boilerplate yang berpotensi rawan kesalahan untuk diayak.
Scott Stanchfield

Tidak yakin apa yang Anda maksud dengan "tulis atribut dua kali" - di mana atribut tersebut muncul dua kali?
Scott Stanchfield

@Martin K. Jika Anda benar-benar tidak ingin menggunakan refleksi, bahkan di bawah tenda, maka Anda mungkin terpaksa melakukan semacam pembuatan kode.
extraneon

1

Anda harus menulis Layanan transformasi generik! Gunakan obat generik untuk membuatnya tetap bebas tipe (sehingga Anda dapat mengonversi setiap objek menjadi key => value dan back).

Bidang apa yang harus menjadi kuncinya? Dapatkan bidang itu dari kacang dan tambahkan nilai non-sementara lainnya di peta nilai.

Jalan kembali cukup mudah. Baca kunci (x) dan tulis terlebih dahulu kunci tersebut dan kemudian setiap entri daftar kembali ke objek baru.

Anda bisa mendapatkan nama properti kacang dengan apache commons beanutils !


1

Jika Anda benar-benar menginginkan kinerja, Anda dapat pergi ke jalur pembuatan kode.

Anda dapat melakukan ini sendiri dengan melakukan refleksi Anda sendiri dan membuat mixin AspectJ ITD.

Atau Anda dapat menggunakan Spring Roo dan membuat Spring Roo Addon . Addon Roo Anda akan melakukan hal serupa di atas, tetapi akan tersedia untuk semua orang yang menggunakan Spring Roo dan Anda tidak perlu menggunakan Anotasi Waktu Proses.

Saya telah melakukan keduanya. Orang-orang tidak suka Spring Roo tetapi itu benar-benar pembuatan kode paling komprehensif untuk Java.


1

Satu cara lain yang mungkin ada di sini.

BeanWrapper menawarkan fungsionalitas untuk mengatur dan mendapatkan nilai properti (secara individual atau massal), mendapatkan deskriptor properti, dan meminta properti untuk menentukan apakah mereka dapat dibaca atau ditulis.

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

1

Jika berkaitan dengan pohon objek sederhana untuk pemetaan daftar nilai kunci, di mana kuncinya mungkin berupa deskripsi jalur bertitik dari elemen akar objek ke daun yang sedang diperiksa, agak jelas bahwa konversi pohon ke daftar nilai kunci sebanding dengan objek untuk pemetaan xml. Setiap elemen dalam dokumen XML memiliki posisi yang ditentukan dan dapat diubah menjadi jalur. Oleh karena itu saya mengambil XStream sebagai alat konversi dasar dan stabil dan mengganti driver hierarkis dan bagian penulis dengan implementasi sendiri. XStream juga dilengkapi dengan mekanisme pelacakan jalur dasar yang - digabungkan dengan dua lainnya - secara ketat mengarah pada solusi yang sesuai untuk tugas tersebut.


1

Dengan bantuan perpustakaan Jackson, saya dapat menemukan semua properti kelas tipe String / integer / double, dan nilai masing-masing dalam kelas Peta. ( tanpa menggunakan api refleksi! )

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

Dengan menggunakan Gson,

  1. Ubah POJO objectke Json
  2. Ubah Json ke Map

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

0

Kita dapat menggunakan perpustakaan Jackson untuk mengubah objek Java menjadi Peta dengan mudah.

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

Jika menggunakan dalam proyek Android, Anda dapat menambahkan jackson di build.gradle aplikasi Anda sebagai berikut:

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'

Implementasi Sampel

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

 }

}

Keluaran:

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

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.