2014-09-02 3 views
7

Java BeanMap으로 변환하려고했습니다. 인터넷에는 많은 자원이 있지만, 불행히도 그들은 모두 간단한 콩을지도로 변환하는 것을 다룹니다. 내 것들은 조금 더 광범위합니다.지도에 Java Bean 병합

간단한 예있다 :

public class MyBean { 

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

    // getters & setters 

} 

내 요점은이 경우, 다음 조건에 해당하는, Map<String, Object>을 생산하는 것입니다 :

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

나는 시도 Apache Commons BeanUtils를 사용하여. BeanUtils#describe(Object)BeanMap(Object) 두 접근법은 "딥 레벨"이 1 인지도를 생성합니다. 즉, 값이 MyHomeAddress 인 객체는 "homeAddress" 키만 존재합니다. 내 메서드는 기본 형식 (또는 문자열)을 충족 할 때까지 개체를 더 깊숙하고 깊게 입력해야합니다. 그런 다음 파지를 중지하고 키를 삽입해야합니다 (예 : "order.customer.contactInfo.home").

내 질문은 : 어떻게 그것을 easliy 할 수 있습니다 (또는 내가 할 수있는 이미 기존의 프로젝트가 있습니까)? 다음은 간단한 반사/재귀 예입니다

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

"원시"는 원시 (객체를 확장)가 아니기 때문에 아마 "원시"를 의미하지는 않습니다. 따라서 어떤 클래스가 트래버스 할 것인지, 어떤 클래스를 값으로 사용할 것인지 알고리즘에 알리는 방법이 필요합니다. 따라서 일종의 구성 (어쩌면 어노테이션 사용)없이이 작업을 수행하는 방법은 없을 것입니다. – Tonio

+1

이것은 리플렉션과 재귀를 통해 수행 할 수 있습니다. 거의 확실하게 직접 작성해야합니다. 현재 라이브러리 권장 사항을 묻는 질문은 주제와 다르기 때문에 질문을 닫을 수 있습니다. – Radiodef

+0

Tonio, Radiodef - 귀하의 제안에 감사드립니다, 나는 내 게시물을 편집했습니다. –

답변

5

:

업데이트는

또한 컬렉션,지도 배열 및 열거 형을 포함하는 Radiodef 응답을 확장했다.

  • 지도 키가 고유해야합니다 :

    당신은 변환 당신이 요청했습니다 방식을하고 몇 가지 문제가 있다는 것을 알고 있어야합니다.

  • Java는 클래스가 자신의 비공개 필드의 이름을 상속 된 클래스가 소유하는 비공개 필드의 이름으로 지정할 수있게합니다.

이 예에서는 사용자가 원하는 경우 어떻게 처리할지 잘 모르므로이 문제를 해결하지 못합니다. 빈이 Object이 아닌 다른 것으로부터 상속받은 경우에는 아이디어를 약간 변경해야합니다. 이 예제는 하위 클래스의 필드 만 고려합니다. 즉

, 당신은

public class SubBean extends Bean { 

가있는 경우이 예제는 SubBean에서 필드를 반환합니다.

자바는 우리가이 미친 것은 수행 할 수 있습니다

아무도 그 일을해야
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; 
} 

하지 않는 것이, 그러나 당신이 열쇠로 문자열을 사용하고자하는 경우는 문제입니다.

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

감사합니다.이 구현은 매력처럼 작동합니다. 내 프로젝트에 필요한 다른 클래스를 포함시키기 위해'isValue (Object)'메소드를 확장했다. (내 포스트 참조). –

+0

큰 코드 @Radiodef를 가져 주셔서 감사합니다. 이 코드에 대한 단위 테스트를 작성했으며 Api에서이 코드를 사용하려고합니다. 바로이 코드에서 누군가가 어떤 버그에 직면하게 될지 궁금합니다. – AngelThread

+1

@AngelThread 여기에서 다시 한 번 내 코드를 훑어 보면 알 수있는 유일한 명백한 문제는 원형 참조를 포함하는 객체가 전달되면 'StackOverflowError'또는 'OutOfMemoryError'가 발생한다는 것입니다 (예 :'class A {B b; }'와'클래스 B {A a; }'. 이러한 객체는 빈으로 간주되지 않을 수도 있지만,이 메서드를 다시 작성하면이 경우를 처리 할 수 ​​있습니다. 나는 당신이'Set values;를 가질 것이라고 생각한다. 그리고 값 유형이 아닌 필드를 추가 할 때마다 먼저 값이 추가되었는지 확인하기 위해'Set'을 점검 할 것이다. 그것이 세트에 있다면, 당신은 재발하지 않습니다. – Radiodef

1

당신은 항상 Jackson Json Processor을 사용할 수 있습니다 여기에

은 TEH codez입니다.이와 같이 :

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

여기서 pojo는 일부 Java bean입니다. 빈에 대한 몇 가지 멋진 주석을 사용하여 직렬화를 제어 할 수 있습니다.

ObjectMapper를 다시 사용할 수 있습니다.

+0

convertValue() 메서드는 내부 객체를 병합하지 않습니다. 예를 들어 특성이있는 Cat 클래스가 friend이고이 특성도 Cat 유형입니다. convertValue 메소드로 Cat 클래스의 인스턴스를 변환하면 다음과 같이됩니다. – AngelThread

+0

@AngelThread 병합은 수행되지 않지만 내부 개체는 해당 유형 (예 : 내부 맵)에 따라 변환 될 수 있습니다. . Jackson도 Map 계층을 만들도록 구성 할 수는 있지만이 문제의 범위를 벗어납니다. – dlaidlaw

+0

이 문제에 대한 추가 기여에 감사드립니다.이 코드 조각을 보여 주려고했습니다. – AngelThread