Java 객체 (Bean)를 키-값 쌍으로 (또는 그 반대로) 변환하는 방법은 무엇입니까?


92

getXXX 및 setXXX 속성 만있는 매우 간단한 Java 개체가 있다고 가정 해 보겠습니다. 이 개체는 기본적으로 레코드 또는 형식 안전 (및 성능) 맵과 같은 값을 처리하는 데만 사용됩니다. 나는 종종이 객체를 키 값 쌍 (문자열 또는 형식 안전)으로 변환하거나 키 값 쌍에서이 객체로 변환해야합니다.

이 변환을 수행하기 위해 리플렉션 또는 수동으로 코드를 작성하는 것 외에이를 달성하는 가장 좋은 방법은 무엇입니까?

예를 들어 ObjectMessage 유형을 사용하지 않고 (또는 수신 메시지를 올바른 유형의 객체로 변환하지 않고) jms를 통해이 객체를 보낼 수 있습니다.


java.beans.Introspector.getBeanInfo(). JDK에 바로 내장되어 있습니다.
Marquis of Lorne

답변:


52

항상 apache commons beanutils가 있지만 물론 후드 아래에서 반사를 사용합니다.


8
게다가, 후드 아래에서 반사를 사용하는 것에 대해 잘못된 것은 없습니다. 특히 결과가 캐시 된 경우 (향후 사용을 위해) 반사 기반 액세스는 일반적으로 대부분의 사용 사례에 충분히 빠릅니다.
StaxMan

22
정확히 말하면 BeanMap (bean)은 트릭을 수행하는 일반적인 beanutils의 특정 부분입니다.
VDR

3
성능 고려 사항을 무시하고 아래 답변에서 Jackson의 ObjectMapper와 함께 BeanMap ()을 사용하면 최상의 결과를 얻을 수 있다는 것을 알았습니다. BeanMap은 내 개체의 더 완전한 맵을 만드는 데 성공했으며 Jackson은 파이프 라인 아래에서 수정을 허용하는 일반 LinkedHashMap 구조로 변환했습니다. objectAsMap = objectMapper.convertValue (new BeanMap (myObject), Map.class);
michaelr524

java.beans플랫폼에서 심각하게 제한된 종속성을 사용하기 때문에 Android에서 작동하지 않습니다 . 자세한 내용은 관련 질문 을 참조하십시오.
SqueezyMo

Apache Commons를 언급하는 솔루션은 대부분의 경우 최상의 솔루션입니다.
рüффп

177

많은 잠재적 솔루션이 있지만 하나만 더 추가하겠습니다. 사용 잭슨 (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에서 결과 유형으로 변환하면 항목이 일치 함을 의미합니다 (제대로 구성된 경우 인식되지 않는 항목을 무시할 수도 있음).

지도, 목록, 배열, 프리미티브, 빈과 같은 POJO를 포함하여 예상되는 경우에 잘 작동합니다.


2
이것은 받아 들여진 대답이어야합니다. BeanUtils좋은이지만, 배열 및 열거 형을 처리 할 수 없습니다
마니 파텔

8

코드 생성은 제가 생각할 수있는 유일한 다른 방법입니다. 개인적으로 저는 일반적으로 재사용 가능한 리플렉션 솔루션을 얻었습니다 (코드의 일부가 성능에 절대적으로 중요하지 않은 경우). JMS를 사용하는 것은 과잉처럼 들립니다 (추가적인 종속성, 그것이 의미하는 바도 아닙니다). 게다가, 아마도 후드 아래에서도 반사를 사용합니다.


성능이 중요하기 때문에 반사가 빠져 있다고 생각합니다. asm 또는 cglib를 사용하는 도구가있을 수 있기를 바랐습니다. Google의 프로토콜 버퍼를 다시 살펴보기 시작했습니다. JMS에 대한 귀하의 의견을받지 못했습니다.이를 사용하여 컴퓨터간에 정보를 전달하고 있습니다.
Shahbaz

나는 그것을 오해했다. 성능에 관해서는 성능이 중요한 것과 특정 부분이 성능이 중요한 부분 사이에 차이가 있습니다. 응용 프로그램이 수행하는 다른 작업과 비교하여 얼마나 중요한지 확인하기 위해 벤치 마크를 수행 할 것입니다.
Michael Borgwardt

그것도 제 생각입니다. 리플렉션을 사용하거나 (직간접 적으로) 코드를 생성해야합니다. (또는 추가 코드를 직접 작성하십시오).
extraneon

8

Java 객체를 Map으로 변환하는 방법입니다.

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을 사용하여 속성 개체로 변환 할 수 있습니다. 이것은 중첩 된 클래스와 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가에 쿨하지만 경향
테오 충 핑

3

juffrou-reflect 의 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;
}

