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);
        }
    }

  • 20
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
当将Java对象序列化JSON字符串时,有一些字段类型是无法直接序列化JSON的。以下是一些常见的无法序列化JSON的字段类型的示例: 1. 非基本数据类型: - 自定义类:如果对象中包含自定义类的实例作为字段,那么默认情况下Jackson库无法直接序列化该字段。 - 枚举类型:枚举类型在序列化时需要进行特殊处理,以便将其转换为JSON格式。 - BigDecimal、BigInteger:这些大数类型在序列化时需要进行特殊处理。 2. Java日期和时间类型: - java.util.Date、java.sql.Date:这些日期类型在序列化时需要考虑日期格式的转换。 - java.time.LocalDate、java.time.LocalDateTime:Java 8引入的日期和时间类型需要进行特殊处理。 3. 集合类型: - java.util.Collection、java.util.Map:集合类型中的元素需要满足可序列化的要求,否则序列化过程会抛出异常。 4. 文件和流类型: - java.io.File、java.io.InputStream、java.io.OutputStream:这些文件和流类型无法直接序列化JSON。 5. 线程和锁类型: - java.lang.Thread、java.util.concurrent.locks.Lock:线程和锁类型通常是与系统相关的,无法直接序列化JSON。 对于上述无法直接序列化JSON的字段类型,可以通过自定义Jackson库的序列化器(Serializer)和反序列化器(Deserializer)来实现特定类型的序列化和反序列化逻辑。通过实现自定义的序列化器和反序列化器,可以告诉Jackson库如何将这些特殊类型转换为JSON格式或从JSON格式中还原。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值