2015-07-19 7 views
2

Я использую Spring MVC для управления API приложения, с которым я сейчас работаю. Сериализация ответа API выполняется через Jackson's ObjectMapper. Я столкнулся со следующей ситуацией: мы расширяем ряд наших объектов для поддержки UserDefinedFields (UDF), который показан ниже в аббревиатуре UserDefinedResponse. Будучи решением SaaS, у нескольких клиентов есть другая конфигурация, которая хранится в базе данных для своих настраиваемых полей.Jackson Сериализация/десериализация: динамические свойства и поля

Цель этого вопроса - дать возможность каждому клиенту отвечать данными UDF. Это потребует

  1. Динамического переименовывать поля customString1, customString2 ... в их соответствующие UDF этикетки
  2. удаление неопределенных UDF поля (пример клиент использует только 2 из 4-х полей.

Пример абстрактного ответа

public abstract class UserDefinedResponse { 
    public String customString1; 
    public String customString2; 
    public String customString3; 
    public String customString4; 
} 

И ответ на продукт, который расширяет UserDefinedResponse объекта

public class Product extends UserDefinedResponse { 
    public long id; 
    public String name; 
    public float price; 
} 

И, наконец, при условии, клиент устанавливает

  • customString1 = "supplier"
  • customString2 = "warehouse"

Сериализация Product для этого клиента должна привести к чему-то похожее на это:

{ 
    "id" : 1234, 
    "name" : "MacBook Air", 
    "price" : 1299, 
    "supplier" : "Apple", 
    "warehouse" : "New York warehouse" 
} 

ответ

2

Я думаю, что вы могли бы делать то, что вам нужно с помощью нескольких аннотаций Джексон:

public abstract class UserDefinedResponse { 

    @JsonIgnore 
    public String customString1; 

    @JsonIgnore 
    public String customString2; 

    @JsonIgnore 
    public String customString3; 

    @JsonIgnore 
    public String customString4; 

    @JsonIgnore // Remove if clientId must be serialized 
    public String clientId; 

    private Map<String, Object> dynamicProperties = new HashMap<>(); 

    @JsonAnyGetter 
    public Map<String, Object> getDynamicProperties() { 
     Mapper.fillDynamicProperties(this, this.dynamicProperties); 
     return this.dynamicProperties; 
    } 

    @JsonAnySetter 
    public void setDynamicProperty(String name, Object value) { 
     this.dynamicProperties.put(name, value); 
     Mapper.setDynamicProperty(this.dynamicProperties, name, this); 
    } 
} 

Во-первых, аннотировать все свойства базового класса с @JsonIgnore, так как они не будут частью ответ. Затем используйте аннотацию @JsonAnyGetter, чтобы сгладить карту dynamicProperties, которая сохранит динамические свойства. Наконец, аннотация для @JsonAnySetter предназначена для использования Джексоном при десериализации.

Недостающая часть класс Mapper утилиты:

public abstract class Mapper<T extends UserDefinedResponse> { 

    private static final Map<Class<T>, Map<String, Mapper<T>>> MAPPERS = new HashMap<>(); 

    static { 
     // Mappers for Products 
     Map<String, Mapper<Product>> productMappers = new HashMap<>(); 
     productMappers.put("CLIENT_1", new ProductMapperClient1()); 
     productMappers.put("CLIENT_2", new ProductMapperClient2()); 
     // etc for rest of clients 
     MAPPERS.put(Product.class, productMappers); 

     // Mappers for Providers 
     Map<String, Mapper<Provider>> providerMappers = new HashMap<>(); 
     providerMappers.put("CLIENT_1", new ProviderMapperClient1()); 
     providerMappers.put("CLIENT_2", new ProviderMapperClient2()); 
     // etc for rest of clients 
     MAPPERS.put(Provider.class, providerMappers); 

     // etc for rest of entities 
     // (each entity needs to add specific mappers for every client) 
    } 

    protected Mapper() { 
    } 

    public static void fillDynamicProperties(T response, Map<String, Object> dynamicProperties) { 
     // Get mapper for entity and client 
     Mapper<T> mapper = MAPPERS.get(response.getClass()).get(response.clientId); 
     // Perform entity -> map mapping 
     mapper.mapFromEntity(response, dynamicProperties); 
    } 

    public static void setDynamicProperty(Map<String, Object> dynamicProperties, String name, T response) { 
     // Get mapper for entity and client 
     Mapper<T> mapper = MAPPERS.get(response.getClass()).get(response.clientId); 
     // Perform map -> entity mapping 
     mapper.mapToEntity(dynamicProperties, name, response); 
    } 

    protected abstract void mapFromEntity(T response, Map<String, Object> dynamicProperties); 

    protected abstract void mapToEntity(Map<String, Object> dynamicProperties, String name, T response); 
} 

И для сущности продукта и клиента CLIENT_1:

public class ProductMapperClient1 extends Mapper<Product> { 

    @Override 
    protected void mapFromEntity(Product response, Map<String, Object> dynamicProperties) { 
     // Actual mapping from Product and CLIENT_1 to map 
     dynamicProperties.put("supplier", response.customString1); 
     dynamicProperties.put("warehouse", response.customString2); 
    } 

    @Override 
    protected void mapToEntity(Map<String, Object> dynamicProperties, String name, Product response) { 
     // Actual mapping from map and CLIENT_1 to Product 
     String property = (String) dynamicProperties.get(name); 
     if ("supplier".equals(name)) { 
      response.customString1 = property; 
     } else if ("warehouse".equals(name)) { 
      response.customString2 = property; 
     } 
    } 
} 

Идея заключается в том, что есть определенный картограф для каждого (лиц, клиента) пара.Если у вас много сущностей и/или клиентов, вы можете рассмотреть возможность динамического отображения карты карт, возможно, чтения из некоторого файла конфигурации и использования отражения для считывания свойств объекта.

+0

Спасибо Федерико за ответ. Я попробую и вернусь к вам сегодня. – Bashar

+0

Работал как шарм. Решение было в @JsonAnyGetter – Bashar

0

Считаете ли вы, что возвращаете карту <> как ответ? Или часть ответа, например response.getUDF(). Get ("customStringX"))? Это должно сэкономить вам некоторые возможные проблемы в будущем, например: 10 миллионов одновременных пользователей - 10 миллионов классов в вашей виртуальной машине.

+0

Я действительно рассматривал использование карты, но использование этого класса обеспечило бы соблюдение и стандартизацию ответа, поскольку некоторые ответы API будут повторно использоваться в разных частях кода. Что касается нескольких классов, я не вижу, как они связаны с параллельными пользователями. Я бы изменил возвращаемые поля на основе запроса и клиента во время выполнения. – Bashar

+0

JVM загружает классы в память, поэтому они потребляют кучу пространства - Classloader также является объектом. Я предположил, что ваш случай использования будет основан на классах, генерируемых «на лету». В худших сценариях это может привести к утечкам памяти и ошибкам производительности - вот статья об этом [Вопросы, связанные с Classloader memmory] (http://www.dynatrace.com/ru/javabook/class-loader-issues.html). –

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