服务调用过程中,请求中一个集合字段如果为null则调用成功,如果为空集合则调用失败。
请求报文如Demo,如果userIds赋值为null,则可以正常调用,赋值为Collections.EMPTY_LIST,则调用过程出现异常。
public static void main(String[] args) {
//此对象可以远程调用
Demo demo = new Demo();
demo.setName("name");
demo.setUserIds(null);
//此对象可以远程调用出错
Demo demoEmpty = new Demo();
demoEmpty.setName("name");
demoEmpty.setUserIds(Collections.EMPTY_LIST);
}
@Data
public static class Demo{
@JsonSerialize(using = ListJsonSerializer.class)
private List<String> userIds;
private String name;
}
public static class ListJsonSerializer extends JsonSerializer<List<String>> {
@Override
public void serialize(List<String> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (CollectionUtils.isEmpty(value)) {
return;
}
String str = value.stream().collect(Collectors.joining("|"));
gen.writeString(str);
}
}
通过日志发现错误信息为com.fasterxml.jackson.core.JsonGenerationException: Can not write a field name, expecting a value,明确是json序列化异常。
排查步骤
1.排除客户端代理等因素,用本地jackson对以上对象序列化,错误相同,证明排除客户端和客户端默认json序列化器问题。
2.将Collections.EMPTY_LIST替换成new ArrayList(),本地依然报相同错误,排除了Collections的问题(因为其中的一些EMPTY集合不可变更)
3.用包含集合属性类进行操作,本地没有报错,怀疑跟Demo有关系,仔细对比发现可能出在自定义序列化器上。
4.注释掉自定义序列化器,序列化正常,证明确实是序列化器导致的序列化异常。
分析原因
1.自定义序列化器代码如下,其中判断如果值为空集合或者null,则不写入任何数据,理论上赋值为空集合或者null 应该都抛异常或者都正常才对。
public static class ListJsonSerializer extends JsonSerializer<List<String>> {
@Override
public void serialize(List<String> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (CollectionUtils.isEmpty(value)) {
return;
}
String str = value.stream().collect(Collectors.joining("|"));
gen.writeString(str);
}
}
2.根据调用流程可知,这个bean的序列化是由BeanPropertyWriter中的serializeAsField进行序列化的,从执行代码中可以看出如果Property的值为null,则会用_nullSerializer进行序列化,不会走到自定义序列化中;如果Property的值不为null,才会走到自定义序列化中。所以判定为自定义序列化代码有问题。
@Override
public void serializeAsField(Object bean, JsonGenerator gen,
SerializerProvider prov) throws Exception {
// inlined 'get()'
final Object value = (_accessorMethod == null) ? _field.get(bean)
: _accessorMethod.invoke(bean, (Object[]) null);
// Null handling is bit different, check that first
if (value == null) {
// 如果Property值为null,则用_nullSerializer进行序列化
if (_nullSerializer != null) {
// 先写入FiledName
gen.writeFieldName(_name);
// 写入Value
_nullSerializer.serialize(null, gen, prov);
}
return;
}
// then find serializer to use
JsonSerializer<Object> ser = _serializer;
if (ser == null) {
Class<?> cls = value.getClass();
PropertySerializerMap m = _dynamicSerializers;
ser = m.serializerFor(cls);
if (ser == null) {
ser = _findAndAddDynamic(m, cls, prov);
}
}
// and then see if we must suppress certain values (default, empty)
if (_suppressableValue != null) {
if (MARKER_FOR_EMPTY == _suppressableValue) {
if (ser.isEmpty(prov, value)) {
return;
}
} else if (_suppressableValue.equals(value)) {
return;
}
}
// For non-nulls: simple check for direct cycles
if (value == bean) {
// four choices: exception; handled by call; pass-through or write null
if (_handleSelfReference(bean, gen, prov, ser)) {
return;
}
}
// 先写入FiledName
gen.writeFieldName(_name);
if (_typeSerializer == null) {
// 如果Property值不为null,则用用自定义序列化
// 写入Value
ser.serialize(value, gen, prov);
} else {
ser.serializeWithType(value, gen, prov, _typeSerializer);
}
}
3.由上述代码流程可以看出,如果要序列化一个Property,首先是写入Property的FiledName然后是Value;
3.1 当值为null时,写入了FiledName和Value;
3.2 当值为空集合时,写了了FiledName,当调用自定义序列化代码时,判断为空集合直接返回,没有写入Value;
3.3 当序列化下一个Property时,会写入FieldName,这时候序列化器中相当于连续出现了FieldName,所以会报Can not write a field name, expecting a value
自定义序列化:需要经过充分测试和了解处理过程
在自定义序列化代码中,如果集合为空,需要写入一个空value即可;
public static class ListJsonSerializer extends JsonSerializer<List<String>> {
@Override
public void serialize(List<String> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (CollectionUtils.isEmpty(value)) {
// 此处写一个null值
gen.writeNull();
return;
}
String str = value.stream().collect(Collectors.joining("|"));
gen.writeString(str);
}
}