zoukankan      html  css  js  c++  java
  • 关于Android数据解析的容错,你做过这些么?

    Android开发者解析服务端返回的数据时候,一般会使用Gson/FastJson/moshi等数据解析框架。例如笔者项目中用的就是Gson,但是大多数使用者包括我之前都只是停留在使用的阶段。

    Gson的toJson() 和 fromJson() 这两个方法是Gson的最基本的使用方式,当被问及Gson如何对Json数据容错,如何灵活序列化和反序列化的时候,就有点懵。

    Json数据容错,最简单的方式是让前后端数据保持一致,就根本不存在容错的问题,但是现实场景并不如我们预期那么好。

    举几个例子:User类中的姓名,有的接口返回 name ,有的接口返回 username ,如何容错呢? age 字段返回的是 “18” 这样的字符串,而java对象将其解析成 Int 类型的时候,Gson 有一定的类型容错性,能够解析成功,但是如果 age 字段的返回值变成了 “” 、null呢? 如何让其不抛出异常,并且设置默认值为 0?

    接下来就详细看看,Gson是如何对数据做容错解析。

    常规的使用,除了 toJson() 将 java 对象序列化成 Json 数据,或者 fromJson() 将 Json 数据反序列化成 java 对象,唯一需要注意的就是泛型擦除(下一篇讲),针对泛型的解析,无非就是参数的差异。

    Gson 的注解:

    @SerializedName 和 @Expose

    @SerializedName可以用来配置 Json 字段的名字,最常见的场景就是不同语言的命名方式不统一,有的使用下划线,有的使用驼峰命名。如 user_name 、userName、或者不规范的 username。那如果多个接口返回的数据结构一致,只有 fieldName 不一致,这样就可以通过 @SerializedName 来做容错。

    在 @SerializedName 中,还有一个 alternate 字段,用来对同一个字段配置多个解析名称。

    class User{
    	@SerializedName(value = "user_name", alternate = arrayOf("name","username"))
    	var userName :String? = null
    	@SerializedName("user_age")
    	var age = 0
    }
    

    再来看 @Expose ,它是用于指定一个字段是否参与序列化和反序列化, 但是一旦使用@Expose,那么常规的 new Gson() 是不能生效的,需要使用 GsonBuilder 配合 .excludeFeildWithoutExposeAnnotation 方法使用,它有两个配置项:serialize 和 deserialize ,用于指定序列化和反序列化是否包含此字段,默认值都是 true

    class User{
        @SerializedName(value = "user_name",alternate = arrayOf("name","username"))
        @Expose
        var userName :String? = null
        @Expose
        var gender = 0
        var age = 0
        @Expose(serialize = true,deserialize = false)
        var genderDesc = ""
    }
    
    fun User.gsonTest(){
        // 序列化
        val user = User()
        user.userName = "xyd"
        user.age = 18
        user.gender = 1
        user.genderDesc = "男"
    
        val gson = GsonBuilder()
                .excludeFieldsWithoutExposeAnnotation()
                .create()
    
        val jsonStr = gson.toJson(user)
        Log.d("xyd","json:$jsonStr")
        // json:{"gender":1,"genderDesc":"男","user_name":"xyd"}
    
        val newUser = gson.fromJson(jsonStr,User::class.java)
        Log.d("xyd","genderDesc:${newUser.genderDesc}")
        // genderDesc:
    }
    

    可以看到,genderDesc 只参与了序列化,而未参与反序列化。

    注意: 一旦使用了 @Expose 后,所有的字段都必须要显式的标记,否则不参与序列化和反序列化。

    Gson 中的许多问题可以通过这两个重要的注解解决,但是更灵活的处理方式就需要进阶了。

    GsonBuilder 进阶

    前面了解到,想要构造一个 Gson 对象,有两种方式:new Gson() 或 利用 GsonBuilder 构造。

    例如,默认情况下, Gson 是不会解析 null 字段的,而我们通过 serializeNulls() 方法,来让 Gson 序列化 null 字段。

    val user = User()
    user.age = 18
    user.gender = 1
    
    // 序列化
    val jsonStr = GsonBuilder().create().toJson(user)
    Log.d("xyd","json:$jsonStr")
    // json:{"age":18,"gender":1}
    
    // 反序列化
    val jsonStr1 = GsonBuilder().serializeNulls().create().toJson(user)
    Log.d("xyd","json1:$jsonStr1")
    // json1:{"age":18,"gender":1,"userName":null}
    

    GsonBuild 提供了更多的操作:

    • .serializeNulls() 序列化 null 字段
    • .setDateFormat() 设置日期格式,如 setDateFormate("yyyy-MM-dd")
    • .disableInnerClassSerialization: 禁止序列化内部类
    • .generateNonExcutableJson():生成不可直接解析的 JSON,会多 )]}' 这 4 个字符。
    • .disableHtmlEscaping():禁止转移 HTML 标签
    • .setPrettyPrinting():格式化输出

    无论是注解还是 GsonBuilder 中提供的一些方法,都是 Gson 针对一些特殊场景为我们提供的便捷 API,更复杂的就不能解决了。

    TypeAdapter

    如果前面介绍的规则都满足不了业务,Gson 还有更进一步的处理方式,那就是使用 TypeAdapter。这个 TypeAdapter 实际是 Object 类型,也就是一个泛指。

    使用 TypeAdapter 就需要用到 GsonBuilder 中的 registerTypeAdapter() 方法。

    public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) {
        $Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?>
            || typeAdapter instanceof JsonDeserializer<?>
            || typeAdapter instanceof InstanceCreator<?>
            || typeAdapter instanceof TypeAdapter<?>);
        if (typeAdapter instanceof InstanceCreator<?>) {
          instanceCreators.put(type, (InstanceCreator) typeAdapter);
        }
        if (typeAdapter instanceof JsonSerializer<?> || typeAdapter instanceof JsonDeserializer<?>) {
          TypeToken<?> typeToken = TypeToken.get(type);
          factories.add(TreeTypeAdapter.newFactoryWithMatchRawType(typeToken, typeAdapter));
        }
        if (typeAdapter instanceof TypeAdapter<?>) {
          factories.add(TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter)typeAdapter));
        }
        return this;
      }
    

    可以看到注册方法,需要指定一个数据类型,且除了支持 TypeAdapter ,还支持 JsonSerializer JsonDeserializer。他们的区别是什么?

    TypeAdapter(抽象类)、JsonSerializer(接口)、JsonDeserializer(接口)。

    TypeAdapter 中包含两个主要的方法 write() 和 read() 方法,分别用于接管序列化和反序列化。

    JsonSerializer 只用来接管序列化。

    JsonDeserializer 只用来接管反序列化。

    举个使用实例:

    val user = User()
    user.age = 18
    user.gender = 1
    // 反序列化
    val jsonStr = """ {"name":"xyd","age":18,"gender":""} """
    val newUser = GsonBuilder().create.fromJson(jsonStr, User:class.java)
    Log.e("xyd", "gender")
    

    上面的例子中,gender 应该是一个 Int 值,而 Json 字符串中是 "" , 这样的代码跑起来就会直接报错。怎么处理呢?我们通过实现 JsonDeserializer 接口,来接管反序列化的操作。

    class IntegerDefaultAdapter : JsonDeserializer<Int>{
    	override fun deserialize(json : JsonElement?, typeOfT : Type?, context : JsonDeserializationContext?){
    		try{
    			return json!!.getAsInt()
    		}catch(e : NumberFormatException){
    			return 0
    		}
    	}
    }
    

    这样当转换 Int 出现异常时,返回默认值 0。然后使用 registerTypeAdapter() 方法加入其中。

    val newUser = GsonBuilder()
            .registerTypeAdapter(Int::class.java, IntegerDefault0Adapter())
            .create().fromJson(jsonStr,User::class.java)
    Log.i("xyd","gender : ${newUser.gender}")
    // gender : 0
    

    TypeAdapter 的使用,到这里就介绍完了。

  • 相关阅读:
    obj文件可视化
    TypeError: unsupported operand type(s) for +: 'range' and 'range'
    ubuntu截屏软件shutter
    什么是Redis缓存穿透、缓存雪崩和缓存击穿
    在 ASP.NET Core 5.0 中访问 HttpContext
    如何使用带有BOM的UTF8编码的C#中的GetBytes()?
    ASP.NET Core 5.0 Web API 自动集成Swashbuckle
    ASP.NET Core 5.0 的新增功能
    面试被问到SQL | delete、truncate、drop 有什么区别?
    3个值得学习和练手的.net企业级开源项目,强烈推荐
  • 原文地址:https://www.cnblogs.com/brin/p/11468297.html
Copyright © 2011-2022 走看看