Json 是一種文字形式的資料交換格式,比 xml 更為輕量。Json 的解析和生成的方式很多,在 Android 平臺上最常用的類庫有 Gson 和 FastJson 兩種,這裡要介紹的是 Gson
Gson 的 GitHub 主頁點選這裡:Gson
一、Gson的基本用法
1.1、Gson物件
在進行序列化與反序列操作前,需要先例項化一個 com .google.gson.Gson
物件,獲取 Gson 物件的方法有兩種
//通過建構函式來獲取
Gson gson = new Gson();
//通過 GsonBuilder 來獲取,可以進行多項特殊配置
Gson gson = new GsonBuilder().create();
複製程式碼
1.2、生成 Json
利用 Gson 可以很方便地生成 Json 字串,通過使用 addProperty
的四個過載方法
public static void main(String[] args) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("String", "leavesC");
jsonObject.addProperty("Number_Integer", 23);
jsonObject.addProperty("Number_Double", 22.9);
jsonObject.addProperty("Boolean", true);
jsonObject.addProperty("Char", 'c');
System.out.println();
System.out.println(jsonObject);
}
複製程式碼
addProperty
方法底層呼叫的是 add(String property, JsonElement value)
方法,即將基本資料型別轉化為了 JsonElement 物件,JsonElement 是一個抽象類,而 JsonObject 繼承了 JsonElement ,因此我們可以通過 JsonObject 自己來構建一個 JsonElement
public static void main(String[] args) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("String", "leavesC");
jsonObject.addProperty("Number", 23);
jsonObject.addProperty("Number", 22.9);
jsonObject.addProperty("Boolean", true);
jsonObject.addProperty("Char", 'c');
JsonObject jsonElement = new JsonObject();
jsonElement.addProperty("Boolean", false);
jsonElement.addProperty("Double", 25.9);
jsonElement.addProperty("Char", 'c');
jsonObject.add("JsonElement", jsonElement);
System.out.println();
System.out.println(jsonObject);
}
複製程式碼
1.3、Json與陣列、List的轉化
Json陣列 與 字串陣列
public static void main(String[] args) {
//Json陣列 轉為 字串陣列
Gson gson = new Gson();
String jsonArray = "[\"https://github.com/leavesC\",\"https://www.jianshu.com/u/9df45b87cfdf\",\"Java\",\"Kotlin\",\"Git\",\"GitHub\"]";
String[] strings = gson.fromJson(jsonArray, String[].class);
System.out.println("Json陣列 轉為 字串陣列: ");
for (String string : strings) {
System.out.println(string);
}
//字串陣列 轉為 Json陣列
jsonArray = gson.toJson(jsonArray, new TypeToken<String>() {
}.getType());
System.out.println("\n字串陣列 轉為 Json陣列: ");
System.out.println(jsonArray);
}
複製程式碼
Json陣列 與 List
public static void main(String[] args) {
//Json陣列 轉為 List
Gson gson = new Gson();
String jsonArray = "[\"https://github.com/leavesC\",\"https://www.jianshu.com/u/9df45b87cfdf\",\"Java\",\"Kotlin\",\"Git\",\"GitHub\"]";
List<String> stringList = gson.fromJson(jsonArray, new TypeToken<List<String>>() {
}.getType());
System.out.println("\nJson陣列 轉為 List: ");
for (String string : stringList) {
System.out.println(string);
}
//List 轉為 Json陣列
jsonArray = gson.toJson(stringList, new TypeToken<List<String>>() {
}.getType());
System.out.println("\nList 轉為 Json陣列: ");
System.out.println(jsonArray);
}
複製程式碼
1.4、序列化與反序列化
Gson 也提供了 toJson()
和 fromJson()
兩個方法用於轉化 Model 與 Json,前者實現了序列化,後者實現了反序列化
首先,宣告一個 User 類
/**
* 作者:chenZY
* 時間:2018/3/17 18:32
* 描述:https://github.com/leavesC
*/
public class User {
private String name;
private int age;
private boolean sex;
public User(String name, int age, boolean sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
複製程式碼
序列化的方法很簡單,呼叫 gson 物件的 toJson 方法,傳入要序列化的物件
public static void main(String[] args) {
//序列化
User user = new User("leavesC", 24, true);
Gson gson = new Gson();
System.out.println();
System.out.println(gson.toJson(user));
}
複製程式碼
反序化的方式也類似
public static void main(String[] args) {
//反序列化
String userJson = "{\"name\":\"leavesC\",\"age\":24,\"sex\":true}";
Gson gson = new Gson();
User user = gson.fromJson(userJson, User.class);
System.out.println();
System.out.println(user);
}
複製程式碼
二、屬性重新命名
繼續使用上一節宣告的 User 類,根據 User 類宣告的各個屬性名,移動端的開發者希望介面返回的資料格式即是如下這樣的
{"name":"leavesC","age":24,"sex":true}
複製程式碼
如果沒有和伺服器端溝通好或者是 API 改版了,介面返回的資料格式可能是這樣的
{"Name":"leavesC","age":24,"sex":true}
複製程式碼
{"userName":"leavesC","age":24,"sex":true}
複製程式碼
如果繼續使用上一節介紹的方法,那無疑會解析出錯 例如
public static void main(String[] args) {
//反序列化
String userJson = "{\"userName\":\"leavesC\",\"age\":24,\"sex\":true}";
Gson gson = new Gson();
User user = gson.fromJson(userJson, User.class);
System.out.println();
System.out.println(user);
}
複製程式碼
name 屬性值解析不到,所以為 null
此時為了兼顧多種格式的資料,就需要使用 SerializedName 註解 根據 SerializedName 的宣告來看,SerializedName 包含兩個屬性值,一個是字串,一個是字串陣列,而字串陣列含有預設值
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface SerializedName {
String value();
String[] alternate() default {};
}
複製程式碼
SerializedName 的作用是為了在序列化或反序列化時,指導 Gson 如果將原有的屬性名和其它特殊情況下的屬性名聯絡起來
例如,修改 User 類,為 name 宣告 SerializedName 註解,註解值為 userName
/**
* 作者:chenZY
* 時間:2018/3/17 18:32
* 描述:https://github.com/leavesC
*/
public class User {
@SerializedName("userName")
private String name;
private int age;
private boolean sex;
}
複製程式碼
在序列時,Json 格式就會相應改變
public static void main(String[] args) {
//序列化
User user = new User("leavesC", 24, true);
Gson gson = new Gson();
System.out.println();
System.out.println(gson.toJson(user));
}
複製程式碼
在反序列化時也一樣,能夠解析到正確的屬性值
public static void main(String[] args) {
//反序列化
String userJson = "{\"userName\":\"leavesC\",\"age\":24,\"sex\":true}";
Gson gson = new Gson();
User user = gson.fromJson(userJson, User.class);
System.out.println();
System.out.println(user);
}
複製程式碼
還有個問題沒解決,為了應對多種屬性名不一致的情況,難道我們要宣告多個 User 類嗎?這顯然是不現實的,所以還需要為 User 類設定多個備選屬性名,這就需要用到 SerializedName 註解的另一個屬性值 alternate 了。
/**
* 作者:chenZY
* 時間:2018/3/17 18:32
* 描述:https://github.com/leavesC
*/
public class User {
@SerializedName(value = "userName", alternate = {"user_name", "Name"})
private String name;
private int age;
private boolean sex;
}
複製程式碼
以下幾種情況都能夠被正確的反序列化
public static void main(String[] args) {
//反序列化
Gson gson = new Gson();
String userJson = "{\"userName\":\"leavesC\",\"age\":24,\"sex\":true}";
User user = gson.fromJson(userJson, User.class);
System.out.println();
System.out.println(user);
userJson = "{\"user_name\":\"leavesC\",\"age\":24,\"sex\":true}";
user = gson.fromJson(userJson, User.class);
System.out.println();
System.out.println(user);
userJson = "{\"Name\":\"leavesC\",\"age\":24,\"sex\":true}";
user = gson.fromJson(userJson, User.class);
System.out.println();
System.out.println(user);
}
複製程式碼
三、欄位過濾
有時候並不是所有的欄位都需要進行系列化和反序列化,因此需要對某些欄位進行排除,有四種方法可以來實現這種需求。
3.1、基於@Expose註解
Expose 註解包含兩個屬性值,且均宣告瞭預設值。Expose 的含義即為“暴露”,即用於對外暴露欄位,serialize 用於指定是否進行序列化,deserialize 用於指定是否進行反序列化。如果欄位不宣告 Expose 註解,則意味著不進行序列化和反序列化操作,相當於兩個屬性值均為 false 。此外,Expose 註解需要和 GsonBuilder 構建的 Gson 物件一起使用才能生效。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Expose {
boolean serialize() default true;
boolean deserialize() default true;
}
複製程式碼
Expose 註解的註解值宣告情況有四種
@Expose(serialize = true, deserialize = true) //序列化和反序列化都生效
@Expose(serialize = false, deserialize = true) //序列化時不生效,反序列化時生效
@Expose(serialize = true, deserialize = false) //序列化時生效,反序列化時不生效
@Expose(serialize = false, deserialize = false) //序列化和反序列化都不生效,和不寫註解一樣
複製程式碼
現在來看個例子,修改 User 類
/**
* 作者:chenZY
* 時間:2018/3/17 18:32
* 描述:https://github.com/leavesC
*/
public class User {
@Expose(serialize = true, deserialize = true) //序列化和反序列化都生效
private String a;
@Expose(serialize = false, deserialize = true) //序列化時不生效,反序列化時生效
private String b;
@Expose(serialize = true, deserialize = false) //序列化時生效,反序列化時不生效
private String c;
@Expose(serialize = false, deserialize = false) //序列化和反序列化都不生效,和不寫註解一樣
private String d;
private String e;
public User(String a, String b, String c, String d, String e) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.e = e;
}
@Override
public String toString() {
return "User{" +
"a='" + a + '\'' +
", b='" + b + '\'' +
", c='" + c + '\'' +
", d='" + d + '\'' +
", e='" + e + '\'' +
'}';
}
}
複製程式碼
按照如上的註解值,只有宣告瞭 Expose 註解且 serialize 值為 true 的欄位才能被序列化,只有宣告瞭 Expose 註解且 deserialize 值為 true 的欄位才能被反序列化
public static void main(String[] args) {
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
User user = new User("A", "B", "C", "D", "E");
System.out.println();
System.out.println(gson.toJson(user));
String json = "{\"a\":\"A\",\"b\":\"B\",\"c\":\"C\",\"d\":\"D\",\"e\":\"E\"}";
user = gson.fromJson(json, User.class);
System.out.println();
System.out.println(user.toString());
}
複製程式碼
3.2、基於版本
Gson 提供了 @Since 和 @Until 兩個註解基於版本對欄位進行過濾,@Since 和 @Until 都包含一個 Double 屬性值,用於設定版本號。Since 的意思是“自……開始”,Until 的意思是“到……為止”,一樣要和 GsonBuilder 配合使用。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Since {
double value();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Until {
double value();
}
複製程式碼
當版本( GsonBuilder 設定的版本) 大於或等於 Since 屬性值或小於 Until 屬性值時欄位會進行序列化和反序列化操作,而沒有宣告註解的欄位都會加入序列化和反序列操作
現在來看個例子,修改 User 類
/**
* 作者:chenZY
* 時間:2018/3/17 18:32
* 描述:https://github.com/leavesC
*/
public class User {
@Since(1.4)
private String a;
@Since(1.6)
private String b;
@Since(1.8)
private String c;
@Until(1.6)
private String d;
@Until(2.0)
private String e;
public User(String a, String b, String c, String d, String e) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.e = e;
}
@Override
public String toString() {
return "User{" +
"a='" + a + '\'' +
", b='" + b + '\'' +
", c='" + c + '\'' +
", d='" + d + '\'' +
", e='" + e + '\'' +
'}';
}
}
複製程式碼
public static void main(String[] args) {
Gson gson = new GsonBuilder().setVersion(1.6).create();
User user = new User("A", "B", "C", "D", "E");
System.out.println();
System.out.println(gson.toJson(user));
String json = "{\"a\":\"A\",\"b\":\"B\",\"c\":\"C\",\"d\":\"D\",\"e\":\"E\"}";
user = gson.fromJson(json, User.class);
System.out.println();
System.out.println(user.toString());
}
複製程式碼
3.3、基於訪問修飾符
訪問修飾符由 java.lang.reflect.Modifier 提供 int 型別的定義,而 GsonBuilder 物件的 excludeFieldsWithModifiers
方法接收一個 int 型別可變引數,指定不進行序列化和反序列化操作的訪問修飾符欄位
看個例子
/**
* 作者:chenZY
* 時間:2018/3/17 18:32
* 描述:https://github.com/leavesC
*/
public class ModifierSample {
public String publicField = "public";
protected String protectedField = "protected";
private String privateField = "private";
String defaultField = "default";
final String finalField = "final";
static String staticField = "static";
}
複製程式碼
public static void main(String[] args) {
Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.PRIVATE, Modifier.STATIC).create();
ModifierSample modifierSample = new ModifierSample();
System.out.println(gson.toJson(modifierSample));
}
複製程式碼
3.4、基於策略
GsonBuilder 類包含 setExclusionStrategies(ExclusionStrategy... strategies)
方法用於傳入不定長引數的策略方法,用於直接排除指定欄位名或者指定欄位型別
看個例子
/**
* 作者:chenZY
* 時間:2018/3/17 18:32
* 描述:https://github.com/leavesC
*/
public class Strategies {
private String stringField;
private int intField;
private double doubleField;
public Strategies(String stringField, int intField, double doubleField) {
this.stringField = stringField;
this.intField = intField;
this.doubleField = doubleField;
}
@Override
public String toString() {
return "Strategies{" +
"stringField='" + stringField + '\'' +
", intField=" + intField +
", doubleField=" + doubleField +
'}';
}
}
複製程式碼
public static void main(String[] args) {
Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
//排除指定欄位名
return fieldAttributes.getName().equals("intField");
}
@Override
public boolean shouldSkipClass(Class<?> aClass) {
//排除指定欄位型別
return aClass.getName().equals(double.class.getName());
}
}).create();
Strategies strategies = new Strategies("stringField", 111, 11.22);
System.out.println();
System.out.println(gson.toJson(strategies));
String json = "{\"stringField\":\"stringField\",\"intField\":111,\"doubleField\":11.22}";
strategies = gson.fromJson(json, Strategies.class);
System.out.println();
System.out.println(strategies);
}
複製程式碼
欄位名為 "intField" 和欄位型別為 double 的欄位都會被排除掉
setExclusionStrategies
方法在序列化和反序列化時都會生效,如果只是想指定其中一種情況下的排除策略或分別指定排除策略,可以改為使用以下兩個方法
addSerializationExclusionStrategy(ExclusionStrategy strategy);
addDeserializationExclusionStrategy(ExclusionStrategy strategy);
複製程式碼
四、個性化配置
4.1、輸出 null
對於 Gson 而言,在序列化時如果某個屬性值為 null 的話,那麼在序列化時該欄位不會參與進來,如果想要顯示輸出該欄位的話,可以通過 GsonBuilder 進行配置
/**
* 作者:chenZY
* 時間:2018/3/17 18:32
* 描述:https://github.com/leavesC
*/
public class Strategies {
private String stringField;
private int intField;
private double doubleField;
}
複製程式碼
public static void main(String[] args) {
Gson gson = new GsonBuilder()
.serializeNulls() //輸出null
.create();
Strategies strategies = new Strategies(null, 24, 22.333);
System.out.println();
System.out.println(gson.toJson(strategies));
}
複製程式碼
4.2、格式化輸出Json
預設的序列化後的 Josn 字串並不太直觀,可以選擇格式化輸出
public static void main(String[] args) {
Gson gson = new GsonBuilder()
.serializeNulls() //輸出null
.setPrettyPrinting()//格式化輸出
.create();
Strategies strategies = new Strategies(null, 24, 22.333);
System.out.println();
System.out.println(gson.toJson(strategies));
}
複製程式碼
4.3、格式化時間
Gson 也可以對時間值進行格式化
/**
* 作者:chenZY
* 時間:2018/3/17 18:32
* 描述:https://github.com/leavesC
*/
public class Strategies {
private Date date;
private Date date2;
public Strategies(Date date, Date date2) {
this.date = date;
this.date2 = date2;
}
@Override
public String toString() {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS", Locale.CHINA);
return "Strategies{" +
"date=" + simpleDateFormat.format(date) +
", date2=" + simpleDateFormat.format(date2) +
'}';
}
}
複製程式碼
public static void main(String[] args) {
Gson gson = new GsonBuilder()
.setPrettyPrinting()//格式化輸出
.setDateFormat("yyyy-MM-dd HH:mm:ss:SSS")//格式
化時間
.create();
Date date = new Date();
Strategies strategies = new Strategies(date, new Date(date.getTime() + 1000000));
System.out.println();
System.out.println(gson.toJson(strategies));
String json = "{\n" +
" \"date\": \"2018-03-17 19:38:50:033\",\n" +
" \"date2\": \"2018-03-17 19:55:30:033\"\n" +
"}";
System.out.println();
System.out.println(gson.fromJson(json, Strategies.class));
}
複製程式碼
五、TypeAdapter
TypeAdapter 是一個泛型抽象類,用於接管某種型別的序列化和反序列化過程,包含兩個抽象方法,分別用於自定義序列化和反序列化過程
public abstract void write(JsonWriter var1, T var2) throws IOException;
public abstract T read(JsonReader var1) throws IOException;
複製程式碼
下面看個簡單的例子
/**
* 作者:chenZY
* 時間:2018/3/17 18:32
* 描述:https://github.com/leavesC
*/
public class User {
private String name;
private int age;
private boolean sex;
public User() {
}
public User(String name, int age, boolean sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
複製程式碼
定義 TypeAdapter 的子類 UserTypeAdapter 來接管 User 類的序列化和反序列化過程 這裡設定當 User 類序列化時 Json 中的Key值都是大寫字母開頭,反序列化時支援“name”和“Name”兩種不同的 Json 風格
public class UserTypeAdapter extends TypeAdapter<User> {
@Override
public void write(JsonWriter jsonWriter, User user) throws IOException {
//流式序列化成物件開始
jsonWriter.beginObject();
//將Json的Key值都指定為大寫字母開頭
jsonWriter.name("Name").value(user.getName());
jsonWriter.name("Age").value(user.getAge());
jsonWriter.name("Sex").value(user.isSex());
//流式序列化結束
jsonWriter.endObject();
}
@Override
public User read(JsonReader jsonReader) throws IOException {
User user = new User();
//流式反序列化開始
jsonReader.beginObject();
while (jsonReader.hasNext()) {
switch (jsonReader.nextName()) {
//首字母大小寫均合法
case "name":
case "Name":
user.setName(jsonReader.nextString());
break;
case "age":
user.setAge(jsonReader.nextInt());
break;
case "sex":
user.setSex(jsonReader.nextBoolean());
break;
}
}
//流式反序列化結束
jsonReader.endObject();
return user;
}
}
複製程式碼
public static void main(String[] args) {
Gson gson = new GsonBuilder().registerTypeAdapter(User.class, new UserTypeAdapter()).create();
User user = new User("leavesC", 24, true);
System.out.println();
System.out.println(gson.toJson(user));
String json = "{\"Name\":\"leavesC\",\"age\":24,\"sex\":true}";
user = gson.fromJson(json, User.class);
System.out.println();
System.out.println(user);
}
複製程式碼
可以看到 User 類按照預定義的策略來完成序列化和反序列化了
六、JsonSerializer 和 JsonDeserializer
TypeAdapter 將序列化和反序列操作都接管了過來,其實 Gson 還提供了只接管序列化過程的介面,即 JsonSerializer 看個例子
public static void main(String[] args) {
Gson gson = new GsonBuilder().registerTypeAdapter(User.class, new JsonSerializer<User>() {
@Override
public JsonElement serialize(User user, Type type, JsonSerializationContext jsonSerializationContext) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("NameHi", user.getName());
jsonObject.addProperty("Sex", user.isSex());
jsonObject.addProperty("Age", user.getAge());
return jsonObject;
}
}).create();
User user = new User("leavesC", 24, true);
System.out.println();
System.out.println(gson.toJson(user));
}
複製程式碼
相對應的,JsonDeserializer 介面提供了反序列化的介面
public static void main(String[] args) {
Gson gson = new GsonBuilder().registerTypeAdapter(User.class, new JsonDeserializer<User>() {
@Override
public User deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
JsonObject jsonObject = jsonElement.getAsJsonObject();
String name = null;
//同時支援 userName 和 name 兩種情況
if (jsonObject.has("userName")) {
name = jsonObject.get("userName").getAsString();
} else if (jsonObject.has("name")) {
name = jsonObject.get("name").getAsString();
}
int age = jsonObject.get("age").getAsInt();
boolean sex = jsonObject.get("sex").getAsBoolean();
return new User(name, age, sex);
}
}).create();
String json = "{\"userName\":\"leavesC\",\"sex\":true,\"age\":24}";
User user = gson.fromJson(json, User.class);
System.out.println();
System.out.println(user);
json = "{\"name\":\"leavesC\",\"sex\":true,\"age\":24}";
user = gson.fromJson(json, User.class);
System.out.println();
System.out.println(user);
}
複製程式碼
這裡有個比較麻煩的地方,那就是在使用 TypeAdapter 、JsonSerializer 和 JsonDeserializer 時,總需要呼叫 registerTypeAdapter 方法進行註冊,那有沒有更簡單的註冊方法呢? 有的,Gosn 還提供了另一個註解 @JsonAdapter 用於進行簡單的宣告
類似於這樣,宣告瞭 User 類的序列化或反序列化操作由 UserTypeAdapter 完成,註解的優先順序高於 registerTypeAdapter 方法
@JsonAdapter(UserTypeAdapter.class)
public class User {
}
複製程式碼
七、TypeAdapterFactory
TypeAdapterFactory 是用於建立 TypeAdapter 的工廠類,通過引數 TypeToken 來查詢確定對應的 TypeAdapter,如果沒有就返回 null 並由 Gson 預設的處理方法來進行序列化和反序列化操作,否則就由使用者預定義的 TypeAdapter 來進行處理
Gson gson = new GsonBuilder().registerTypeAdapterFactory(new TypeAdapterFactory() {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
//如果 Gson 需要與 User 類相關的 TypeAdapter ,則返回我們自己定義的 TypeAdapter 物件
if (typeToken.getType().getTypeName().equals(User.class.getTypeName())) {
return (TypeAdapter<T>) new UserTypeAdapter();
}
//找不到則返回null
return null;
}
}).create();
複製程式碼
八、結語
這一篇文章好像寫得太長了一點?Gson 的知識點介紹到這裡也差不多了,以後如果還發現新內容的話我會繼續補充,現在就先這樣啦 此外,可以來簡書關注我:葉應是葉 -> https://www.jianshu.com/u/9df45b87cfdf
還有我的 GitHub 主頁: leavesC -> https://github.com/leavesC