BUG实录 - fastjson JSONArray.parseArray 异常

老版本 fastjson JSONArray.parseArray 失效

Posted by 壹芝 on September 1, 2018

fastjson JSONArray.parseArray 错误

整理自本人的有道历史云笔记

简介

fastjson用于将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean
之前使用过程中从来没有遇到过错误的情况,这次的情况不同,使用过程中发现老版本的fastjson的一个反序列化的BUG

二. 问题发现

公司某项目中接口使用json交互,一直运行正常,客户端部分接口上传的是JSONArray,项目发布之后部分接口出现异常
还原代码如下,实际代码比例子中的复杂很多

有错误的 fastjson 版本

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.7</version>
</dependency>

服务端版本发布前代码

@Getter
@Setter
public class Clot {
private String name;
}

@Getter
@Setter
public class ClotHeme extends Clot{
 private String level;
 private String icon;
}

新版本代码

@Getter
@Setter
@Accessors(chain = true)
public class Clot {
private String name;
}

@Getter
@Setter
public class ClotHeme extends Clot{
 private String level;
 private String icon;
}

反序列化代码

List<ClotHeme> a = JSONArray.parseArray("[{\"name\":\"Cili\"}]",ClotHeme.class);
// name 字段丢失

三. 问题分析

代码修改前后之变化了一行 @Accessors(chain = true)
类似效果如下,set之后返回this

public static class Clot {
    private String name;

    public String getName() {
        return name;
    }

    public Clot setName(String name) {
        this.name = name;
        return this;
    }
}

老版本 fasjson 在发序列化对象的时候先遍历Class里面的全部字段,遍历方法是取出setXxx方法,去掉set变成Xxx,然后对应到字段xxx

有BUG的代码如下 computeSetters

com.alibaba.fastjson.util.DeserializeBeanInfo#computeSetters

 ......
 for (Method method : clazz.getMethods()) {
    int ordinal = 0, serialzeFeatures = 0;
    String methodName = method.getName();
    if (methodName.length() < 4) {
        continue;
    }

    if (Modifier.isStatic(method.getModifiers())) {
        continue;
    }

    // support builder set
    if (!(method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(clazz))) {
        continue;
    }

    if (method.getParameterTypes().length != 1) {
        continue;
    }

    JSONField annotation = method.getAnnotation(JSONField.class);

    if (annotation == null) {
        annotation = TypeUtils.getSupperMethodAnnotation(clazz, method);
    }

    if (annotation != null) {
        if (!annotation.deserialize()) {
            continue;
        }

        ordinal = annotation.ordinal();
        serialzeFeatures = SerializerFeature.of(annotation.serialzeFeatures());

        if (annotation.name().length() != 0) {
            String propertyName = annotation.name();
            beanInfo.add(new FieldInfo(propertyName, method, null, clazz, type, ordinal, serialzeFeatures));
            TypeUtils.setAccessible(method);
            continue;
        }
    }

    if (!methodName.startsWith("set")) {
        continue;
    }
    ......

其中以下代码限制了setXxx方法是必须返回类型必须是 Void.TYPE || 和本身相等 而没有判断是否是父类

// support builder set
if (!(method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(clazz))) {
    continue;
}

三. 解决方法

1. 删除新增代码

@Accessors(chain = true)

2. 升级 fastjson 版本
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.48</version>
</dependency>

新版本 fastjson 字段处理代码如下实现如下截取computeFields部分代码

com.alibaba.fastjson.util.JavaBeanInfo#computeFields

......

if (fieldBased) {
    for (Class<?> currentClass = clazz; currentClass != null; currentClass = currentClass.getSuperclass()) {
        Field[] fields = currentClass.getDeclaredFields();

        computeFields(clazz, type, propertyNamingStrategy, fieldList, fields);
    }
    return new JavaBeanInfo(clazz, builderClass, defaultConstructor, null, factoryMethod, buildMethod, jsonType, fieldList);
}
......


......   
private static void computeFields(Class<?> clazz, Type type, PropertyNamingStrategy propertyNamingStrategy, List<FieldInfo> fieldList, Field[] fields) {
    for (Field field : fields) { // public static fields
        int modifiers = field.getModifiers();
        if ((modifiers & Modifier.STATIC) != 0) {
            continue;
        }

        if ((modifiers & Modifier.FINAL) != 0) {
            Class<?> fieldType = field.getType();
            boolean supportReadOnly = Map.class.isAssignableFrom(fieldType)
                    || Collection.class.isAssignableFrom(fieldType)
                    || AtomicLong.class.equals(fieldType) //
                    || AtomicInteger.class.equals(fieldType) //
                    || AtomicBoolean.class.equals(fieldType);
            if (!supportReadOnly) {
                continue;
            }
        }

        boolean contains = false;
        for (FieldInfo item : fieldList) {
            if (item.name.equals(field.getName())) {
                contains = true;
                break; // 已经是 contains = true,无需继续遍历
            }
        }

        if (contains) {
            continue;
        }

        int ordinal = 0, serialzeFeatures = 0, parserFeatures = 0;
        String propertyName = field.getName();

        JSONField fieldAnnotation = field.getAnnotation(JSONField.class);

        if (fieldAnnotation != null) {
            if (!fieldAnnotation.deserialize()) {
                continue;
            }

            ordinal = fieldAnnotation.ordinal();
            serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
            parserFeatures = Feature.of(fieldAnnotation.parseFeatures());

            if (fieldAnnotation.name().length() != 0) {
                propertyName = fieldAnnotation.name();
            }
        }

        if (propertyNamingStrategy != null) {
            propertyName = propertyNamingStrategy.translate(propertyName);
        }

        add(fieldList, new FieldInfo(propertyName, null, field, clazz, type, ordinal, serialzeFeatures, parserFeatures, null,
                fieldAnnotation, null));
    }
}
......