Javaオブジェクト(bean)をキーと値のペアに(またはその逆に)変換する方法は?


91

いくつかのgetXXXおよびsetXXXプロパティのみを持つ非常に単純なJavaオブジェクトがあるとします。このオブジェクトは、値を処理するためにのみ使用されます。基本的には、レコードまたはタイプセーフな(そしてパフォーマンスの高い)マップです。このオブジェクトをキー値ペア(文字列またはタイプセーフ)に変換するか、キー値ペアからこのオブジェクトに変換する必要があることがよくあります。

この変換を行うためにリフレクションまたは手動でコードを書く以外に、これを達成するための最良の方法は何ですか?

たとえば、ObjectMessageタイプを使用せずに(または受信メッセージを適切な種類のオブジェクトに変換して)、jmsを介してこのオブジェクトを送信する場合があります。


java.beans.Introspector.getBeanInfo()。JDKに直接組み込まれています。
ローン侯爵

回答:


52

apache commons beanutilsは常に存在しますが、もちろん内部でリフレクションを使用します


8
その上、内部で反射が使用されていることには何の問題もありません。特に、結果が(将来の使用のために)キャッシュされる場合、リフレクションベースのアクセスは通常、ほとんどのユースケースで十分高速です。
StaxMan 2010年

22
正確には、BeanMap(bean)は、トリックを実行する一般的なbeanutilsの特定の部分です。
vdr

3
パフォーマンスに関する考慮事項を無視して、BeanMap()をJacksonのObjectMapperと組み合わせて使用​​すると、以下の答えが最良の結果をもたらすことがわかりました。BeanMapはオブジェクトのより完全なマップの作成に成功し、JacksonはそれをプレーンなLinkedHashMap構造に変換しました。objectAsMap = objectMapper.convertValue(new BeanMap(myObject)、Map.class);
michaelr524 14年

Android java.beansはプラットフォームで大幅に制限された依存関係を使用するため、Androidでは機能しません。詳細については、関連する質問を参照してください。
SqueezyMo 2017

ほとんどの場合、Apache Commonsに言及するソリューションが最良のソリューションです。
2006

177

解決策はたくさんありますが、もう1つ追加しましょう。ジャクソン(JSON処理lib)を使用して、「jsonなし」の変換を行います。

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

このブログエントリにはいくつかの例があります)

基本的に、互換性のあるタイプを変換できます。互換性とは、タイプからJSONに、そのJSONから結果のタイプに変換した場合、エントリが一致することを意味します(適切に構成されていれば、認識されないものも無視できます)。

マップ、リスト、配列、プリミティブ、BeanのようなPOJOなど、予想されるケースに適しています。


2
これは受け入れられる答えになるはずです。BeanUtilsいいですが、配列と列挙型を処理できません
Manish Patel

8

コード生成は、私が考えることができる他の唯一の方法でしょう。個人的には、一般的に再利用可能なリフレクションソリューションを使用します(コードのその部分が絶対にパフォーマンスが重要でない限り)。JMSの使用はやり過ぎのように聞こえます(追加の依存関係であり、それが意図されていることすらありません)。その上、それはおそらくフードの下で同様に反射を使用します。


パフォーマンスは非常に重要なので、反射は出ていないと思います。asmまたはcglibを使用するツールがいくつかあることを期待していました。私は再びGoogleのプロトコルバッファを調べ始めました。JMSについてのコメントはありませんでした。JMSを使用してマシン間で情報をやり取りしています。
Shahbaz

私はそれを誤解しました。パフォーマンスに関しては、パフォーマンスが重要であることと、その特定の部分がパフォーマンスが重要であることには違いがあります。ベンチマークを実行して、アプリケーションが実行している他のことと比較して、実際にどれほど重要であるかを確認します。
Michael Borgwardt

それも私の考えです。リフレクションを(直接的または間接的に)使用するか、コードを生成する必要があります。(もちろん、追加のコードを自分で記述します)。
extraneon

8

これは、Javaオブジェクトをマップに変換するためのメソッドです

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

これはそれを呼び出す方法です

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

1
望ましい答えは、各オブジェクトで定義されたメソッドを反復するものではなく、反射のように聞こえると思います。
Robert Karl

2
私はあなたの目的があなたの方法であることを知りません。しかし、これは、ジェネリック型のオブジェクトを使用してプログラミングしたい場合の非常に優れた例だと思います
DeXoN

