2017-02-07 9 views
2

У меня есть приложение для загрузки весны, и я хочу отправить ограничения проверки DTO, а также значение поля клиенту.Сериализация аннотаций, а также полей для JSON

Имея DTO

class PetDTO { 
    @Length(min=5, max=15) 
    String name; 
} 

где название происходит быть «Левиафан», должно привести к этому JSON, посылаемой клиенту:

{ 
    name: 'Leviathan' 
    name_constraint: { type: 'length', min:5, max: 15}, 
} 

Рассуждая, чтобы иметь единый источник истины для валидаций. Можно ли это сделать с разумным объемом работы?

ответ

1

Чтобы расширить ответ Фредерика, я покажу небольшой пример кода, который связывает объект с картой и сериализует его.

Так вот POJO Пользователь:

import org.hibernate.validator.constraints.Length; 

public class User { 

    private String name; 

    public User(String name) { 
     this.name = name; 
    } 

    @Length(min = 5, max = 15) 
    public String getName() { 
     return name; 
    } 
} 

Тогда фактическая сериализатору:

import com.fasterxml.jackson.core.JsonGenerator; 
import com.fasterxml.jackson.databind.SerializerProvider; 
import com.fasterxml.jackson.databind.ser.std.StdSerializer; 
import org.springframework.util.ReflectionUtils; 

import java.beans.IntrospectionException; 
import java.beans.Introspector; 
import java.io.IOException; 
import java.lang.annotation.Annotation; 
import java.lang.reflect.Method; 
import java.lang.reflect.Proxy; 
import java.util.*; 
import java.util.stream.Stream; 

import static java.util.stream.Collectors.toMap; 

public class UserSerializer extends StdSerializer<User> { 

    public UserSerializer(){ 
     this(User.class); 
    } 

    private UserSerializer(Class t) { 
     super(t); 
    } 

    @Override 
    public void serialize(User bean, JsonGenerator gen, SerializerProvider provider) throws IOException { 
     Map<String, Object> properties = beanProperties(bean); 
     gen.writeStartObject(); 
     for (Map.Entry<String, Object> entry : properties.entrySet()) { 
      gen.writeObjectField(entry.getKey(), entry.getValue()); 
     } 
     gen.writeEndObject(); 
    } 

    private static Map<String, Object> beanProperties(Object bean) { 
     try { 
      return Arrays.stream(Introspector.getBeanInfo(bean.getClass(), Object.class).getPropertyDescriptors()) 
        .filter(descriptor -> Objects.nonNull(descriptor.getReadMethod())) 
        .flatMap(descriptor -> { 
         String name = descriptor.getName(); 
         Method getter = descriptor.getReadMethod(); 
         Object value = ReflectionUtils.invokeMethod(getter, bean); 
         Property originalProperty = new Property(name, value); 

         Stream<Property> constraintProperties = Stream.of(getter.getAnnotations()) 
           .map(anno -> new Property(name + "_constraint", annotationProperties(anno))); 

         return Stream.concat(Stream.of(originalProperty), constraintProperties); 
        }) 
        .collect(toMap(Property::getName, Property::getValue)); 
     } catch (Exception e) { 
      return Collections.emptyMap(); 
     } 
    } 

    // Methods from Annotation.class 
    private static List<String> EXCLUDED_ANNO_NAMES = Arrays.asList("toString", "equals", "hashCode", "annotationType"); 

    private static Map<String, Object> annotationProperties(Annotation anno) { 
     try { 
      Stream<Property> annoProps = Arrays.stream(Introspector.getBeanInfo(anno.getClass(), Proxy.class).getMethodDescriptors()) 
        .filter(descriptor -> !EXCLUDED_ANNO_NAMES.contains(descriptor.getName())) 
        .map(descriptor -> { 
         String name = descriptor.getName(); 
         Method method = descriptor.getMethod(); 
         Object value = ReflectionUtils.invokeMethod(method, anno); 
         return new Property(name, value); 
        }); 
      Stream<Property> type = Stream.of(new Property("type", anno.annotationType().getName())); 
      return Stream.concat(type, annoProps).collect(toMap(Property::getName, Property::getValue)); 
     } catch (IntrospectionException e) { 
      return Collections.emptyMap(); 
     } 
    } 

    private static class Property { 
     private String name; 
     private Object value; 

     public Property(String name, Object value) { 
      this.name = name; 
      this.value = value; 
     } 

     public String getName() { 
      return name; 
     } 

     public Object getValue() { 
      return value; 
     } 
    } 
} 

И, наконец, мы должны зарегистрировать эту сериалайзер для использования Джексон:

import org.springframework.boot.SpringApplication; 
import org.springframework.boot.autoconfigure.SpringBootApplication; 
import org.springframework.context.annotation.Bean; 
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; 
import org.springframework.web.bind.annotation.GetMapping; 
import org.springframework.web.bind.annotation.RestController; 

@SpringBootApplication(scanBasePackages = "sample.spring.serialization") 
public class SerializationApp { 

    @Bean 
    public Jackson2ObjectMapperBuilder mapperBuilder(){ 
     Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder = new Jackson2ObjectMapperBuilder(); 
     jackson2ObjectMapperBuilder.serializers(new UserSerializer()); 
     return jackson2ObjectMapperBuilder; 
    } 

    public static void main(String[] args) { 
     SpringApplication.run(SerializationApp.class, args); 
    } 
} 

@RestController 
class SerializationController { 
    @GetMapping("/user") 
    public User user() { 
     return new User("sample"); 
    } 
} 

Json, который будет излучаться:

{ 
    "name_constraint":{ 
     "min":5, 
     "max":15, 
     "payload":[], 
     "groups":[], 
     "message":"{org.hibernate.validator.constraints.Length.message}", 
     "type":"org.hibernate.validator.constraints.Length" 
    }, 
    "name":"sample" 
} 

Надеюсь, это поможет. Удачи.

1

Для этого вы всегда можете использовать пользовательский сериализатор Джексона. Много докторов, чтобы сделать это можно найти в Интернете, может выглядеть примерно так:

public void serialize(PetDTO value, JsonGenerator jgen, ...) { 

    jgen.writeStartObject(); 
    jgen.writeNumberField("name", value.name); 
    jgen.writeObjectField("name_consteaint", getConstraintValue(value)); 
} 

public ConstaintDTO getConstraintValue(PetDTO value) { 

    // Use reflection to check if the name field on the PetDTO is annotated 
    // and extract the min, max and type values from the annotation 
    return new ConstaintDTO().withMaxValue(...).withMinValue(...).ofType(...); 
} 

Вы можете создать базовый класс-DTO, для которого преобразователь пинки в, так что вы не должны создавать пользовательский конвертер для всех ваших объектов домена, которым необходимо выявить ограничения.

Объединив отражение и интеллектуальное использование полей ввода, вы можете приблизиться. Даунсайд - вы не можете использовать аннотации @JsonXXX на своих объектах домена, так как вы сами пишете JSON.

Более идеальное решение, которое должно было бы преобразовать Джексон, но имеет какой-то пост-конверсионный вызов для добавления дополнительных объектов XX_condtion. Может быть, начните с переопределения объекта-сериализатора по умолчанию (если возможно)?

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