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序列化异常。

3.1 排查步骤

  • 1)排除客户端代理等因素,用本地jackson对以上对象序列化,错误相同,证明排除客户端和客户端默认json序列化器问题。
  • 2)将Collections.EMPTY_LIST替换成new
    ArrayList(),本地依然报相同错误,排除了Collections的问题(因为其中的一些EMPTY集合不可变更)
  • 3)用包含集合属性类进行操作,本地没有报错,怀疑跟Demo有关系,仔细对比发现可能出在自定义序列化器上。
  • 4)注释掉自定义序列化器,序列化正常,证明确实是序列化器导致的序列化异常。

3.2 代码分析

  • 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.3 出错复现

  • 1) 当值为null时,写入了FiledName和Value;
  • 2)当值为空集合时,写了了FiledName,当调用自定义序列化代码时,判断为空集合直接返回,没有写入Value;
  • 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);
        }
    }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值