网络解析框架-Gson使用方法总结

Gson

2018-9-5 20:32 - QG2017移动组 - 张艺隽

Gson是使用率非常高的Json解析框架,提供快捷的方法来实现Java实体类和Json字符串的转换。

  • 来自官方的说明:

Gson is a Java library that can be used to convert Java Objects into their JSON representation. It can also be used to convert a JSON string to an equivalent Java object. Gson can work with arbitrary Java objects including pre-existing objects that you do not have source-code of.

There are a few open-source projects that can convert Java objects to JSON. However, most of them require that you place Java annotations in your classes; something that you can not do if you do not have access to the source-code. Most also do not fully support the use of Java Generics. Gson considers both of these as very important design goals.

0. 参考资料

  • 你真的会用Gson吗?Gson使用指南(一)
  • 你真的会用Gson吗?Gson使用指南(二)
  • 你真的会用Gson吗?Gson使用指南(三)
  • 你真的会用Gson吗?Gson使用指南(四)

1. 开始使用

导入依赖:

implementation 'com.google.code.gson:gson:2.8.5'

2. JavaBean转Json

使用toJson()方法生成Json字符串。

  • Java:
String userData = new Gson().toJson(user);
  • Kotlin:
val userData = Gson().toJson(user)

3. Json转JavaBean

使用fromJson()方法将Json字符串转化为Java实体类
- Java:

User user = new Gson().fromJson(userData,User.class);
  • Kotlin:
val user = Gson().fromJson(userData,User::class.java)

4. @SerializedName 注解

在实际情况中,经常因为后台瞎写接口,导致代码与文档不符,这种问题常出现于大小写、下划线。例如:

realpath

realpath字段按照后台提供的接口文档编写,但后台实际代码却是使用了驼峰的realPath。这种情况下使用 @SerializedName重命名注解,将可能发生错误的字段进行重命名处理。如:
- Java

@SerializedName(value = "realpath", alternate = {"realPath","RealPath", "real_path", "Realpath"})
private String realpath;
  • Kotlin
@SerializedName(value = "realpath", alternate = {"realPath","RealPath", "real_path", "Realpath"})
var realpath : String

在注解中用alternate属性,为realpath提供了几种可能的命名,包括
- 驼峰的realPath
- 单词大写的RealPath
- 首字母大写的Realpath
- js风格的real_path

一些需要注意的点:

  • 重命名仅用于反序列化
  • 使用alternate属性时不能省略value属性,value指定字段的默认名称。
  • 当有多条字段同时符合重命名时,以(Json字符串中)最后一个出现的值为准。
{
    "realpath": "C:\\a.txt",
    "real_path": "E:\\b.jpg",
    "realPath": "F:\\c.doc"
}
  • Java
System.out.println(file.realpath); // F:\c.doc
  • Kotlin
println(file.realpath) 

5. @Expose注解

在实际情况中,有些实体类包含本地字段,这些字段无需加入json中,那么就可以用 @Expose 注解。

@Expose 注解从名字上就可以看出是暴露的意思,所以该注解是用于对外暴露字段的。该注解必须和GsonBuilder配合使用。

使用方法: 简单说来就是需要导出的字段上加上 @Expose 注解,不导出的字段不加。注意是不导出的不加

@Expose //序列化和反序列化都都生效
@Expose(deserialize = true,serialize = true) //序列化和反序列化都都生效,等价于上一条
@Expose(deserialize = true,serialize = false) //反序列化时生效
@Expose(deserialize = false,serialize = true) //序列化时生效
@Expose(deserialize = false,serialize = false) // 和不写注解一样
  • Java
String jsonBody = new GsonBuilder()
    .excludeFieldsWithoutExposeAnnotation()
    .create()
    .toJson(user);
  • Kotlin
val jsonBody = GsonBuilder()
    .excludeFieldsWithoutExposeAnnotation()
    .create()
    .toJson(user)

6.@Since和@Until注解

Gson提供基于版本的注解:@Since@Until 当设置版本大于Since或小于Until的时候将字段导出,例如:
- Java

public class User{
    @Since(2)
    public String name;