5

おそらくパーティーに遅れる。Jacksonを使用して、それをPropertiesオブジェクトに変換できます。これは、ネストされたクラスや、abc = valueのキーが必要な場合に適しています。

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

サフィックスが必要な場合は、次のようにしてください

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

この依存関係を追加する必要があります

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

4

Java 8では、これを試すことができます。

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

たとえばXStream + Jettisonを使用するJSONは、キーと値のペアを持つ単純なテキスト形式です。たとえば、他のプラットフォームや言語とのJavaオブジェクト交換のためのApache ActiveMQ JMSメッセージブローカーによってサポートされています。


3

単にリフレクションと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;
}

クールだがStackOverflowErrorが発生しやすい
Teo Choong Ping

3

使用juffrouは、反映のBeanWrapperを。とても高性能です。

Beanをマップに変換する方法は次のとおりです。

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

私はジュフロを自分で開発しました。これはオープンソースなので、自由に使用して変更できます。また、ご不明な点がございましたら、お気軽にお問い合わせください。

乾杯

カルロス


私はそれについて考えました、そして、あなたのbeanグラフに循環参照があるならば、私の以前の解決策が壊れることに気づきました。そこで、私はそれを透過的に行い、循環参照を美しく処理する方法を開発しました。これで、Beanをマップに変換したり、juffrou-reflectを使用してその逆を行うことができます。お楽しみください:)
Martins 2013年

3

Springを使用する場合は、Spring Integrationのオブジェクトからマップへの変換を使用することもできます。これだけの依存関係としてSpringを追加する価値はないでしょう。

ドキュメントについては、http://docs.spring.io/spring-integration/docs/4.0.4.RELEASE/reference/html/messaging-transformation-chapter.htmlで「Object-to-Map Transformer」を検索してください

基本的に、入力として与えられたオブジェクトから到達可能なオブジェクトグラフ全体を横断し、オブジェクトのすべてのプリミティブ型/文字列フィールドからマップを生成します。次のいずれかを出力するように構成できます。

  • フラットマップ:{rootObject.someField = Joe、rootObject.leafObject.someField = Jane}、または
  • 構造化マップ:{someField = Joe、leafObject = {someField = Jane}}。

これが彼らのページの例です:

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
}

出力は次のようになります。

{person.name = George、person.child.name = Jenna、person.child.nickNames [0] =ヤリマン。。。等}

逆変圧器も利用できます。


2

Jodaフレームワークを使用できます。

http://joda.sourceforge.net/

JodaPropertiesを利用します。ただし、これは特定の方法でBeanを作成し、特定のインターフェースを実装することを規定しています。ただし、リフレクションなしで特定のクラスからプロパティマップを返すことができます。サンプルコードはこちらです:

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


面白そうですが、残念ながらメンテナンスされているようには見えません。さらに、返されるプロパティマップは<String、String>のマップであるため、タイプセーフではありません。
Shahbaz

1
確かに、それは2002年以来維持されていません。非反射ベースのソリューションが存在するかどうかについて私はただ知りたがっていました。それが返すプロパティマップは、実際には単なる標準マップであり、ジェネリックなどはありません...
Jon

2

各ゲッターとセッターへの呼び出しをハードコーディングしたくない場合は、リフレクションがこれらのメソッドを呼び出す唯一の方法です(ただし、難しくはありません)。

問題のクラスをリファクタリングして、Propertiesオブジェクトを使用して実際のデータを保持し、各ゲッターとセッターにget / setを呼び出させることはできますか?そして、あなたはあなたがしたいことによく合った構造を持っています。これらをKey-Valueフォームに保存してロードする方法もあります。


何が鍵で何が価値に依存するので、メソッドはありません!このような変換を行うサービスは簡単に作成できるはずです。単純な表現の場合、CSVで十分です。
マーティンK.

2

最善の解決策はDozerを使用することです。マッパーファイルに次のようなものが必要です。

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

そして、それだけです。残りはDozerが処理します!!!

DozerドキュメントURL


なぜマッピングファイルが必要なのですか?それが変換する方法を知っているなら、なぜこの追加の作業が必要なのですか?ソースオブジェクト、期待されるタイプを取るだけですか?
StaxMan 2010年

OK。それが有効であるかどうかはわかりませんが、理由を知るのは良いことです:)
StaxMan

2

