Jackson 不会使用自定义序列化器序列化 null
Jackson won't serialize null with a custom Serializer
我有一个我想应用的自定义 bean 序列化程序,但是当我这样做时,Jackson 不再包含 null 属性。
以下代码重现了该问题:
import java.io.IOException;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import lombok.Value;
public class Test {
@Value
public static class Contact {
String first;
String middle;
String last;
String email;
}
public static void main(String[] args) throws Exception {
Contact contact = new Contact("Bob", null, "Barker", null);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new SimpleModule() {
@Override public void setupModule(SetupContext context) {
super.setupModule(context);
context.addBeanSerializerModifier(new BeanSerializerModifier() {
@Override public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription desc, JsonSerializer<?> serializer) {
// return serializer;
return new JsonSerializer<Object>() {
@Override public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
((JsonSerializer<Object>) serializer).serialize(value, gen, serializers);
}};
}
});
}
});
System.out.println(
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(contact)
);
}
}
上面的代码除了注册一个 'custom' 序列化器(只是委托回原始序列化器)之外没有做任何其他事情,但是它产生 JSON 没有空属性:
{ "first" : "Bob", "last" : "Barker" }
如果您按 return serializer;
原样注释掉 return new JsonSerializer<Object>() {...
和 return 传入的序列化程序,则 Jackson 序列化空属性:
{ "first" : "Bob", "middle" : null, "last" : "Barker", "email"
: null }
我已经阅读了许多看似相关的 SO 文章,但是 none 已经引导我找到了解决方案。我已经尝试在序列化时将映射器显式设置为 Include.ALWAYS
,但没有成功。
我唯一的线索是 JsonSerializer 的 JavaDoc 中的评论:
NOTE: various serialize
methods are never (to be) called
with null values -- caller must handle null values, usually
by calling {@link SerializerProvider#findNullValueSerializer} to obtain
serializer to use.
This also means that custom serializers cannot be directly used to change
the output to produce when serializing null values.
我正在使用 Jackson 版本 2.11.2。
我的问题是:如何编写自定义序列化程序并让 Jackson 遵守其通常的关于 null 属性 序列化的包含指令?
上下文信息:我实际的自定义序列化程序的工作是有条件地隐藏序列化中的属性。我有一个自定义注释 @JsonAuth
,它用 @JacksonAnnotationsInside @JsonInclude(Include.NON_EMPTY)
进行了元注释,我的自定义序列化程序(ContextualSerializer
)在覆盖的 isEmpty
方法和 returns true
(视为空)如果缺少授权。最终结果是我有一个可以应用于属性的注释,如果客户端未被授权,它将隐藏序列化中的 属性。除了...自定义序列化程序的使用具有丢弃所有空属性的意外副作用。
更新:Jackson 的 BeanPropertyWriter.serializeAsField(...)
方法将完全忽略分配给 属性 的任何自定义序列化程序(如果值为 null)。
我能够通过对 class 编写一个小扩展来覆盖此行为,这允许我的“isAuthorized”逻辑抢占空检查:
public class JsonAuthPropertyWriter extends BeanPropertyWriter {
private final Predicate<Object> authFilter;
private JsonAuthPropertyWriter(BeanPropertyWriter delegate, Predicate<Object> authFilter) {
super(delegate);
this.authFilter = authFilter;
// set null serializer or authorized null values disappear
super.assignNullSerializer(NullSerializer.instance);
}
@Override
public void serializeAsField(
Object bean,
JsonGenerator gen,
SerializerProvider prov) throws Exception {
boolean authorized = authFilter.test(bean);
if (!authorized) return;
super.serializeAsField(bean, gen, prov);
}
}
我使用 BeanSerializerModifier
:
注入了这些自定义 BeanPropertyWriters
private static class JsonAuthBeanSerializerModifier extends BeanSerializerModifier {
@Override
public List<BeanPropertyWriter> changeProperties(
SerializationConfig config,
BeanDescription beanDesc,
List<BeanPropertyWriter> beanProperties
) {
for (int i = 0; i < beanProperties.size(); i++) {
BeanPropertyWriter beanPropertyWriter = beanProperties.get(i);
JsonAuth jsonAuth = beanPropertyWriter.findAnnotation(JsonAuth.class);
if (jsonAuth != null) {
Predicate<Object> authPredicate = ...
beanProperties.set(i, new JsonAuthPropertyWriter(beanPropertyWriter, authPredicate));
}
}
return beanProperties;
}
}
我可能误解了你的意思,但这个方法似乎很有用:
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.Map;
public class Test2 {
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface JsonAuth {
}
@JsonFilter("myFilter")
public static class Contact {
@JsonAuth
String first;
@JsonAuth
String middle;
@JsonAuth
String last;
String email;
public Contact(String first, String middle, String last, String email) {
this.first = first;
this.middle = middle;
this.last = last;
this.email = email;
}
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getMiddle() {
return middle;
}
public void setMiddle(String middle) {
this.middle = middle;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
public static Map<String,Boolean> fieldSerialisationCount = new HashMap<>();
public static void main(String[] args) throws Exception {
Contact contact = new Contact("Bob", null, "Barker", null);
ObjectMapper mapper = new ObjectMapper();
FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter", new SimpleBeanPropertyFilter() {
@Override
protected boolean include(BeanPropertyWriter writer) {
return super.include(writer) && isAuthed(writer);
}
@Override
protected boolean include(PropertyWriter writer) {
return super.include(writer) && isAuthed(writer);
}
private boolean isAuthed(PropertyWriter writer) {
if (!writer.getMember().hasAnnotation(JsonAuth.class)) {
return true;
} else {
return fieldSerialisationCount.compute(writer.getName(), (n, b) -> b == null ? true : !b); // check auth here
}
}
});
mapper.setFilterProvider(filters);
ObjectWriter writer = mapper.writer(filters).withDefaultPrettyPrinter();
System.out.println(
writer.writeValueAsString(contact)
);
System.out.println(
writer.writeValueAsString(contact)
);
System.out.println(
writer.writeValueAsString(contact)
);
}
}
它每隔一段时间序列化带注释的字段,就像使用持久状态的过滤器示例一样。
请告诉我这是否适合你。
顺便说一下,我同意 Jackson 有你描述的问题,但我不知道如何解决它,所以这是一个解决方法,而不是对你原来问题的回答。
我有一个我想应用的自定义 bean 序列化程序,但是当我这样做时,Jackson 不再包含 null 属性。
以下代码重现了该问题:
import java.io.IOException;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import lombok.Value;
public class Test {
@Value
public static class Contact {
String first;
String middle;
String last;
String email;
}
public static void main(String[] args) throws Exception {
Contact contact = new Contact("Bob", null, "Barker", null);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new SimpleModule() {
@Override public void setupModule(SetupContext context) {
super.setupModule(context);
context.addBeanSerializerModifier(new BeanSerializerModifier() {
@Override public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription desc, JsonSerializer<?> serializer) {
// return serializer;
return new JsonSerializer<Object>() {
@Override public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
((JsonSerializer<Object>) serializer).serialize(value, gen, serializers);
}};
}
});
}
});
System.out.println(
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(contact)
);
}
}
上面的代码除了注册一个 'custom' 序列化器(只是委托回原始序列化器)之外没有做任何其他事情,但是它产生 JSON 没有空属性:
{ "first" : "Bob", "last" : "Barker" }
如果您按 return serializer;
原样注释掉 return new JsonSerializer<Object>() {...
和 return 传入的序列化程序,则 Jackson 序列化空属性:
{ "first" : "Bob", "middle" : null, "last" : "Barker", "email" : null }
我已经阅读了许多看似相关的 SO 文章,但是 none 已经引导我找到了解决方案。我已经尝试在序列化时将映射器显式设置为 Include.ALWAYS
,但没有成功。
我唯一的线索是 JsonSerializer 的 JavaDoc 中的评论:
NOTE: various
serialize
methods are never (to be) called with null values -- caller must handle null values, usually by calling {@link SerializerProvider#findNullValueSerializer} to obtain serializer to use.
This also means that custom serializers cannot be directly used to change
the output to produce when serializing null values.
我正在使用 Jackson 版本 2.11.2。
我的问题是:如何编写自定义序列化程序并让 Jackson 遵守其通常的关于 null 属性 序列化的包含指令?
上下文信息:我实际的自定义序列化程序的工作是有条件地隐藏序列化中的属性。我有一个自定义注释 @JsonAuth
,它用 @JacksonAnnotationsInside @JsonInclude(Include.NON_EMPTY)
进行了元注释,我的自定义序列化程序(ContextualSerializer
)在覆盖的 isEmpty
方法和 returns true
(视为空)如果缺少授权。最终结果是我有一个可以应用于属性的注释,如果客户端未被授权,它将隐藏序列化中的 属性。除了...自定义序列化程序的使用具有丢弃所有空属性的意外副作用。
更新:Jackson 的 BeanPropertyWriter.serializeAsField(...)
方法将完全忽略分配给 属性 的任何自定义序列化程序(如果值为 null)。
我能够通过对 class 编写一个小扩展来覆盖此行为,这允许我的“isAuthorized”逻辑抢占空检查:
public class JsonAuthPropertyWriter extends BeanPropertyWriter {
private final Predicate<Object> authFilter;
private JsonAuthPropertyWriter(BeanPropertyWriter delegate, Predicate<Object> authFilter) {
super(delegate);
this.authFilter = authFilter;
// set null serializer or authorized null values disappear
super.assignNullSerializer(NullSerializer.instance);
}
@Override
public void serializeAsField(
Object bean,
JsonGenerator gen,
SerializerProvider prov) throws Exception {
boolean authorized = authFilter.test(bean);
if (!authorized) return;
super.serializeAsField(bean, gen, prov);
}
}
我使用 BeanSerializerModifier
:
private static class JsonAuthBeanSerializerModifier extends BeanSerializerModifier {
@Override
public List<BeanPropertyWriter> changeProperties(
SerializationConfig config,
BeanDescription beanDesc,
List<BeanPropertyWriter> beanProperties
) {
for (int i = 0; i < beanProperties.size(); i++) {
BeanPropertyWriter beanPropertyWriter = beanProperties.get(i);
JsonAuth jsonAuth = beanPropertyWriter.findAnnotation(JsonAuth.class);
if (jsonAuth != null) {
Predicate<Object> authPredicate = ...
beanProperties.set(i, new JsonAuthPropertyWriter(beanPropertyWriter, authPredicate));
}
}
return beanProperties;
}
}
我可能误解了你的意思,但这个方法似乎很有用:
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.Map;
public class Test2 {
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface JsonAuth {
}
@JsonFilter("myFilter")
public static class Contact {
@JsonAuth
String first;
@JsonAuth
String middle;
@JsonAuth
String last;
String email;
public Contact(String first, String middle, String last, String email) {
this.first = first;
this.middle = middle;
this.last = last;
this.email = email;
}
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getMiddle() {
return middle;
}
public void setMiddle(String middle) {
this.middle = middle;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
public static Map<String,Boolean> fieldSerialisationCount = new HashMap<>();
public static void main(String[] args) throws Exception {
Contact contact = new Contact("Bob", null, "Barker", null);
ObjectMapper mapper = new ObjectMapper();
FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter", new SimpleBeanPropertyFilter() {
@Override
protected boolean include(BeanPropertyWriter writer) {
return super.include(writer) && isAuthed(writer);
}
@Override
protected boolean include(PropertyWriter writer) {
return super.include(writer) && isAuthed(writer);
}
private boolean isAuthed(PropertyWriter writer) {
if (!writer.getMember().hasAnnotation(JsonAuth.class)) {
return true;
} else {
return fieldSerialisationCount.compute(writer.getName(), (n, b) -> b == null ? true : !b); // check auth here
}
}
});
mapper.setFilterProvider(filters);
ObjectWriter writer = mapper.writer(filters).withDefaultPrettyPrinter();
System.out.println(
writer.writeValueAsString(contact)
);
System.out.println(
writer.writeValueAsString(contact)
);
System.out.println(
writer.writeValueAsString(contact)
);
}
}
它每隔一段时间序列化带注释的字段,就像使用持久状态的过滤器示例一样。
请告诉我这是否适合你。
顺便说一下,我同意 Jackson 有你描述的问题,但我不知道如何解决它,所以这是一个解决方法,而不是对你原来问题的回答。