将注释和字段序列化为 JSON
serializing annotations as well as fields to JSON
我有一个 spring 启动应用程序,我想向客户端发送 DTO 验证约束和字段值。
有 DTO
class PetDTO {
@Length(min=5, max=15)
String name;
}
名称恰好是 'Leviathan',应该导致此 JSON 被发送到客户端:
{
name: 'Leviathan'
name_constraint: { type: 'length', min:5, max: 15},
}
推理是为了验证的单一事实来源。这可以通过合理的工作量来完成吗?
您始终可以为此使用自定义 Jackson Serializer。可以在互联网上找到大量执行此操作的文档,可能看起来像这样:
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(...);
}
您可能想要创建一个 base-DTO class 转换器启动,这样您就不必为 所有域对象创建自定义转换器 需要公开约束。
结合反射和写域的巧妙使用,可以亲近。缺点是您 无法利用域对象上的 @JsonXXX
注释 ,因为您是自己编写 JSON。
更理想的解决方案是让 Jackson 转换,但有某种 post-conversion-call 来向对象添加额外的 XX_condtion
属性。也许从覆盖默认值 object-serializer(如果可能)开始?
为了扩展 Frederik 的回答,我将展示一些示例代码,将对象转换为映射对象并将其序列化。
所以这是用户 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;
}
}
}
最后我们需要注册这个序列化器以供 Jackson 使用:
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"
}
希望这对您有所帮助。祝你好运。
我有一个 spring 启动应用程序,我想向客户端发送 DTO 验证约束和字段值。
有 DTO
class PetDTO {
@Length(min=5, max=15)
String name;
}
名称恰好是 'Leviathan',应该导致此 JSON 被发送到客户端:
{
name: 'Leviathan'
name_constraint: { type: 'length', min:5, max: 15},
}
推理是为了验证的单一事实来源。这可以通过合理的工作量来完成吗?
您始终可以为此使用自定义 Jackson Serializer。可以在互联网上找到大量执行此操作的文档,可能看起来像这样:
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(...);
}
您可能想要创建一个 base-DTO class 转换器启动,这样您就不必为 所有域对象创建自定义转换器 需要公开约束。
结合反射和写域的巧妙使用,可以亲近。缺点是您 无法利用域对象上的 @JsonXXX
注释 ,因为您是自己编写 JSON。
更理想的解决方案是让 Jackson 转换,但有某种 post-conversion-call 来向对象添加额外的 XX_condtion
属性。也许从覆盖默认值 object-serializer(如果可能)开始?
为了扩展 Frederik 的回答,我将展示一些示例代码,将对象转换为映射对象并将其序列化。
所以这是用户 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;
}
}
}
最后我们需要注册这个序列化器以供 Jackson 使用:
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"
}
希望这对您有所帮助。祝你好运。