もちろん、可能な限り最も単純な変換方法があります-変​​換はまったくありません!

クラスで定義されたプライベート変数を使用する代わりに、インスタンスの値を格納するHashMapのみをクラスに含めるようにします。

次に、ゲッターとセッターがHashMapに値を返したり、HashMapから値を設定したりします。そして、それをマップに変換するときが来たら、出来上がりです!-それはすでに地図です。

AOPウィザードを少し使用すると、実際に個々のゲッターとセッターを作成しなくても、各値の名前に固有のゲッターとセッターを引き続き使用できるため、Beanに固有の柔軟性を維持することもできます。


2

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

私のJavaDude Bean注釈プロセッサは、これを行うためのコードを生成します。

http://javadude.googlecode.com

例えば:

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

上記は、@ Beanを使用して定義されたすべてのプロパティのマップを生成するcreatePropertyMap()メソッドを含むスーパークラスPersonGenを生成します。

(次のバージョンではAPIを少し変更していることに注意してください-アノテーション属性はdefineCreatePropertyMap = trueになります)


コードレビューをしましたか?これは良いアプローチではないようです!アノテーションで継承パスを変更します!Beanがクラスですでに拡張されている場合はどうなりますか?属性を2回記述する必要があるのはなぜですか?
マーティンK.

すでにスーパークラスがある場合は、@ Bean(superclass = XXX.class、...)を使用して、生成されたスーパークラスを間に挿入します。これはコードレビューに最適です。ただし、エラーが発生しやすいボイラープレートをふるいにかけることはほとんどありません。
スコットスタンフィールド

「属性を2回書き込む」という意味がわからない-どこに2回表示されるのか。
スコットスタンフィールド

@Martin K.内部で実際にリフレクションを使用したくない場合は、おそらく何らかのコード生成を行わざるを得ません。
extraneon

1

一般的な変換サービスを作成する必要があります!ジェネリックを使用して型を空けておきます(すべてのオブジェクトをkey => valueに変換して戻すことができます)。

キーとなるフィールドは何ですか?Beanからそのフィールドを取得し、値マップに他の非一時的な値を追加します。

戻る方法はかなり簡単です。key(x)を読み取り、最初にキーを書き込んでから、すべてのリストエントリを新しいオブジェクトに戻します。

Apache Commons beanutilsを使用して、Beanのプロパティ名を取得できます。


1

本当にパフォーマンスが必要な場合は、コード生成ルートに進むことができます。

独自のリフレクションを行い、ミックスインのAspectJ ITDを構築することで、これを自分で行うことができます。

または、Spring Rooを使用してSpring Rooアドオンを作成することもできます。Rooアドオンは上記と同様の機能を果たしますが、Spring Rooを使用するすべてのユーザーが利用でき、ランタイムアノテーションを使用する必要はありません。

私は両方を行いました。人々はSpring Rooに熱狂していますが、それは本当にJavaの最も包括的なコード生成です。


1

もう1つの可能な方法はこちらです。

BeanWrapperは、プロパティ値の設定と取得(個別または一括)、プロパティ記述子の取得、およびプロパティのクエリを実行して、それらが読み取り可能か書き込み可能かを判断する機能を提供します。

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

1

単純なオブジェクトツリーからキー値リストへのマッピングの場合、キーはオブジェクトのルート要素から検査対象の葉へのドットパスの説明である可能性があります。キー値リストへのツリー変換は、オブジェクトからXMLへのマッピング。XMLドキュメント内の各要素には位置が定義されており、パスに変換できます。そのため、XStreamを基本的で安定した変換ツールとして、階層的なドライバーとライターの部分を独自の実装に置き換えました。XStreamには、他の2つと組み合わせると、厳密にタスクに適したソリューションにつながる基本的なパス追跡メカニズムも付属しています。


1

ジャクソンライブラリーの助けを借りて、タイプString / integer / doubleのすべてのクラスプロパティと、Mapクラスのそれぞれの値を見つけることができました。(リフレクションAPIを使用しない!

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

Gsonを使用することにより、

  1. POJO objectをJsonに変換する
  2. Jsonをマップに変換

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

0

Jacksonライブラリーを使用して、JavaオブジェクトをMapに簡単に変換できます。

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

Androidプロジェクトで使用する場合は、次のようにアプリのbuild.gradleにjacksonを追加できます。

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'

実装例

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

 }

}

出力:

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.