2014-09-02 2 views
7

Я застрял при преобразовании Java Bean в Map. В Интернете много ресурсов, но, к сожалению, все они рассматривают преобразование простых бобы в Карты. Мои немного более обширны.Свертывание 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 (я имею в виду, что есть только "homeAddress" ключ, содержащий MyHomeAddress объект в качестве значения). Мой метод должен вводить объекты глубже и глубже, пока не встретит примитивный тип (или String), тогда он должен прекратить копать и вставлять ключ, то есть "order.customer.contactInfo.home".

Итак, мой вопрос: как он может быть выполнен easliy (или есть уже существующий проект, который позволил бы мне это сделать)?

обновление

Я расширил Radiodef ответ включает также коллекцию, Карты Массивы и перечисления:

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

Вы, вероятно, не имеете в виду «примитивный», поскольку «String» не является примитивным (он расширяет «Object»). Таким образом, вам нужен способ рассказать алгоритму, какие классы должны проходить, и какие значения принимать значения, поэтому, вероятно, не будет способа сделать это без какой-либо конфигурации (возможно, с помощью аннотаций). – Tonio

+1

Это можно сделать с отражением и рекурсией, вы почти наверняка должны написать это самостоятельно. Обратите внимание, что на данный момент вопрос будет закрыт, потому что запрос рекомендаций библиотеки не соответствует теме. – Radiodef

+0

Tonio, Radiodef - спасибо за ваши предложения, я редактировал свой пост. –

ответ

5

Вот простой отражательный/рекурсивный пример.

Вы должны знать, что есть некоторые проблемы с делать конверсию так, как вы просили:

  • ключи Карты должны быть уникальными.
  • Java позволяет классам называть свои частные поля тем же именем, что и частное поле, принадлежащее унаследованному классу.

В этом примере не рассматриваются эти проблемы, потому что я не уверен, как вы хотите их учитывать (если да, то). Если ваши бобы наследуются от чего-то другого, кроме Object, вам нужно немного изменить свою идею. В этом примере рассматриваются только поля подкласса.

Другими словами, если у вас есть

public class SubBean extends Bean { 

этот пример будет возвращать только поля из SubBean.

Java позволяет нам сделать эту безумную вещь:

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

Не то, чтобы кто-то должен делать это, но это проблема, если вы хотите использовать строки в качестве ключей.

Вот Teh codez:

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; } 'и' class B {A a; } '. Такой объект не может считаться bean-компонентом, но если бы я снова написал этот метод, я бы справился с такими случаями. Я думаю, что у вас есть значения «Set », и каждый раз, когда вы добавляете поле, которое не является типом значения, вы должны сначала проверить «Set», чтобы узнать, добавлено ли это значение. Если он находится в комплекте, вы не возвращаетесь. – Radiodef

1

Вы всегда можете использовать Jackson Json Processor.Пример:

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

где pojo - это Java-компонент. Вы можете использовать некоторые полезные аннотации для компонента для управления сериализацией.

Вы можете повторно использовать ObjectMapper.

+0

Метод convertValue() не сглаживает внутренние объекты, например, у меня есть класс Cat, который имеет атрибут, называется другом, и этот атрибут также является типом Cat. Если я конвертирую экземпляр класса Cat с методом convertValue, он будет похож на следующий. {friend = {friend = null, childCount = 2, name = yellow}, childCount = 1, name = red} – AngelThread

+0

@AngelThread Flattening не выполняется, но внутренние объекты могут быть преобразованы в зависимости от их типа (например, внутренние карты) , Также можно настроить Jackson для создания иерархии карт, но это выходит за рамки этого вопроса. – dlaidlaw

+0

Спасибо за дальнейший вклад в это, я пытался показать сторону этого фрагмента кода. – AngelThread

Смежные вопросы