나는 Juffrou를 직접 개발했습니다. 오픈 소스이므로 자유롭게 사용하고 수정할 수 있습니다. 이에 대해 질문이 있으시면 기꺼이 답변 해 드리겠습니다.

건배

카를로스


나는 그것에 대해 생각하고 빈 그래프에 순환 참조가 있으면 이전 솔루션이 중단된다는 것을 깨달았습니다. 그래서 저는 이것을 투명하게 수행하고 순환 참조를 아름답게 처리하는 방법을 개발했습니다. 이제 juffrou-reflect를 사용하여 Bean을 맵으로 또는 그 반대로 변환 할 수 있습니다. 즐기세요 :)
Martins

3

Spring을 사용할 때 Spring Integration object-to-map-transformer를 사용할 수도 있습니다. 이것에 대한 의존성으로 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] = Bimbo. . . 기타}

역 변압기도 사용할 수 있습니다.


2

Joda 프레임 워크를 사용할 수 있습니다.

http://joda.sourceforge.net/

JodaProperties를 활용하십시오. 그러나 이것은 당신이 특정 방식으로 빈을 생성하고 특정 인터페이스를 구현한다는 것을 규정합니다. 그러나 리플렉션없이 특정 클래스에서 속성 맵을 반환 할 수 있습니다. 샘플 코드는 다음과 같습니다.

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


흥미롭게 보이지만 불행히도 더 이상 유지되지 않는 것 같습니다. 또한 반환되는 속성 맵은 <String, String>의 맵이므로 형식이 안전하지 않습니다.
Shahbaz

1
사실, 2002 년 이후로 유지되지 않았습니다. 무반사 기반 솔루션이 존재하는지 궁금했습니다. 반환되는 속성 맵은 실제로 표준 맵이며 제네릭은 없습니다 ...
Jon

2

각 getter 및 setter에 대한 호출을 하드 코딩하지 않으려면 리플렉션이 이러한 메서드를 호출하는 유일한 방법입니다 (하지만 어렵지는 않습니다).

실제 데이터를 보유하기 위해 Properties 객체를 사용하고 각 getter 및 setter가 get / set을 호출하도록 해당 클래스를 리팩토링 할 수 있습니까? 그런 다음 원하는 작업에 적합한 구조를 갖게됩니다. 키-값 형식으로 저장하고로드하는 방법도 있습니다.


열쇠와 가치는 당신에게 달려 있기 때문에 방법이 없습니다! 그러한 변환을 수행하는 서비스를 작성하는 것은 쉬울 것입니다. 단순 표현의 경우 CSV가 허용 될 수 있습니다.
Martin 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

확인. 이유를 아는 것이 좋습니다. 비록 그것이 타당한 지 확실하지 않지만 :)
StaxMan

2

물론 가능한 가장 단순한 변환 수단이 있습니다. 변환이 전혀 없습니다!

클래스에 정의 된 개인 변수를 사용하는 대신 인스턴스에 대한 값을 저장하는 HashMap 만 클래스에 포함하도록합니다.

그런 다음 getter와 setter가 HashMap으로 값을 반환하고 설정합니다. 그리고 맵으로 변환 할 때가되면 짜잔! -이미지도입니다.

