1、简介
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它的作用是数据标记,存储,传输,具有读写速度快、解析简单、轻量级、独立于语言和平台、具有自我描述性等特点。
像 gson、fastjson、jackson 这些都是 JSON 的解析器。而 gson 又称为 Google Gson,是 Google 发布的开源 Java 库,主要用于把 Java 对象序列化为 JSON 字符串,或把 JSON 字符串反序列化为 Java 对象。
2、JSON 语法
JSON 的语法构建于 key-value 键值对和值的有效列表(数组)之上。这两种都是常见的数据结构,大部分计算机语言都以某种形式支持它们,这使得 JSON 数据格式在同样基于这些结构的编程语言之间交换成为可能。
JSON 具有对象(Object)、数组(Array)、值(Value)、字符串(String)、数值(Number)等形式。
2.1 对象 Object
无序的键值对集合,以"{“开始,以”}“结束,在 key 后面跟一个”:“,键值对之间用”,"隔开。
json"> {"name": "英语","score": 78.3}
2.2 值 Value
可以是双引号括起来的字符串、数值、true、false、null、对象或数组,可以嵌套。
json">{"url":"https://qqe2.com","name": "欢迎使用JSON在线解析编辑器","array": { "JSON校验": "http://jsonlint.qqe2.com/", "Cron生成": "http://cron.qqe2.com/", "JS加密解密": "http://edit.qqe2.com/" },"boolean": true,"null": null, "number": 123, "object": { "a": "b", "c": "d", "e": "f"}
}
2.3 数组 Array
有序的值的集合。以"[“开始,以”]“结束。值之间使用”,"分隔。
json">"courses": [ // 一个对象{}作为一个 value,中间用逗号隔开{"name": "英语", "score": 78.3 },{"name": "数学", "score": 88.3 }
]
2.4 字符串 String
是由双引号包围的任意数量 Unicode 字符的集合,使用反斜线转义。一个字符(character)即一个单独的字符串(character string)。
json">{ "name": "Zero",
}
2.5 数值 Number
与 C 或者 Java 的数值非常相似。除去未曾使用的八进制与十六进制格式和一些编码细节。
json">{ "age": 28,
}
3、Gson 基础
3.1 Gson 的基本使用
public class GsonTest {static class GsonBean {private int i;private String string;public GsonBean(int i, String string) {this.i = i;this.string = string;}@Overridepublic String toString() {return "GsonBean{" +"i=" + i +", string='" + string + '\'' +'}';}}public static void main(String[] args) {Gson gson = new Gson();// 基本类型的序列化System.out.println(gson.toJson(1)); // 1System.out.println(gson.toJson("test")); // "test"System.out.println(gson.toJson(new int[]{1, 2, 3})); // [1,2,3]// 反序列化Integer i = gson.fromJson("1", int.class);System.out.println("i=" + i); // i=1// 引用类型的序列化与反序列化GsonBean gsonBean = new GsonBean(2, "test");String json = gson.toJson(gsonBean);System.out.println("json:" + json); // json:{"i":2,"string":"test"}GsonBean gsonBean1 = gson.fromJson(json, GsonBean.class);System.out.println("gsonBean1" + gsonBean1); // gsonBean1GsonBean{i=2, string='test'}}/*** 将 gsonBean 序列化成 JSON 格式并写入 filePath 指定的文件中*/private static void writeJsonToFile(GsonBean gsonBean, String filePath) {try (OutputStream os = new FileOutputStream(filePath);JsonWriter jsonWriter = new JsonWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8))) {Gson gson = new Gson();gson.toJson(gsonBean, new TypeToken<GsonBean>() {}.getType(), jsonWriter);jsonWriter.flush();} catch (IOException e) {e.printStackTrace();}}private static GsonBean readJsonObjectFromFile(String filePath) {try (JsonReader jsonReader = new JsonReader(new InputStreamReader(new FileInputStream(filePath)))) {Gson gson = new Gson();return gson.fromJson(jsonReader, new TypeToken<GsonBean>() {}.getType());} catch (IOException e) {e.printStackTrace();}return null;}
}
此外,给定一个 JSON 格式,需要熟练的将其转换成 JavaBean,如:
json">// 最外层是一个对象,里面有key、simpleArray、arrays、innerclass四个对象
{// 字符串对象"key": "value",// [] 表示数组,数组内对象只有value没有key就是纯数组"simpleArray": [1,2,3],// 两组[]说明是数组嵌套,内层[]内放了一个对象,该对象对应Java的内部类"arrays": [[{"arrInnerClsKey": "arrInnerClsValue","arrInnerClsKeyNub": 1}]],// 对象嵌套,对应Java的内部类,其对象名为父对象的key,即innerclass"innerclass": {"name": "zero","age": 25,"sex": "男"}
}
转换成 JavaBean 是:
public class GsonBean {private String key;private List<Integer> simpleArray;private List<List<ArraysBean>> arrays;private InnerClassBean innerclass;// getters and setters...public static class ArraysBean {private String arrInnerClsKey;private int arrInnerClsKeyNub;// getters and setters...}public static class InnerClassBean {private String name;private int age;private String sex;// getters and setters...}
}
3.2 Gson 中的注解
Gson 中有一些注解,会给序列化/反序列化工作提供很大的便利。
SerializedName 注解
@SerializedName 表示序列化时,会用给定的名字作为 key:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface SerializedName {/*** @return 在序列化/反序列化时,返回该字段想要得到的名字*/String value();/*** @return 反序列化时,返回该字段可选的名字*/String[] alternate() default {};
}
示例代码:
public class MyClass {@SerializedName("name")String a;@SerializedName(value = "name1", alternate = {"name2", "name3"})String b;String c;public MyClass(String a, String b, String c) {this.a = a;this.b = b;this.c = c;}}private void test() {MyClass target = new MyClass("v1", "v2", "v3");Gson gson = new Gson();String json = gson.toJson(target);System.out.println(json); // {"name":"v1","name1":"v2","c":"v3"}MyClass target1 = gson.fromJson("{'name1':'v1'}", MyClass.class);assertEquals("v1", target1.b);target1 = gson.fromJson("{'name2':'v2'}", MyClass.class);assertEquals("v2", target1.b);target1 = gson.fromJson("{'name3':'v3'}", MyClass.class);assertEquals("v3", target1.b);}private void assertEquals(String value, String keyValue) {assert value.equals(keyValue);}
MyClass 中,字段 a 在序列化到 Json 中之后的名字为 name,字段 b 序列化后的名字为 name1(如果指定了多个值,会用第一个),而字段 c 则采用默认策略,序列化的名字就为变量名 c。
反序列化时,Json 中的 name1、name2、name3 都是字段 b 所指定的名字,所以它们都会被反序列化到字段 b 上。
@SerializedName 主要用途就是解决后端返回的 Json 字段名字不便建立 JavaBean 字段的情况,比如说后端给的 Json 是这样的:
json">{"Type": 6,"Result": {"State": "1","Msg": "成功"},"Data": {"1": 1000000001,"2": 2800,"3": "UPDATE","4": 201901021130,"5": 2854,"6": 2,"7": 2.58,}
}
Data 中的1、2、3、4、5、6、7根本无法作为 Java 字段声明,这时就可以用 @SerializedName 注解:
public class TestPojo {@SerializedName("1")private int totalAccount;@SerializedName("2")private int numIns;@SerializedName("3")private String type;@SerializedName("4")private long time;
}
Expose 注解
还有一个 @Expose 注解用来标明该字段是否参与序列化/反序列化:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Expose {/*** If {@code true}, the field marked with this annotation is written out in the JSON while* serializing. If {@code false}, the field marked with this annotation is skipped from the* serialized output. Defaults to {@code true}.* @since 1.4*/public boolean serialize() default true;/*** If {@code true}, the field marked with this annotation is deserialized from the JSON.* If {@code false}, the field marked with this annotation is skipped during deserialization. * Defaults to {@code true}.* @since 1.4*/public boolean deserialize() default true;
}
Since 和 Until 注解
此外还有用于版本控制的注解 @Since 和 @Until:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Since {/*** the value indicating a version number since this member* or type has been present.*/double value();
}@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Until {/*** the value indicating a version number until this member* or type should be ignored.*/double value();
}
@Since 表示当被标记字段的当前版本大于等于 value() 指定的值时才参与序列化/反序列化,否则忽略;@Until 表示当前被标记的作用域的当前版本小于(注意没有等于) value() 指定的值时才参与序列化/反序列化,否则忽略。
代码示例:
public class User {private String name;@Since(1.0)@Until(1.3)private String emailAddress;@Since(1.0)@Until(1.2)private String password;// 构造方法和 toString() 省略
}
name 字段会参加所有版本的序列化/反序列化,emailAddress 会参加 [1.0,1.3) 版本之间的序列化/反序列化,password 会参加 [1.0,1.2) 版本之间的序列化/反序列化。注意需要使用 GsonBuilder 指定当前版本后创建 Gson,不能使用 Gson 的空构造方法,否则 User 中使用 @Since、@Until 指定的版本将无效:
// 使用空参构造方法创建 Gsonprivate void testUser() {Gson gson = new Gson();User user = new User("Test", "xyz@test.com", "123456");String json = gson.toJson(user);System.out.println(json); // {"name":"Test","emailAddress":"xyz@test.com","password":"123456"}User user1 = gson.fromJson(json, User.class);System.out.println(user1); // User{name='Test', emailAddress='xyz@test.com', password='123456'}}// 使用 GsonBuilder 创建 Gson 并设置其版本号private void testUserWithVersion() {Gson gson1 = new GsonBuilder().setVersion(1.1).create();User user = new User("Test", "xyz@test.com", "123456");String json1 = gson1.toJson(user);System.out.println(json1); // {"name":"Test","emailAddress":"xyz@test.com","password":"123456"}User user1 = gson1.fromJson(json1, User.class);System.out.println(user1); // User{name='Test', emailAddress='xyz@test.com', password='123456'}Gson gson2 = new GsonBuilder().setVersion(1.2).create();String json2 = gson2.toJson(user);System.out.println(json2); // {"name":"Test","emailAddress":"xyz@test.com"}User user2 = gson2.fromJson(json2, User.class);System.out.println(user2); // User{name='Test', emailAddress='xyz@test.com', password='null'}}
当指定 gson2 的版本号为 1.2 时,password 字段没有参与序列化/反序列化,说明 @Until 指定的边界不在参与序列化/反序列化的版本之内。
JsonAdapter 注解
Gson 中的解析使用了反射技术,由于反射会使性能受到影响,因此 Gson 提供了 @JsonAdapter 注解用来指定一个具体的 JavaBean 的解析类来代替默认的反射。源码如下:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface JsonAdapter {/** Either a {@link TypeAdapter} or {@link TypeAdapterFactory}, or one or both of {@link JsonDeserializer} or {@link JsonSerializer}. */Class<?> value();/** false, to be able to handle {@code null} values within the adapter, default value is true. */boolean nullSafe() default true;}
@JsonAdapter 既可以作用于类,也可以作用于字段,先看如何在类上使用。JavaBean 类上加注解 @JsonAdapter,值为代替默认的反射方式解析 Json 的类对象:
@JsonAdapter(PersonJsonAdapter.class)
public class Person {public final String firstName, lastName;public Person(String firstName, String lastName) {this.firstName = firstName;this.lastName = lastName;}// toString()...
}
PersonJsonAdapter 类需要继承 TypeAdapter 并实现 write() 和 read():
public class PersonJsonAdapter extends TypeAdapter<Person> {// implement write: combine firstName and lastName into name@Overridepublic void write(JsonWriter out, Person value) throws IOException {out.beginObject();out.name("name");out.value(value.firstName + " " + value.lastName);out.endObject();}// implement read: split name into firstName and lastName@Overridepublic Person read(JsonReader in) throws IOException {in.beginObject();in.nextName();String[] nameParts = in.nextString().split(" ");in.endObject();return new Person(nameParts[0], nameParts[1]);}
}
write() 其实就是执行序列化过程,把 Person 类写成 Json 字符串,read() 就是反序列化把 Json 字符串转换成 Person 对象。
测试代码:
private void testPerson() {Person person = new Person("first", "last");Gson gson = new Gson();String json = gson.toJson(person);System.out.println(json); // {"name":"first last"}System.out.println(gson.fromJson(json, Person.class)); // Person{firstName='first', lastName='last'}}
该注解作用于字段:
private static final class Gadget {@JsonAdapter(UserJsonAdapter.class)final User user;Gadget(User user) {this.user = user;}
}
可以在一个字段、该字段的类型以及 GsonBuilder 中(通过 registerTypeAdapter())指定不同的类型适配器(TypeAdapter),三者的优先级由高到低是:作用于字段的注解 -> 在 GsonBuilder 上注册的适配器 -> 作用于类上的注解。
这个注解引用的类必须是 TypeAdapter 或者 TypeAdapterFactory,或者是至少实现了 JsonSerializer 或 JsonDeserializer 接口之一。使用 TypeAdapterFactory 可以使该注解委派给一个封闭的 Gson 实例。
3.3 其它实用方法
主要是 GsonBuilder 中提供的常用方法:
- setPrettyPrinting():输出 Json 的时候会按照 Json 的格式输出,默认是在一行中输出。
- registerTypeAdapter():设置自定义的解析器(需要继承 TypeAdapter),是 @JsonAdapter 的等价替换方式。
3.4 对字段空值的处理
实际开发时可能会遇到后端返回的 Json 数据某个字段的值是一个空值,类似于:
json">{"name":"java","authors":""
}
JavaBean 如下:
public class GsonError {private String name;private List<AuthorsBean> authors;public static class AuthorsBean {private String id;private String name;}
}
使用默认的 Gson 进行反序列化:
public static void test2() {String json = "{\n" +" \"name\": \"java\",\n" +" \"authors\": \"\"\n" +"}";Gson gson = new Gson();GsonError gsonError = gson.fromJson(json, GsonError.class);System.out.println(gsonError);}
会抛出异常:
Caused by: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was STRING at line 3 column 17 path $.authors
异常原因是 authors 字段的值应该是一个数组,但是 JSON 数据给出的却是一个空的字符串,默认的 Gson 解析器无法处理这种类型不匹配的情况,因此抛出了异常。既然默认的解析器无法处理这种情况,那我们可以自定义反序列化过程对此做专门的处理,有两种方式:
1. 自定义 JsonDeserializer:实现 JsonDeserializer 接口,在处理反序列化的方法 deserialize() 内对可能出现问题的 key 进行特殊处理。
2. 自定义 TypeAdapter:继承 TypeAdapter,重写反序列化方法 read() 时对可能出现问题的 key 进行特殊处理。如果不想在 read()、write() 中手动处理空值,可以调用 TypeAdapter 的 nullSafe() 保证空值安全。
两种方式都需要通过 GsonBuilder 的 registerTypeAdapter() 注册才可生效。
自定义 JsonDeserializer
先来看第一种方式:
public static class GsonErrorDeserializer implements JsonDeserializer<GsonError> {@Overridepublic GsonError deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {JsonObject jsonObject = json.getAsJsonObject();GsonError gsonError = new GsonError();// 先处理 nameString name = jsonObject.get("name").getAsString();gsonError.setName(name);// 再处理 authorJsonElement authorElement = jsonObject.get("authors");if (!authorElement.isJsonArray()) {// 不是 Json 数组,视为无效值gsonError.setAuthors(null);} else {AuthorsBean[] authors = context.deserialize(authorElement, AuthorsBean[].class);gsonError.setAuthors(Arrays.asList(authors));}return gsonError;}}public static class AuthorDeserializer implements JsonDeserializer<GsonError.AuthorsBean> {@Overridepublic AuthorsBean deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {JsonObject jsonObject = json.getAsJsonObject();String id = jsonObject.get("id").getAsString();String name = jsonObject.get("name").getAsString();return new GsonError.AuthorsBean(id, name);}}private static void test2() {String json = "{\n" +" \"name\": \"java\",\n" +" \"authors\": \"\"\n" +"}";Gson gson = new GsonBuilder().registerTypeAdapter(GsonError.class, new GsonErrorDeserializer()) // 注册 GsonErrorDeserializer.registerTypeAdapter(GsonError.AuthorsBean.class, new AuthorDeserializer()) // 注册 AuthorDeserializer.create();GsonError gsonError = gson.fromJson(json, GsonError.class);System.out.println(gsonError); // GsonError{name='java', authors=null}}
这里是把两个 JsonDeserializer 的实现类作为 GsonError 的静态内部类了。通过 jsonObject.get(xxx) 得到的是 JsonElement 对象,可以用它的 isJsonArray() 判断当前的 Json 元素是否为一个数组。
自定义 TypeAdapter
再看第二种方式:
public static void test3() {String json = "{\n" +" \"name\": \"java\",\n" +" \"authors\": \"\"\n" +"}";Gson gson = new GsonBuilder().registerTypeAdapter(GsonError.class,new TypeAdapter<GsonError>() {@Overridepublic void write(JsonWriter out, GsonError value) throws IOException {if (value == null) {out.nullValue();return;}// 写 GsonError 对象out.beginObject(); // {out.name("name").value(value.getName());// 写数组out.name("authors");List<GsonError.AuthorsBean> authors = value.getAuthors();if (authors == null) {// 如果数组为空,这里有多种写法out.value("null");//out.nullValue();//out.value("");} else {// 数组不为空,开始写数组out.beginArray(); // [for (int i = 0; i < authors.size(); i++) {out.beginObject(); // {out.name("id");out.value(authors.get(i).getId());out.name("name");out.value(authors.get(i).getName());out.endObject(); // }}out.endArray(); // ]}out.endObject(); // }}@Overridepublic GsonError read(JsonReader in) throws IOException {// 先进行非空判断if (in.peek() == JsonToken.NULL) {// 如果做 doPeek() 操作拿出的 JsonToken 确实为 null,// 就消费掉这个 JsonToken 并断言它是 null。in.nextNull();return null;}GsonError gsonError = new GsonError();in.beginObject();List<GsonError.AuthorsBean> authors = new ArrayList<>();while (in.hasNext()) {// 取下一个 key,并对 key 的值进行判断switch (in.nextName()) {// 如果 key 的值为 name,那么就把对应的 value 取出设置给 GsonError 的 name 字段。case "name":gsonError.setName(in.nextString());break;case "authors":// 先对 authors 的值进行判断,如果不是 [ 开头,说明是一个无效值。if (in.peek() != JsonToken.BEGIN_ARRAY) {authors = null;in.skipValue(); // 跳过这个 valuecontinue;}// 如果 authors 的值是有效的数组值,开始解析in.beginArray();while (in.hasNext()) {String id = in.nextString();String name = in.nextString();authors.add(new GsonError.AuthorsBean(id, name));}in.endArray();break;}}in.endObject();gsonError.setAuthors(authors);return gsonError;}}).setPrettyPrinting().create();GsonError gsonError = gson.fromJson(json, GsonError.class);System.out.println(gsonError); // GsonError{name='java', authors=null}}
操作过程已经在注释中详细标出,其实就是在 read() 读取到 authors 的值时,对值做一个判断,如果是一个有效的数组值,那么它肯定是以 [ 开始的,如果不是,而是诸如 “”、“null”、null 等形式,就直接跳过这个 value 不处理。
另外在 write() 写值的时候,有个细节,就是如果 List<GsonError.AuthorsBean> authors 为空的时候值该怎么写的问题。示例代码中给出了三种写法,得到的序列化结果分别是:
json">out.value(""):
{"name": "java","authors": ""
}out.nullValue():
{"name": "java"
}out.value("null"):
{"name": "java","authors": "null"
}
可以看到当使用 out.nullValue() 时,生成的 Json 中没有对应的 key-value,即 “authors” 及其 value。
TypeAdapter 的 nullSafe()
倘若你在自定义 TypeAdapter 时不想对繁杂的空值进行处理,那么可以使用 TypeAdapter 的包装方法 nullSafe():
/*** 此包装方法可使类型适配器具有空容忍能力。通常,需要类型适配器来处理写和读方法中的空值。下面* 是通常的做法:* <pre> {@code** Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,* new TypeAdapter<Foo>() {* public Foo read(JsonReader in) throws IOException {* if (in.peek() == JsonToken.NULL) {* in.nextNull();* return null;* }* // read a Foo from in and return it* }* public void write(JsonWriter out, Foo src) throws IOException {* if (src == null) {* out.nullValue();* return;* }* // write src as JSON to out* }* }).create();* }</pre>* 使用此方法包装你的类型适配器,可以避免这种对空值的样板处理。以下代码展示了如何* 重写上面的例子。* <pre> {@code** Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,* new TypeAdapter<Foo>() {* public Foo read(JsonReader in) throws IOException {* // read a Foo from in and return it* }* public void write(JsonWriter out, Foo src) throws IOException {* // write src as JSON to out* }* }.nullSafe()).create();* }</pre>* 注意,使用了 nullSafe 之后,就不需要在我们的类型适配器中检查空值了。*/public final TypeAdapter<T> nullSafe() {return new TypeAdapter<T>() {@Override public void write(JsonWriter out, T value) throws IOException {// 如果是空值就不记录这条key-valueif (value == null) {out.nullValue();} else {// 否则就用当前的 TypeAdapter 正常序列化TypeAdapter.this.write(out, value);}}@Override public T read(JsonReader reader) throws IOException {// 如果读到一个空值,就消费掉它 if (reader.peek() == JsonToken.NULL) {reader.nextNull();return null;}// 否则就用当前的 TypeAdapter 正常反序列化return TypeAdapter.this.read(reader);}};}
参考资料:
@SerializedName注解