json序列化集合异常

项目场景:

服务调用过程中,请求中一个集合字段如果为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);
        }
    }

你可能感兴趣的:(Java,json,java)