    @Until(4)
    public int age;
}
String json = new GsonBuilder()
    .setVersion(version)
    .create()
    .toJson(user);
  • Kotlin
data class(@Since(2)var name:String, @Until(4)var age:Int)
val json = GsonBuilder()
    .setVersion(version)
    .create()
    .toJson(User)
  • version < 2 : {“age”: 19}
  • version >=2 && version < 4 : {“name”: “zhangyijun”,”age”: 19}
  • version >= 4 : {“name”: “zhangyijun”}

  • @Since和@Until注解均用于序列化

7. 基于修饰符

Gson提供基于修饰符进行序列化/反序列化的方法:excludeFieldsWithModifiers(),参数由java.lang.reflect.Modifier提供。
- Java

public class User{
    private String name = "zyj";
    static int age = 18;
    final int height = 180;
}
String json = new GsonBuilder()
    .excludeFieldsWithModifiers(Modifier.FINAL,MOdifier.STATIC)
    .create()
    .toJson(user);

输出结果:{“name”:”zyj”}
- Kotlin

data class User(private var name: String = "zyj", val age: Int = 18) {
    companion object {
        var height: Int = 180
    }
}

Kotlin中static的值以companion object形式存在,使用Gson的时候默认不会将其序列化,使用excludeFieldsWithModifiers()方法后可以将static的值打印出来

val json = GsonBuilder()
    .excludeFieldsWithModifiers()
    .create()
    .toJson(user)

输出结果:

{
    "age": 18,
    "name": "zyj",
    "Companion": {
        "serialVersionUID": -3895247665888796413
    },
    "height": 180,
    "serialVersionUID": 1851844703672024324
}

在Kotlin使用excludeFieldsWithModifiers()方法后,无论是序列化还是反序列化,serialVersionUID都是可读可写的。

private static final long serialVersionUID = 1L;

为了保证程序的安全,在excludeFieldsWithModifiers()方法中至少传入private/static/final的一种,以隐藏serialVersionUID这一属性。

8. 基于策略

基于策略是一种非常灵活的规则,相比@Expose、@Since、@Until、excludeFieldsWithModifiers()更加强大,但代码量也会少量提升。Gson提供两种设置策略的方法:
- addSerializationExclusionStrategy()
- addDeserializationExclusionStrategy()

分别针对序列化和反序列化,直接看大佬的代码
- Java

Gson gson = new GsonBuilder()
    .addSerializationExclusionStrategy(new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes f) {
            // 这里作判断,决定要不要排除该字段,return true为排除
            if ("finalField".equals(f.getName())) return true; //按字段名排除
            Expose expose = f.getAnnotation(Expose.class); 
            if (expose != null && expose.deserialize() == false) return true; //按注解排除
            return false;
        }
        @Override
        public boolean shouldSkipClass(Class clazz) {
            // 直接排除某个类 ,return true为排除
            return (clazz == int.class || clazz == Integer.class);
        }
    })
    .create();

作者:怪盗kidou
链接:https://www.jianshu.com/p/0e40a52c0063
  • Kotlin
val gson = GsonBuilder()
    .addSerializationExclusionStrategy(object : ExclusionStrategy {
        override fun shouldSkipField(f: FieldAttributes): Boolean {

            if ("finalField" == f.name) return true
            val expose = f.getAnnotation(Expose::class.java)
            return expose != null && !expose.deserialize

        }

        override fun shouldSkipClass(clazz: Class<*>): Boolean {
            return clazz == Int::class.javaPrimitiveType || clazz == Int::class.java
        }
    })
    .create()

9. 自定义映射规则

GsonBuilder提供两种方法让我们自定义映射规则:

1. Policy:

GsonBuilder的setFieldNamingPolicy() 方法,参数为Gson提供的FieldNamingPolicy枚举类。
- Java

Gson gson = new GsonBuilder()
    .setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
    .create();
  • Kotlin
 val gson = GsonBuilder()
    .setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
    .create()

FieldNamingPolicy的参数有:

FieldNamingPolicy 结果(仅输出emailAddress字段)
IDENTITY {“emailAddress”:”[email protected]”}
LOWER_CASE_WITH_DASHES {“email-address”:”[email protected]”}
LOWER_CASE_WITH_UNDERSCORES {“email_address”:”[email protected]”}
UPPER_CAMEL_CASE {“EmailAddress”:”[email protected]”}
UPPER_CAMEL_CASE_WITH_SPACES {“Email Address”:”[email protected]”}

