踩坑之Gson序列化Kotlin数据类型默认值失效

背景

最近开发的时候遇到一个问题,服务端返回的Json中如果没有某个字段的时候,我需要设置一个默认的值,但是给这个数据类设置默认值1的时候,Json解析之后还是返回了0。 但是项目里面其他数据类设置默认值是有效的。

样例

data class A(val author:String = "", val age:Int=1)

data class B(val author:String?, val age:Int =1)

项目中唯一的不同就是 A的构造函数author设置了,B没有设置。当服务端没有返回age字段的时候,A序列化age具有默认值,B的age 默认值为0, 与预期不符合

源码解析

Gson序列化,最终都是通过Gson.fromJson进行序列化的

public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
    boolean isEmpty = true;
    boolean oldLenient = reader.isLenient();
    reader.setLenient(true);
    try {
      reader.peek();
      isEmpty = false;
      TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
      TypeAdapter<T> typeAdapter = getAdapter(typeToken); //获取类型构造器
      T object = typeAdapter.read(reader); //构造类
      return object;
    } catch (EOFException e) {
      /*
       * For compatibility with JSON 1.5 and earlier, we return null for empty
       * documents instead of throwing.
       */
      if (isEmpty) {
        return null;
      }
      throw new JsonSyntaxException(e);
    } catch (IllegalStateException e) {
      throw new JsonSyntaxException(e);
    } catch (IOException e) {
      // TODO(inder): Figure out whether it is indeed right to rethrow this as JsonSyntaxException
      throw new JsonSyntaxException(e);
    } catch (AssertionError e) {
      AssertionError error = new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage());
      error.initCause(e);
      throw error;
    } finally {
      reader.setLenient(oldLenient);
    }
  }

关键的代码就是,获取TypeAdapter然后通过这个TypeAdapter#read进行构造(这里注册了很多TypeAdapter包括我们自己自定义)
通过断点我们可以知道调用ReflectiveTypeAdapterFactory 这个类

@Override public T read(JsonReader in) throws IOException {
      if (in.peek() == JsonToken.NULL) {
        in.nextNull();
        return null;
      }

      T instance = constructor.construct(); //关键
     //.....
}

constructor.construct()跟踪到ConstructorConstructor 方法

// 该类是否已经继承InstanceCreator,外部提供构造函数
final InstanceCreator<T> typeCreator = (InstanceCreator<T>) instanceCreators.get(type);
    if (typeCreator != null) {
      return new ObjectConstructor<T>() {
        @Override public T construct() {
          return typeCreator.createInstance(type);
        }
      };
    }

    // 基础类型构造int,String,char等
    @SuppressWarnings("unchecked") // types must agree
    final InstanceCreator<T> rawTypeCreator =
        (InstanceCreator<T>) instanceCreators.get(rawType);
    if (rawTypeCreator != null) {
      return new ObjectConstructor<T>() {
        @Override public T construct() {
          return rawTypeCreator.createInstance(type);
        }
      };
    } 

    // 默认构造函数
    ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType);
    if (defaultConstructor != null) {
      return defaultConstructor;
    }

    //一些队列构造Set,List,Map等
    ObjectConstructor<T> defaultImplementation = newDefaultImplementationConstructor(type, rawType);
    if (defaultImplementation != null) {
      return defaultImplementation;
    }
 
    // 非安全构造
    return newUnsafeAllocator(type, rawType);

也就是说Gson构造对象有下面优先级
1.该类自身扩展的构造方法,需要实现 InstanceCreator 接口
2.基础类型构建
3.默认构造函数
4.队列Set,List,Map等特殊构造
5.非安全构造,兜底使用
其中默认构造函数取的是空参数构造方法

private <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType) {
    try {
      final Constructor<? super T> constructor = rawType.getDeclaredConstructor();
     //....
     //....
}

unsafe 方式去构造对象,会绕过构造函数,只会在堆中去分配一个对象实例

原因定位

我们再回过来头看A和B的差异,原因就是A都默认给了参数,Kotlin(反编译成Java)会生成默认的无参构造函数,构造A对象的时候就会调用A的构造,所以赋值操作也会执行。但是B的构造函数,则没有默认的无参构造函数,所以只能通过unsafe方式构造对象,绕过了构造函数,不会执行赋初值逻辑,默认值只能是基础类型的初值

问题解决方法

本质: 尽量走构造方法,而不是走unsafe构建

  1. 构造函数所有变量设置默认值
data class B(val author:String="", val age:Int =1)

2.不复写构造函数,使用无参构造函数

class B{
  val author:String?
  val age:Int = 1
}

  1. 实现Gson#InstanceCreator接口(不推荐,稍复杂)

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
图片

  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Kotlin的数据类(data class)可以方便地进行序列化和反序列化操作。可以使用Kotlin中的内置库或第三方库来实现这些操作。 使用Kotlin内置库实现序列化和反序列化: 1. 首先,需要在build.gradle文件中添加Kotlin Serialization库的依赖: ``` implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0" ``` 2. 然后,在数据类中添加注解@Serializable,以便使用Kotlin Serialization库进行序列化和反序列化操作: ```kotlin @Serializable data class Person(val name: String, val age: Int) ``` 3. 对于序列化操作,可以使用toJsonString()方法将数据类转换为JSON格式的字符串: ```kotlin val person = Person("John", 30) val jsonString = Json.encodeToString(person) println(jsonString) // {"name":"John","age":30} ``` 4. 对于反序列化操作,可以使用decodeFromString()方法将JSON格式的字符串转换为数据类: ```kotlin val jsonString = """{"name":"John","age":30}""" val person = Json.decodeFromString<Person>(jsonString) println(person) // Person(name=John, age=30) ``` 使用第三方库Gson实现序列化和反序列化: 1. 首先,需要在build.gradle文件中添加Gson库的依赖: ``` implementation 'com.google.code.gson:gson:2.8.6' ``` 2. 然后,使用Gson对象的toJson()方法将数据类转换为JSON格式的字符串: ```kotlin val gson = Gson() val person = Person("John", 30) val jsonString = gson.toJson(person) println(jsonString) // {"name":"John","age":30} ``` 3. 对于反序列化操作,可以使用fromJson()方法将JSON格式的字符串转换为数据类: ```kotlin val jsonString = """{"name":"John","age":30}""" val person = gson.fromJson(jsonString, Person::class.java) println(person) // Person(name=John, age=30) ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值