약간의 AOP 마법사를 사용하면 실제로 개별 getter 및 setter를 작성하지 않고도 각 값 이름에 특정한 getter 및 setter를 계속 사용할 수 있도록하여 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 Annotation Processor는이를 수행하는 코드를 생성합니다.

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을 사용하여 정의 된 모든 속성에 대한 Map을 생성하는 createPropertyMap () 메서드를 포함하는 수퍼 클래스 PersonGen을 생성합니다.

(다음 버전에서는 API를 약간 변경하고 있습니다. 주석 속성은 defineCreatePropertyMap = true가됩니다.)


코드 검토를 했습니까? 이것은 좋은 접근 방식이 아닌 것 같습니다! 주석으로 상속 경로를 변경합니다! 빈이 이미 클래스에서 확장된다면?! 왜 속성을 두 번 써야합니까?
Martin K.

이미 수퍼 클래스가있는 경우 @Bean (superclass = XXX.class, ...)을 사용하고 생성 된 수퍼 클래스를 중간에 삽입합니다. 이것은 코드 검토에 매우 유용했습니다.하지만 잠재적으로 오류가 발생하기 쉬운 상용구가 훨씬 적습니다.
Scott Stanchfield

"속성을 두 번 작성"이 무슨 뜻인지 잘 모르겠습니다. 어디에 두 번 표시됩니까?
Scott Stanchfield

@Martin K. 내부에서도 리플렉션을 사용하고 싶지 않다면 아마도 일종의 코드 생성을해야 할 것입니다.
extraneon

1

일반 변환 서비스를 작성해야합니다! 제네릭을 사용하여 유형을 자유롭게 유지하십시오 (모든 객체를 key => value로 변환하고 그 반대로 변환 할 수 있음).

어떤 필드가 키 여야합니까? 빈에서 해당 필드를 가져와 값 맵에 다른 비 임시 값을 추가하십시오.

돌아 오는 길은 아주 쉽습니다. key (x)를 읽고 처음에는 키를 쓴 다음 모든 목록 항목을 다시 새 객체에 씁니다.

apache commons beanutils 로 빈의 속성 이름을 얻을 수 있습니다 !


1

정말로 성능을 원한다면 코드 생성 경로로 갈 수 있습니다.

자신의 성찰을 수행하고 AspectJ ITD를 믹스 인하여이를 수행 할 수 있습니다.

또는 Spring Roo를 사용하여 Spring Roo Addon을 만들 수 있습니다. . Roo 애드온은 위와 비슷한 작업을 수행하지만 Spring Roo를 사용하는 모든 사용자가 사용할 수 있으며 런타임 주석을 사용할 필요가 없습니다.

나는 둘 다 해냈다. 사람들은 Spring Roo를 싫어하지만 실제로 Java를위한 가장 포괄적 인 코드 생성입니다.


1

다른 가능한 방법은 여기에 있습니다.

BeanWrapper는 속성 값을 설정 및 가져오고 (개별적으로 또는 대량으로) 속성 설명자를 가져오고 속성을 쿼리하여 읽기 또는 쓰기 가능 여부를 결정하는 기능을 제공합니다.

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

1

키가 개체의 루트 요소에서 검사중인 리프까지 점으로 구분 된 경로 설명 일 수있는 간단한 개체 트리 대 키 값 목록 매핑의 경우 키-값 목록으로의 트리 변환이 XML 매핑에 개체. XML 문서 내의 각 요소에는 정의 된 위치가 있으며 경로로 변환 할 수 있습니다. 따라서 XStream 을 기본적이고 안정적인 변환 도구로 사용하고 계층 적 드라이버 및 작성기 부분을 자체 구현으로 대체했습니다. XStream은 또한 기본 경로 추적 메커니즘을 제공합니다.이 메커니즘은 다른 두 가지와 결합되어 작업에 적합한 솔루션을 엄격하게 유도합니다.


1

Jackson 라이브러리의 도움으로 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.