- 有减号、下划线、驼峰、空格等规则。

2. Strategy:

GsonBuilder的setFieldNamingStrategy() 方法,参数为Gson提供的FieldNamingStrategy接口

ctrl+c加ctrl+v:

  • Java
Gson gson = new GsonBuilder()
    .setFieldNamingStrategy(new FieldNamingStrategy() {
        @Override
        public String translateName(Field f) {
            //实现自己的规则
            return null;
        }
    })
    .create();

作者:怪盗kidou
链接:https://www.jianshu.com/p/0e40a52c0063
  • Kotlin
val gson = GsonBuilder()
    .setFieldNamingStrategy { f -> f?.name }
    .create()

10. Gson泛型

假设有一返回数据类:
- Java

public class UserResponse {
    public int code;
    public String message;
    public User data;
}
  • Kotlin
data class UserResponse(var code:Int, var message:String, var data:User)

在编写实体类的时候,发现data不仅是User,也有可能是List,甚至是其他类或基本类型。这时可能要编写十几个实体类,十分麻烦。

以下为Json数据

{
    "code": 200,
    "message": "success",
    "data": {
        "user": {
            "name": "zhangyijun",
            "num": 3117004910
        }
    }
}

引入泛型前,需要额外编写UserResultUserListResult两种实体类
- Java

String json = "{..........}";

UserResult userResult = new Gson().fromJson(json,UserResult.class);
User user = userResult.data;

UserListResult userListResult = new Gson().fromJson(json,UserListResult.class);
List<User> users = userListResult.data;
  • Kotlin
val json = "{..........}"

var user = Gson().fromJson(json,UserResult::class.java)

var users = Gson().fromJson(json,UserListResult::class.java)

引入泛型后只需使用统一标准格式的返回数据类即可
- Java

public class Result{

    public int code;
    public String message;
    public T data;

}
String json = "{..........}";

Type userType = new TypeToken<Result<User>>(){}.getType();
Result<User> userResult = gson.fromJson(json,userType);
User user = userResult.data;

Type userListType = new TypeToken<Result<List<User>>>(){}.getType();
Result<List<User>> userListResult = gson.fromJson(json,userListType);
List<User> users = userListResult.data;
  • Kotlin
data class Result<T>(var code: Int, var message: String, var data: T)
var userType = TypeToken<Result<User>>(){}.getType()
var user = Gson()
    .fromJson(json,userType)
    .data

var userListType = TypeToken<Result<List<User>>>(){}.getType()
var users = Gson()
    .fromJson(json,userListType)
    .data

11. 导出null值

在使用Gson时,默认不会导出null的值和键,当我们需要导出null值的时就需要使用GsonBuilder

  • Java
Gson gson = new GsonBuilder()
    .serializeNulls()
    .create();
  • Kotlin
val gson = GsonBuilder()
    .serializeNulls()
    .create()
  • 还有一些常用方法:
    1. 设置日期格式(格式化实体类中的Date)
val gson = GsonBuilder()
    .setDateFormat("yyyy-MM-dd")
    .create()
  1. 格式化输出
val gson = GsonBuilder()
    .setPrettyPrinting()
    .create()

结语:花了一晚上的时间复习Gson,总感觉有很多知识点都是用不上的,而且看完之后也很容易忘掉。但是花时间去琢磨一下也是挺不错的。Gson就像其他主流框架一样,经过十多年的发展,已经变得十分完善。这篇学习笔记中,Gson还有很多方法没有提及到,包括非常灵活但是很麻烦的TypeAdapter(用来接管某种类型的序列化和反序列化过程)。在阶段性(且碎片化)的学习中,掌握一种主流框架的50%已经是成功了,剩下的50%作为提高,可以阅读源码,这肯定会花费大量时间。考虑到搭建属于自己的技术堆栈,我可能会学习阿里出品的FastJson。

#Gson - 完()

你可能感兴趣的:(网络解析框架-Gson使用方法总结)