JSON类库
文章目录
一:Jackson
1:Spring boot默认选手
新建一个spring boot项目,当引入spring-boot-starter-json
的时候,就会发现依赖中多了Jackson的身影
Jackson 有很多优点:
- 解析大文件的速度比较快;
- 运行时占用的内存比较少,性能更佳;
- API 很灵活,容易进行扩展和定制。
Jackson 的核心模块由三部分组成:
- jackson-core,核心包,提供基于“流模式”解析的相关 API,包括 JsonPaser 和 JsonGenerator。
- jackson-annotations,注解包,提供标准的注解功能;
- jackson-databind ,数据绑定包,提供基于“对象绑定”解析的相关 API ( ObjectMapper ) 和基于“树模型”解析的相关 API (JsonNode)。
2:简单使用
如果是普通maven项目想使用jackson,只需要引入jackson-databind的依赖即可
因为jackson-databind 依赖于 jackson-core 和 jackson-annotations,所以添加完 jackson-databind 之后,Maven 会自动将 jackson-core 和 jackson-annotations 引入到项目当中。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.1</version>
</dependency>
使用 ObjectMapper:
- Java对象 -> writeValue -> JSON;
- JSON -> readValue -> Java对象
Jackson 最常用的 API 就是基于”对象绑定” 的 ObjectMapper,它通过 writeValue 的系列方法将 Java 对象序列化为 JSON,并且可以存储成不同的格式。
writeValueAsString(Object value)
方法,将对象存储成字符串writeValueAsBytes(Object value)
方法,将对象存储成字节数组writeValue(File resultFile, Object value)
方法,将对象存储成文件
package org.example.open_source.json.jackson;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* <p>
* 功能描述:测试Object Mapper的使用
* </p>
*
* @author cui haida
* @date 2024/04/05/10:37
*/
public class ObjectMapperTest {
public static void main(String[] args) {
Writer writer = new Writer("cui haida", 18);
// 构建一个ObjectMapper
ObjectMapper objectMapper = new ObjectMapper();
try {
// 将java对象 -> JSON 字符串
String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(writer);
System.out.println(json);
} catch (JsonProcessingException e) {
System.out.println("转成字符串异常" + e.getMessage());
}
}
}
class Writer {
private String name;
private int age;
public Writer(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
不是所有的字段都支持序列化和反序列化,需要符合以下规则:
- 如果字段的修饰符是 public,则该字段可序列化和反序列化(不是标准写法)
- 如果字段的修饰符不是 public,但是它的 getter 方法和 setter 方法是 public,则该字段可序列化和反序列化。getter 方法用于序列化,setter 方法用于反序列化。
- 如果字段只有 public 的 setter 方法,而无 public 的 getter 方 法,则该字段只能用于反序列化
如果想更改默认的序列化和反序列化规则,需要调用 ObjectMapper 的 setVisibility()
方法。否则将会抛出 InvalidDefinitionException 异常。
ObjectMapper 通过 readValue 的系列方法从不同的数据源将 JSON 反序列化为 Java 对象。
readValue(String content, Class<T> valueType)
方法,将字符串反序列化为 Java 对象readValue(byte[] src, Class<T> valueType)
方法,将字节数组反序列化为 Java 对象readValue(File src, Class<T> valueType)
方法,将文件反序列化为 Java 对象
注意:如果反序列化的对象有带参的构造方法,它必须有一个空的默认构造方法
package org.example.open_source.json.jackson;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* <p>
* 功能描述:测试Object Mapper的使用
* </p>
*
* @author cui haida
* @date 2024/04/05/10:37
*/
public class ObjectMapperTest {
public static void main(String[] args) {
doTestForJavaObjectToJson();
doTestForJsonToJavaObject();
}
private static void doTestForJsonToJavaObject() {
ObjectMapper objectMapper = new ObjectMapper();
try {
String json = "{\"name\":\"cui haida\",\"age\":18}";
Writer writer = objectMapper.readValue(json, Writer.class);
System.out.println(writer.getName());
System.out.println(writer.getAge());
} catch (JsonProcessingException e) {
System.out.println("转成对象异常" + e.getMessage());
}
}
private static void doTestForJavaObjectToJson() {
Writer writer = new Writer("cui haida", 18);
ObjectMapper objectMapper = new ObjectMapper();
try {
String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(writer);
System.out.println(json);
} catch (JsonProcessingException e) {
System.out.println("转成字符串异常" + e.getMessage());
}
}
}
class Writer {
private String name;
private int age;
// 如果反序列化的对象有带参的构造方法,它必须有一个空的默认构造方法
public Writer() {
}
public Writer(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Jackson 最常用的 API 就是基于”对象绑定” 的 ObjectMapper,
ObjectMapper 也可以将 JSON 解析为基于“树模型”的 JsonNode 对象
private static void doTestForJsonNode() {
ObjectMapper objectMapper = new ObjectMapper();
try {
String json = "{\"name\":\"cui haida\",\"age\":18}";
JsonNode jsonNode = objectMapper.readTree(json);
System.out.println(jsonNode.get("name").asText());
System.out.println(jsonNode.get("age").asInt());
} catch (JsonProcessingException e) {
System.out.println("转成对象异常" + e.getMessage());
}
}
借助 TypeReference 可以将 JSON 字符串数组转成泛型 List
package org.example.open_source.json.jackson;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
/**
* <p>
* 功能描述:
* </p>
*
* @author cui haida
* @date 2024/04/05/16:30
*/
public class TypeReferenceTest {
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
String json = "[{\"name\":\"cuihaida\",\"age\":18}, {\"name\":\"张三\",\"age\":19}]";
try {
// 使用TypeReference<List<Author>>指定泛型类型
List<Author> authors = objectMapper.readValue(json, new TypeReference<List<Author>>() {});
System.out.println(authors);
} catch (JsonProcessingException e) {
System.out.println(e.getMessage());
}
}
}
class Author {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Author{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
3:更高级的配置
3.1:属性配置
JSON 中常常会有一些 Java 对象中没有的字段,这时候,如果直接解析的话,会抛出 UnrecognizedPropertyException 异常
class Writer{
private String name;
private int age;
// getter/setter
}
{
"name": "cui haida",
"age": 18,
"sex": "男"
}
可以通过 configure()
方法忽略掉这些“无法识别”的字段。
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
除此之外,还有其他一些有用的配置信息:
// 在序列化时忽略值为 null 的属性
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 忽略值为默认值的属性
mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_DEFAULT);
3.2:处理日期格式
对于日期类型的字段,比如说 java.util.Date,如果不指定格式,序列化后将显示为 long 类型的数据,这种默认格式的可读性很差。
{
"age" : 18,
"birthday" : 1606358621209
}
对于这种情况,可以使用两种方式解决:
第一种方案,在 getter 上使用
@JsonFormat
注解。
private Date birthday;
// GMT+8 是指格林尼治的标准时间,在加上八个小时表示你现在所在时区的时间,pattern是格式
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
ObjectMapper mapper = new ObjectMapper();
Writer person = new Writer("张三", 18);
person.setBirthday(new Date());
String jsonString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(person);
System.out.println(jsonString);
第二种方案:调用 ObjectMapper 的
setDateFormat()
方法。
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(StdDateFormat.getDateTimeInstance());
Writer person = new Writer("张三", 18);
person.setBirthday(new Date());
String jsonString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(person);
System.out.println(jsonString);
3.3:字段过滤
在将 Java 对象序列化为 JSON 时,可能有些字段需要过滤,不显示在 JSON 中,Jackson 有一种比较简单的实现方式。
@JsonIgnore 用于过滤单个字段。
@JsonIgnore
public String getName() {
return name;
}
@JsonIgnoreProperties 用于过滤多个字段。
@JsonIgnoreProperties(value = { "age","birthday" })
class Writer{
private String name;
private int age;
private Date birthday;
}
4:自定义序列化和反序列化
当 Jackson 默认序列化和反序列化不能满足实际的开发需要时,可以自定义新的序列化和反序列化类。
自定义的序列化类需要继承 StdSerializer,同时重写 serialize()
方法,利用 JsonGenerator 生成 JSON
package org.example.open_source.json.jackson;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
/**
* <p>
* 功能描述:
* </p>
*
* @author cui haida
* @date 2024/04/05/16:47
*/
public class MySerializer extends StdSerializer<Man> {
protected MySerializer(Class<Man> t) {
super(t);
}
public MySerializer() {
this(null);
}
// 这里设置age的序列化进行测试
@Override
public void serialize(Man man, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
// jsonGenerator.writeNumberField("age", man.getAge());
jsonGenerator.writeStringField("name", man.getName());
jsonGenerator.writeEndObject();
}
}
class Man{
private int age;
private String name;
public Man(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package org.example.open_source.json.jackson;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
/**
* <p>
* 功能描述:
* </p>
*
* @author cui haida
* @date 2024/04/05/16:49
*/
public class MySerializerTest {
/**
* 主函数入口。
* 该函数创建一个Man对象,并使用自定义的序列化器将其转换为JSON字符串。
* @param args 命令行参数(未使用)
*/
public static void main(String[] args) {
// 创建Man对象,初始化年龄和姓名
Man man = new Man(19, "cui haida");
// 创建ObjectMapper实例,用于对象与JSON之间的转换
ObjectMapper mapper = new ObjectMapper();
// 创建SimpleModule用于注册自定义序列化器
SimpleModule module = new SimpleModule("my-serializer", new Version(1, 0, 0, null, null, null));
// 将自定义的MySerializer注册为Man类的序列化器
module.addSerializer(Man.class, new MySerializer());
// 将模块注册到ObjectMapper,使自定义序列化器生效
mapper.registerModule(module);
try {
// 将Man对象转换为JSON字符串
String json = mapper.writeValueAsString(man);
// 打印JSON字符串
System.out.println(json);
} catch (JsonProcessingException e) {
// 处理JSON处理异常,打印异常信息
System.out.println(e.getMessage());
}
}
}
自定义序列化类中没有添加 age 字段,所以只输出了 name 字段。
而自定义的反序列化类,继承 StdDeserializer,同时重写 deserialize()
方法,利用 JsonGenerator 读取 JSON
package org.example.open_source.json.jackson;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
/**
* <p>
* 功能描述:自定义反序列化
* </p>
*
* @author cui haida
* @date 2024/04/05/17:00
*/
public class MyDeserializer extends StdDeserializer<Woman> {
protected MyDeserializer(Class<?> vc) {
super(vc);
}
public MyDeserializer() {
this(null);
}
/**
* 从JSON解析器中反序列化出一个Woman对象。
* @param p JsonParser,用于解析JSON内容。
* @param ctxt DeserializationContext,提供反序列化时的上下文信息。
* @return Woman,反序列化后的Woman对象。
* @throws IOException 如果在读取JSON时发生IO异常。
* @throws JsonProcessingException 如果在处理JSON时发生异常。
*/
@Override
public Woman deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// 从JsonParser读取JSON树
JsonNode node = p.getCodec().readTree(p);
Woman woman = new Woman();
// 从JSON节点中提取年龄和姓名,并设置到Woman对象中
int age = (Integer) node.get("age").numberValue();
String name = node.get("name").asText();
woman.setAge(age);
woman.setName(name);
return woman;
}
}
class Woman{
private int age;
private String name;
public Woman() {
}
public Woman(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Woman{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
二:fastJson
是阿里巴巴开源的一款 JSON 解析库,可以将 Java 对象序列化成 JSON 字符串,同时也可以将 JSON 字符串反序列化为 Java 对象。
1:简单入门
fastJson是阿里巴巴开源的一款 JSON 解析库,可以将 Java 对象序列化成 JSON 字符串,同时也可以将 JSON 字符串反序列化为 Java 对象。
- 提供了服务器端和安卓客户端两种解析工具,性能表现还不错。
- 提供了便捷的方式来进行 Java 对象和 JSON 之间的互转,
toJSONString()
方法用来序列化,parseObject()
方法用来反序列化。 - 允许转换预先存在的无法修改的对象(只有 class、没有源代码)。
- 对 Java 泛型有着广泛的支持。
- 支持任意复杂的对象(深度的继承层次)。
2012 年的时候,我就被开源中国评选为最受欢迎的国产开源软件之一。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version> <!-- 版本视情况而定 -->
</dependency>
package org.example.open_source.json;
import com.alibaba.fastjson.JSON;
/**
* <p>
* 功能描述:简单指南
* </p>
*
* @author cui haida
* @date 2024/04/04/16:37
*/
public class SimpleDemo {
public static void main(String[] args) {
Person person = new Person();
person.setAge(18);
person.setName("cuihaida");
// java对象 -> json
String json = JSON.toJSONString(person);
System.out.println(json);
// json -> java对象
Person person1 = JSON.parseObject(json, Person.class);
System.out.println(person1.getName() + ": " + person1.getAge());
// json -> java array
List<Person> list = JSON.parseArray("[{\"age\":18,\"name\":\"张三\"},{\"age\":19,\"name\":\"李四\"}]", Person.class);
list.forEach(item -> System.out.println(item.getName() + ": " + item.getAge()));
}
}
class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
如果没有特殊要求的话,我敢这么说,以上 3 个方法就可以覆盖到你绝大多数的业务场景了
2:使用注解
- 有时候,你的 JSON 字符串中的 key 可能与 Java 对象中的字段不匹配,比如大小写;
- 有时候,你需要指定一些字段序列化但不反序列化;
- 有时候,你需要日期字段显示成指定的格式。
这些特殊场景,fastJson 统统为你考虑到了,只需要在对应的字段上加上 @JSONField
注解就可以了。
public @interface JSONField {
// ...;
String name() default ""; // name 用来指定字段的名称
String format() default ""; // format 用来指定日期格式
// serialize 和 deserialize 用来指定是否序列化和反序列化。
boolean serialize() default true;
boolean deserialize() default true;
// ...;
}
package org.example.open_source.json;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import java.util.Date;
/**
* <p>
* 功能描述:
* </p>
*
* @author cui haida
* @date 2024/04/04/16:55
*/
public class JsonForAnnotation {
public static void main(String[] args) {
Writer writer = new Writer();
writer.setAge(18);
writer.setName("王五");
writer.setBirthday(new Date());
String json = JSON.toJSONString(writer);
// {"Age":18,"birthday":"2024年04月04日"}
// 因为name属性被@JSONField(serialize = false)注解了,所以不会被序列化
// 因为birthday属性被@JSONField(format = "yyyy年MM月dd日")注解了,所以会被格式化
// 因为age属性被@JSONField(name = "Age")注解了,所以会被重命名
System.out.println(json);
}
}
class Writer {
private int age;
private String name;
private Date birthday;
@JSONField(format = "yyyy年MM月dd日")
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@JSONField(name = "Age")
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@JSONField(serialize = false)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3:序列化特性
为了满足更多个性化的需求,我在 SerializerFeature 类中定义了很多特性,你可以在调用 toJSONString()
方法的时候进行指定。
PrettyFormat
,让 JSON 格式打印得更漂亮一些WriteClassName
,输出类名UseSingleQuotes
,key 使用单引号WriteNullListAsEmpty
,List 为空则输出 []WriteNullStringAsEmpty
,String 为空则输出- …;
String json2 = JSON.toJSONString(writer, SerializerFeature.PrettyFormat, SerializerFeature.WriteClassName);
System.out.println(json2);
{
"@type":"org.example.open_source.json.Writer",
"Age":18,
"birthday":"2024年04月04日"
}
4:为什么这么快?
把 Java 对象序列化成 JSON 字符串,是不可能使用字符串直接拼接的,因为这样性能很差。比字符串拼接更好的办法就是使用 StringBuilder
。
但StringBuilder在性能上还有上升的空间。于是fastJson就创造了一个 SerializeWriter 类,专门用来序列化。
SerializeWriter 类中包含了一个 char[] buf
,每序列化一次,都要做一次分配
fastJson 使用了 ThreadLocal 来进行优化,这样就能够有效地减少对象的分配和垃圾回收,从而提升性能。
除此之外,还有很多其他的细节,比如说使用 IdentityHashMap 而不是 HashMap;使用asm技术避免反射reflect造成的开销
5:AutoType
快的同时,也带来了一些安全性的问题。尤其是 AutoType 的引入,让黑客有了可乘之机。
- 1.2.59 发布,增强 AutoType 打开时的安全性
- 1.2.60 发布,增加了 AutoType 黑名单,修复拒绝服务安全问题
- 1.2.61 发布,增加 AutoType 安全黑名单
- 1.2.62 发布,增加 AutoType 黑名单、增强日期反序列化和 JSONPath
- 1.2.66 发布,Bug 修复安全加固,并且做安全加固,补充了 AutoType 黑名单
- 1.2.67 发布,Bug 修复安全加固,补充了 AutoType 黑名单
- 1.2.68 发布,支持 GEOJSON,补充了 AutoType 黑名单。(引入一个 safeMode 的配置,配置 safeMode 后,无论白名单和黑名单,都不支持 autoType。)
- 1.2.69 发布,修复新发现高危 AutoType 开关绕过安全漏洞,补充了 AutoType 黑名单
- 1.2.70 发布,提升兼容性,补充了 AutoType 黑名单
为了彻底解决 AutoType 带来的问题,在 1.2.68 版本中,我引入了 safeMode 的安全模式,无论白名单和黑名单,都不支持 AutoType,这样就可以彻底地杜绝攻击。
if (safeMode) {
throw new JSONException("safeMode not support autoType : " + typeName);
}
安全模式下,checkAutoType()
方法会直接抛出异常
三:Gson
JSON解析三巨头:
Jackson:Spring Boot 的默认 JSON 解析器,优点是运行时占用的内存较少,8.8k star
Fastjson:阿里出品,优点是速度更快,25.6k star
Gson:谷歌出品,优点是处理任意的 Java 对象,对泛型的支持也更加的友好,23k star
https://github.com/google/gson
1:简单入门
Gson其实性能非常强大,下面是测试结果:
- 在反序列化 25M 以上的字符串时没有出现过任何问题。
- 可以序列化 140 万个对象的集合。
- 可以反序列化包含 87000 个对象的集合。
- 将字节数组和集合的反序列化限制从 80K 提高到 11M 以上。
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
package org.example.open_source.json.gson;
import com.google.gson.Gson;
import java.util.Arrays;
/**
* <p>
* 功能描述:
* </p>
*
* @author cui haida
* @date 2024/04/04/17:35
*/
public class BaseTest {
public static void main(String[] args) {
// 基本类型序列化
Gson gson = new Gson();
System.out.println(gson.toJson(18));
System.out.println(gson.toJson("沉默"));
System.out.println(gson.toJson(new Integer(18)));
int[] values = { 18,20 };
System.out.println(gson.toJson(values));
// 反序列化
Gson gson2 = new Gson();
int one = gson2.fromJson("1", int.class);
Integer two = gson2.fromJson("2", Integer.class);
Boolean false1 = gson2.fromJson("false", Boolean.class);
String str = gson2.fromJson("\"张三\"", String.class);
String[] anotherStr = gson2.fromJson("[\"沉默\",\"张三\"]", String[].class);
System.out.println(one);
System.out.println(two);
System.out.println(false1);
System.out.println(str);
System.out.println(Arrays.toString(anotherStr));
}
}
对于自定义实体类的json解析和系列化,有如下几点说明
- 推荐使用
private
修饰字段。 - 不需要使用任何的注解来表明哪些字段需要序列化,哪些字段不需要序列化,默认全部序列化,包括从父类继承来的
- 如果一个字段被
transient
关键字修饰的话,它将不参与序列化。 - 如果一个字段的值为 null,它不会在序列化后的结果中显示。
- JSON 中缺少的字段将在反序列化后设置为默认值,引用数据类型的默认值为 null,数字类型的默认值为 0,布尔值默认为 false。
package org.example.open_source.json.gson;
import com.google.gson.Gson;
/**
* <p>
* 功能描述:
* </p>
*
* @author cui haida
* @date 2024/04/04/17:40
*/
public class BaseForObjectTest {
public static void main(String[] args) {
Writer writer = new Writer(18, "cuihaida", "男");
Gson gson = new Gson();
String json = gson.toJson(writer);
System.out.println(json);
Writer writer1 = gson.fromJson(json, Writer.class);
System.out.println(writer1.getName() + ": " + writer1.getAge() + ": " + writer1.getSex());
}
}
class Writer {
private int age;
private String name;
private transient String sex; // 这个字段不会被序列化
public Writer(int age, String name, String sex) {
this.age = age;
this.name = name;
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
2:泛型问题及Type
toJson
是一个细心的方法,在你调用 toJson()
方法进行序列化的时候,会先判 null,防止抛出 NPE,再通过 getClass()
获取参数的类型,然后进行序列化。
public String toJson(Object src) {
// 防止NPE被抛出
if (src == null) {
return toJson(JsonNull.INSTANCE);
}
return toJson(src, src.getClass());
}
但是呢?对于泛型来说,getClass()
的时候会丢掉参数化类型:
public class Foo<T> {
T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
public static void main(String[] args) {
Gson gson = new Gson();
Foo<Bar> foo = new Foo<Bar>();
Bar bar = new Bar();
foo.set(bar);
String json = gson.toJson(foo);
}
}
class Bar{
private int age = 10;
private String name = "图灵";
}
foo 的实际类型为 Foo<Bar>
,但在调用 foo.getClass()
的时候,只会得到 Foo,这就意味着她并不知道 foo 的实际类型。
序列化的时候还好,反序列化的时候就无能为力了。
Foo<Bar> foo1 = gson.fromJson(json, foo.getClass()); // error -> ClassCaseException
Bar bar1 = foo1.get();
默认情况下,泛型的参数类型会被转成 LinkedTreeMap,这显然并不是我们预期的 Bar
于是,在tojson
的体内植入了另外两种方法,带 Type 类型参数的
public String toJson(Object src) {
if (src == null) {
return this.toJson((JsonElement)JsonNull.INSTANCE);
} else {
return this.toJson((Object)src, (Type)src.getClass());
}
}
这样的话,你在进行泛型的序列化和反序列化时,就可以指定泛型的参数化类型了。
package org.example.open_source.json.gson;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
/**
* <p>
* 功能描述:
* </p>
*
* @author cui haida
* @date 2024/04/04/18:01
*/
public class Foo<T> {
T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
public static void main(String[] args) {
Gson gson = new Gson();
Foo<Bar> foo = new Foo<>();
Bar bar = new Bar();
foo.set(bar);
Type fooType = new TypeToken<Foo<Bar>>() {}.getType(); // 指定泛型type
String json = gson.toJson(foo, fooType); // 同时将泛型类型传给toJson方法
Foo<Bar> foo1 = gson.fromJson(json, fooType);
Bar bar1 = foo1.get();
System.out.println(bar1.getName() + " " + bar1.getAge());
}
}
class Bar{
private int age = 10;
private String name = "图灵";
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3:GsonBuilder个性化定制
3.1:setPrettyPrinting
public class Writer {
private int age = 18;
private String name = "张三";
public static void main(String[] args) {
Writer writer = new Writer();
Gson gson = new Gson();
String json = gson.toJson(writer);
System.out.println(json);
Gson gson1 = new GsonBuilder().setPrettyPrinting().create();
String jsonOutput = gson1.toJson(writer);
System.out.println(jsonOutput);
}
}
{"age":18,"name":"张三"}
// pretty之后
{
"age": 18,
"name": "张三"
}
通过 setPrettyPrinting()
定制后,输出的格式更加层次化、立体化,字段与值之间有空格,每个不同的字段之间也会有换行。
3.2:serializeNulls
toJson
在序列化的时候会忽略 null 值的字段,如果不想这样的话,同样可以通过 GsonBuilder 的 serializeNulls将null也进行序列化。
public class Writer {
private int age = 18;
private String name = null;
public static void main(String[] args) {
Writer writer = new Writer();
Gson gson = new Gson();
String json = gson.toJson(writer);
System.out.println(json);
Gson gson2 = new GsonBuilder().serializeNulls().create();
String jsonOutput2 = gson2.toJson(writer);
System.out.println(jsonOutput2);
}
}
{"age":18}
{"age":18,"name":null}
3.3:保留和过滤
也许,你在序列化和反序列化的时候想要筛选一些字段,GsonBuilder也考虑到这种需求了,主要有如下两种方案。
第一种,通过 Java 修饰符。
你之前也看到了,使用 transient
关键字修饰的字段将不会参与序列化和反序列化。同样的,static
关键字修饰的字段也不会。
如果你想保留这些关键字修饰的字段,可以这样做。
保留单种
Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).create();
保留多种。
Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT, Modifier.VOLATILE)
.create();
第二种,通过
@Expose
注解。
要使用 @Expose
注解,你需要先这样做:
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
再在需要序列化和反序列化的字段上加上 @Expose
注解,如果没加的话,该字段将会被忽略。