1 简介
json : JavaScript Object Notation,JS对象简谱。
官网: https://www.json.org/json-zh.html
2 使用场景
网络传输
描述同样的信息,json相比xml占用更少的空间,如:
<?xml version="1.0" encoding="UTF-8"?> <person> <id>1</id> <name>张三</name> <age>30</age> </person>
json表示:
{ "id":1, "name":"张三", "age":30 }
序列化存储
3 java里面操作json有哪些技术?
所谓的操作指的是什么?
把java里面的bean、map、collection等转为json字符串(序列化)或反向操作(反序列化)。
java里操作json的技术一览
4 fastjson
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
4.1 序列化
拿一个Person类为例
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Date;
@Data
public class Person {
/**
* 用户id
*/
private Long id;
private String name;
@JSONField(serialize = false, deserialize = false)
private String pwd;
/**
* 地址
* name = "address":指定属性名和json字符串key的对应关系 addr---address
*/
@JSONField(name = "address")
private String addr;
/**
* 网站
*/
private String websiteUrl;
private Date registerDate;
private LocalDateTime birthDay;
}
包含null
/** * 测试序列化: * 把bean转为json字符串 */ @Test public void test1(){ Person person = new Person(); person.setId(1L); // person.setName("王二麻子"); person.setPwd("123456"); person.setAddr("河南"); person.setWebsiteUrl("http://www.roadjava.com"); person.setRegisterDate(new Date()); person.setBirthDay(LocalDateTime.now()); // 序列化 /* WriteMapNullValue:指定序列化时包含null */ String string = JSON.toJSONString(person, SerializerFeature.WriteMapNullValue); System.out.println(string); }
{"address":"河南","birthDay":"2023-07-04T21:17:19.662","id":1,"name":null,"registerDate":1688476639629,"websiteUrl":"http://www.roadjava.com"}
日期时间格式化
import com.alibaba.fastjson.annotation.JSONField; import lombok.Data; import java.time.LocalDateTime; import java.util.Date; @Data public class Person { /** * 用户id */ private Long id; private String name; @JSONField(serialize = false, deserialize = false) private String pwd; /** * 地址 * name = "address":指定属性名和json字符串key的对应关系 addr---address */ @JSONField(name = "address") private String addr; /** * 网站 */ private String websiteUrl; @JSONField(format = "yyyy-MM-dd HH:mm:ss") private Date registerDate; @JSONField(format = "yyyy-MM-dd HH:mm:ss") private LocalDateTime birthDay; }
{"address":"河南","birthDay":"2023-07-04 21:14:26","id":1,"name":null,"registerDate":"2023-07-04 21:14:26","websiteUrl":"http://www.roadjava.com"}
奇怪的"$ref"
/** * 测试fastjson的引用探测 * $ref:对象中多次引用了同一个其他对象的时候,序列化就会出现$ref */ @Test public void test$Ref() { List<Person> list = new ArrayList<>(); Person person = new Person(); person.setId(33L); person.setName("王二麻子"); list.add(person); list.add(person); list.add(person); String string = JSON.toJSONString(list); System.out.println(string); }
[{"id":33,"name":"王二麻子"},{"$ref":"$[0]"},{"$ref":"$[0]"}]
/** * 测试fastjson的引用探测 * $ref:对象中多次引用了同一个其他对象的时候,序列化就会出现$ref */ @Test public void test$Ref() { List<Person> list = new ArrayList<>(); Person person = new Person(); person.setId(33L); person.setName("王二麻子"); list.add(person); list.add(person); list.add(person); // DisableCircularReferenceDetect 禁用引用探测功能 String string = JSON.toJSONString(list,SerializerFeature.DisableCircularReferenceDetect); System.out.println(string); }
[{"id":33,"name":"王二麻子"},{"id":33,"name":"王二麻子"},{"id":33,"name":"王二麻子"}]
SerializeFilter定制处理
对属性或属性值在序列化前做定制化处理
/** * SerializeFilter定制处理.要求: * 输出的json字符串的key是大写的、驼峰转下划线 */ @Test public void testSerializeFilter() { Person person = new Person(); person.setId(1L); person.setName("王二麻子"); person.setPwd("123"); person.setAddr("河南"); person.setWebsiteUrl("http://www.roadjava.com"); person.setRegisterDate(new Date()); person.setBirthDay(LocalDateTime.now()); /* object: person对象 name: 属性 value: name属性对应的值 */ NameFilter nameFilter = (object, name, value) -> name.toUpperCase(); String string = JSON.toJSONString(person, nameFilter); System.out.println(string); }
{"ADDRESS":"河南","BIRTHDAY":"2023-07-04T21:25:08.527","ID":1,"NAME":"王二麻子","REGISTERDATE":1688477108492,"WEBSITEURL":"http://www.roadjava.com"}
美化输出
// 美化格式输出方式1 String string = JSON.toJSONString(person, true); // 美化格式输出方式2 String string = JSON.toJSONString(person, SerializerFeature.WriteMapNullValue, SerializerFeature.PrettyFormat);
{ "address":"河南", "birthDay":"2023-07-04 21:30:38", "id":1, "name":null, "registerDate":"2023-07-04 21:30:38", "websiteUrl":"http://www.roadjava.com" }
4.2 反序列化
假设返回给前端的是ResultVO<T>
import lombok.Data;
@Data
public class ResultVO<T> {
private Boolean success = Boolean.TRUE;
private T data;
private ResultVO() {
}
public static <T> ResultVO<T> buildSuccess(T t) {
ResultVO<T> resultVO = new ResultVO<>();
resultVO.setData(t);
return resultVO;
}
}
泛型处理
==fastjson对于json中多的key默认的处理就是忽略==
/** * 测试反序列化 * json字符串-->bean */ @Test public void testDeSerialize() { String jsonStr = "{\"address\":\"河南\",\"birthDay\":\"2021-08-17 03:38:09\",\"id\":1,\"name\":null,\"pwd\":\"123\",\"registerDate\":\"2021-08-17 03:38:09\",\"websiteUrl\":\"http://www.roadjava.com\"}"; // 反序列化为 person对象 Person person = JSON.parseObject(jsonStr, Person.class); System.out.println(person); // 返回给调用端 ResultVO ResultVO<Person> personResultVO = ResultVO.buildSuccess(person); String voJsonStr = JSON.toJSONString(personResultVO); // 调用端需要把 voJsonStr 反序列化为对象 // 反序列化后不能够获取到泛型类型 ResultVO resultVO = JSON.parseObject(voJsonStr, ResultVO.class); System.out.println("resultVO: " + resultVO); Object data = resultVO.getData(); }
Person(id=1, name=null, pwd=null, addr=河南, websiteUrl=http://www.roadjava.com, registerDate=Tue Aug 17 03:38:09 CST 2021, birthDay=2021-08-17T03:38:09) resultVO: ResultVO(success=true, data={"birthDay":"2021-08-17 03:38:09","address":"河南","websiteUrl":"http://www.roadjava.com","id":1,"registerDate":"2021-08-17 03:38:09"})
这样的话就无法体现出==泛型==(反序列化后不能够获取到泛型类型)
/** * 测试反序列化 * json字符串-->bean */ @Test public void testDeSerialize() { String jsonStr = "{\"address\":\"河南\",\"birthDay\":\"2021-08-17 03:38:09\",\"id\":1,\"name\":null,\"pwd\":\"123\",\"registerDate\":\"2021-08-17 03:38:09\",\"websiteUrl\":\"http://www.roadjava.com\"}"; // 反序列化为 person对象 Person person = JSON.parseObject(jsonStr, Person.class); System.out.println(person); // 返回给调用端 ResultVO ResultVO<Person> personResultVO = ResultVO.buildSuccess(person); String voJsonStr = JSON.toJSONString(personResultVO); // 需要反序列化为什么类型,就给TypeReference传入什么类型即可 ResultVO<Person> deSerializedVo = JSON.parseObject(voJsonStr, new TypeReference<ResultVO<Person>>() { }); System.out.println("deSerializedVo: " + deSerializedVo); Person data = deSerializedVo.getData(); System.out.println("data: " + data); }
Person(id=1, name=null, pwd=null, addr=河南, websiteUrl=http://www.roadjava.com, registerDate=Tue Aug 17 03:38:09 CST 2021, birthDay=2021-08-17T03:38:09) deSerializedVo: ResultVO(success=true, data=Person(id=1, name=null, pwd=null, addr=河南, websiteUrl=http://www.roadjava.com, registerDate=Tue Aug 17 03:38:09 CST 2021, birthDay=2021-08-17T03:38:09)) data: Person(id=1, name=null, pwd=null, addr=河南, websiteUrl=http://www.roadjava.com, registerDate=Tue Aug 17 03:38:09 CST 2021, birthDay=2021-08-17T03:38:09)
4.3 通用配置
指定属性名和json字符串key的对应关系
/** * 地址 * name = "address":指定属性名和json字符串key的对应关系 addr---address */ @JSONField(name = "address") private String addr;
忽略指定属性
@JSONField(serialize = false, deserialize = false) private String pwd;
5 jackson
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.1</version>
</dependency>
<!--对LocalDateTime等jdk8时间日期api的转化支持-->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.10.1</version>
</dependency>
拿一个User类为例
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Date;
@Data
public class User {
/**
* 用户id
*/
private Long id;
private String name;
private String pwd;
/**
* 地址
*/
private String addr;
/**
* 网站
*/
private String websiteUrl;
private Date registerDate;
private LocalDateTime birthDay;
}
private static ObjectMapper objectMapper = new ObjectMapper();
static {
}
/**
* 序列化
*/
@Test
public void test1() throws JsonProcessingException {
User user = new User();
user.setId(1L);
// user.setName("王二麻子");
user.setPwd("123");
user.setAddr("河南");
user.setWebsiteUrl("http://www.roadjava.com");
user.setRegisterDate(new Date());
user.setBirthDay(LocalDateTime.now());
String string = objectMapper.writeValueAsString(user);
System.out.println(string);
}
{"id":1,"name":null,"pwd":"123","addr":"河南","websiteUrl":"http://www.roadjava.com","registerDate":1688479431825,"birthDay":{"dayOfWeek":"TUESDAY","dayOfYear":185,"month":"JULY","year":2023,"dayOfMonth":4,"hour":22,"minute":3,"monthValue":7,"nano":860000000,"second":51,"chronology":{"id":"ISO","calendarType":"iso8601"}}}
5.1 序列化
只包含非null属性
全局配置
private static ObjectMapper objectMapper = new ObjectMapper(); static { // 全局配置: 配置序列化时只包含非空属性 objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); } /** * 序列化 */ @Test public void test1() throws JsonProcessingException { User user = new User(); user.setId(1L); // user.setName("王二麻子"); user.setPwd("123"); user.setAddr("河南"); user.setWebsiteUrl("http://www.roadjava.com"); user.setRegisterDate(new Date()); user.setBirthDay(LocalDateTime.now()); String string = objectMapper.writeValueAsString(user); System.out.println(string); }
{"id":1,"pwd":"123","addr":"河南","websiteUrl":"http://www.roadjava.com","registerDate":1688479507149,"birthDay":{"month":"JULY","year":2023,"dayOfMonth":4,"hour":22,"minute":5,"monthValue":7,"nano":179000000,"second":7,"dayOfWeek":"TUESDAY","dayOfYear":185,"chronology":{"id":"ISO","calendarType":"iso8601"}}}
单个的bean配置
import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; import java.time.LocalDateTime; import java.util.Date; /** * @author zhaodaowen * @see <a href="http://www.roadjava.com">王二麻子</a> */ @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class User { /** * 用户id */ private Long id; private String name; private String pwd; /** * 地址 */ private String addr; /** * 网站 */ private String websiteUrl; private Date registerDate; private LocalDateTime birthDay; }
日期时间格式化
全局配置
private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; private static ObjectMapper objectMapper = new ObjectMapper(); static { // 全局配置: 配置序列化时只包含非空属性 objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 手动配置JavaTimeModule并注册 JavaTimeModule javaTimeModule = new JavaTimeModule(); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT))); javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT))); objectMapper.registerModule(javaTimeModule); } /** * 序列化 */ @Test public void test1() throws JsonProcessingException { User user = new User(); user.setId(1L); // user.setName("王二麻子"); user.setPwd("123"); user.setAddr("河南"); user.setWebsiteUrl("http://www.roadjava.com"); user.setRegisterDate(new Date()); user.setBirthDay(LocalDateTime.now()); String string = objectMapper.writeValueAsString(user); System.out.println(string); }
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") // 建议单个bean配置 private Date registerDate; private LocalDateTime birthDay;
{"id":1,"pwd":"123","addr":"河南","websiteUrl":"http://www.roadjava.com","registerDate":"2023-07-04 22:18:03","birthDay":"2023-07-04 22:18:03"}
单个bean的配置
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date registerDate; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime birthDay;
{"id":1,"pwd":"123","addr":"河南","websiteUrl":"http://www.roadjava.com","registerDate":"2023-07-04 22:09:28","birthDay":{"month":"JULY","year":2023,"dayOfMonth":4,"hour":22,"minute":9,"monthValue":7,"nano":124000000,"second":28,"dayOfWeek":"TUESDAY","dayOfYear":185,"chronology":{"id":"ISO","calendarType":"iso8601"}}}
可以发现registerDate格式化生效了,但birthDay还是没有格式化成想要的样子
我们依然要做一个全局配置然后才能生效
private static ObjectMapper objectMapper = new ObjectMapper(); static { // 全局配置: 配置序列化时只包含非空属性 objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 自动通过spi发现jackson的module并注册 objectMapper.findAndRegisterModules(); } /** * 序列化 */ @Test public void test1() throws JsonProcessingException { User user = new User(); user.setId(1L); // user.setName("王二麻子"); user.setPwd("123"); user.setAddr("河南"); user.setWebsiteUrl("http://www.roadjava.com"); user.setRegisterDate(new Date()); user.setBirthDay(LocalDateTime.now()); String string = objectMapper.writeValueAsString(user); System.out.println(string); }
{"id":1,"pwd":"123","addr":"河南","websiteUrl":"http://www.roadjava.com","registerDate":"2023-07-04 22:13:11","birthDay":"2023-07-04 22:13:11"}
美化输出
static { // 全局配置: 配置序列化时只包含非空属性 objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 美化输出 objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true); }
{ "id" : 1, "pwd" : "123", "addr" : "河南", "websiteUrl" : "http://www.roadjava.com", "registerDate" : "2023-07-04 22:20:38", "birthDay" : "2023-07-04 22:20:38" }
5.2 反序列化
忽略不存在的key(fastjson中默认,但jackson中需要手动配置)
/** * 反序列化 */ @Test public void test2() throws Exception { String str = "{\"id\":1,\"pwd\":\"123\",\"address\":\"河南\",\"websiteUrl\":\"http://www.roadjava.com\",\"registerDate\":\"2021-08-17 04:29:42\",\"birthDay\":\"2021-08-17 04:29:42\"}"; User user = objectMapper.readValue(str, User.class); System.out.println(user); }
static { // 两种写法一样 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); }
User(id=1, name=null, pwd=123, addr=null, websiteUrl=http://www.roadjava.com, registerDate=Tue Aug 17 04:29:42 CST 2021, birthDay=2021-08-17T04:29:42)
泛型处理
假设有这样一个DTO类
import lombok.Data; @Data public class ResultDTO<T> { private Boolean success = Boolean.TRUE; private T data; private ResultDTO() { } public static <T> ResultDTO<T> buildSuccess(T t) { ResultDTO<T> resultDTO = new ResultDTO<>(); resultDTO.setData(t); return resultDTO; } }
先序列化,再反序列化这个bean
/** * 泛型的处理 */ @Test public void test3() throws Exception { User user = new User(); user.setName("王二麻子"); user.setWebsiteUrl("http://www.roadjava.com"); ResultDTO<User> userResultDTO = ResultDTO.buildSuccess(user); String dtoSerializationResult = objectMapper.writeValueAsString(userResultDTO); // 反序列化为 ResultDTO<User> ResultDTO<User> dataResult = objectMapper.readValue(dtoSerializationResult, new TypeReference<ResultDTO<User>>() { }); System.out.println("dataResult: " + dataResult); System.out.println("data: " + dataResult.getData()); }
dataResult: ResultDTO(success=true, data=User(id=null, name=王二麻子, pwd=null, addr=null, websiteUrl=http://www.roadjava.com, registerDate=null, birthDay=null)) data: User(id=null, name=王二麻子, pwd=null, addr=null, websiteUrl=http://www.roadjava.com, registerDate=null, birthDay=null)
5.3 通用配置
序列化:驼峰转下划线 / 反序列化:下划线转驼峰
static { // 驼峰转下划线 userName -- user_name objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); }
/** * 反序列化 */ @Test public void test2() throws Exception { String str = "{\"id\":1,\"pwd\":\"123\",\"address\":\"河南\",\"website_url\":\"http://www.roadjava.com\",\"register_date\":\"2021-08-17 04:29:42\",\"birth_day\":\"2021-08-17 04:29:42\"}"; User user = objectMapper.readValue(str, User.class); System.out.println(user); }
User(id=1, name=null, pwd=123, addr=null, websiteUrl=http://www.roadjava.com, registerDate=Tue Aug 17 04:29:42 CST 2021, birthDay=2021-08-17T04:29:42)
指定属性名和json字符串key的对应关系
/** * 地址 */ @JsonProperty("address") private String addr;
忽略指定属性(不参与序列化)
@JsonIgnore private String pwd;
5.4 其他应用
对象更新
/** * objectMapper.updateValue(对象更新, 对象的重写) * 如果后者的属性有值,则用后者,否则前者的值不变 */ @Test public void test4() throws Exception { User originalUser = new User(); originalUser.setId(1L); originalUser.setName("王二麻子"); originalUser.setWebsiteUrl("http://www.baidu.com"); User newUser = new User(); newUser.setId(2L); newUser.setWebsiteUrl("http://www.roadjava.com"); // 让我们省去了很多判断 User updatedUser = objectMapper.updateValue(originalUser, newUser); // id:2 name:乐之者; java websiteUrl:http://www.roadjava.com System.out.println(updatedUser); }
User(id=2, name=王二麻子, pwd=null, addr=null, websiteUrl=http://www.roadjava.com, registerDate=null, birthDay=null)