Gson 1.7.1 toJson BUG解析

由于Gson包在1.7.1版本中,对于父类与子类有相同属性时,会导致子类数据被覆盖。导致数据错误。在Gson2.0以上版本中,对这样的类进行toJson操作时会抛出异常。

Gson 1.7.1 BUG复现:

测试使用的父类:Base类,只有一个属性,name。

public class Base {

    private String name;

    public Base(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "{" + "\"name\":\"" + name + '\"' + '}';
    }
}

测试使用的子类:Student类,具有与父类相同属性 name,并提供构造对父类、子类的name赋值。

public class Student extends Base {

    private String name;

    public Student(String baseName, String studentName) {
        super(baseName);
        this.name = studentName;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "{" + "\"name\":\"" + name + '\"' + '}';
    }
}

测试开始:

public static void main(String[] args) {
        Gson gson = new Gson();
        Student student = new Student("baseName", "studentName");
        // 打印学生的json字符串
        String studentStr = gson.toJson(student);
        System.out.println(studentStr);
        // 转回学生对象
        Student fromStrStudent = gson.fromJson(studentStr, Student.class);
        System.out.println(fromStrStudent);
    }

测试结果:

可以看到,student对象复制正确。在执行toJson操作后,由于json无法保留相同key值,所以仅生成base类中的值,student类中name值丢失。

在执行fromJson操作后,base类和student类中均为原base中的name值,导致子类数据错误。

Gson 2.0 以上版本修复方案:

由于json串格式定义无法满足存储相同属性的值,所以是不可能通过代码进行修复问题的。只能在遇到此情况时,抛出异常,对开发人员进行提示。异常提示信息也非常明确。

class asia.zhq.vo.Student declares multiple JSON fields named name

2.0源码解析

原因找到了,那么google是如何实现它的呢?实现这个逻辑也是很简单的。我们来一起看一下源码:com.google.gson.internal.bind.ReflectiveTypeAdapterFactory

private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {
    Map<String, BoundField> result = new LinkedHashMap<String, BoundField>();
    if (raw.isInterface()) {
      return result;
    }

    Type declaredType = type.getType();
    while (raw != Object.class) {
      Field[] fields = raw.getDeclaredFields();
      for (Field field : fields) {
        boolean serialize = excludeField(field, true);
        boolean deserialize = excludeField(field, false);
        if (!serialize && !deserialize) {
          continue;
        }
        field.setAccessible(true);
        Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
        List<String> fieldNames = getFieldNames(field);
        BoundField previous = null;
        for (int i = 0, size = fieldNames.size(); i < size; ++i) {
          String name = fieldNames.get(i);
          if (i != 0) serialize = false; // only serialize the default name
          BoundField boundField = createBoundField(context, field, name,
              TypeToken.get(fieldType), serialize, deserialize);
          BoundField replaced = result.put(name, boundField);
          if (previous == null) previous = replaced;
        }
        if (previous != null) {
          throw new IllegalArgumentException(declaredType
              + " declares multiple JSON fields named " + previous.name);
        }
      }
      type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
      raw = type.getRawType();
    }
    return result;
  }

 

这个方法反射了被转换对象,将其转换为Map。上面代码中关键代码在这一部分。其中result.put时,如果已经有值,replaced将获取到原值,如果这个key已经在map中存在了,将抛出异常。即 declares multiple JSON fields named

for (int i = 0, size = fieldNames.size(); i < size; ++i) {
          String name = fieldNames.get(i);
          if (i != 0) serialize = false; // only serialize the default name
          BoundField boundField = createBoundField(context, field, name,
              TypeToken.get(fieldType), serialize, deserialize);
          BoundField replaced = result.put(name, boundField);
          if (previous == null) previous = replaced;
        }
        if (previous != null) {
          throw new IllegalArgumentException(declaredType
              + " declares multiple JSON fields named " + previous.name);
        }

由于这个BUG,导致了一次线上故障,在此留存备忘。

《Gson 1.7.1 toJson BUG解析》上有1条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注