Java与JSON

关于 JSON,最重要的是要理解它是一种数据格式,不是一种编程语言**。虽然具有相同的语法形式,但 JSON 并不从属于 JavaScript。而且,并不是只有 JavaScript 才使用 JSON,毕竟 JSON 只是一种数据格式。很多编程语言都有针对 JSON 的解析器和序列化器。

JSON 是一个轻量级的数据格式,可以简化表示复杂数据结构的工作量。

对象(object):一个对象包含一系列非排序的键/值对,一个对象以 {开始,并以 }结束。每个键/值对之间使用 :分区。多个键值对之间通过 , 分割。需要注意的是JSON 的键是一个 String 类型的字符串

img

值(value):可以是双引号括起来的字符串(string)数值(number)truefalsenull对象(object)或者数组(array)。这些结构可以嵌套使用。

JSON 值的格式

ECMAScript 5 定义了一个原生的 JSON 对象; JSON对象包含两个方法: 用于解析 JavaScript Object Notation (JSON) 的 parse() 方法,以及将 对象/值 转换为 JSON字符串的 stringify() 方法。

更详细的JSON基础在

关于JSON对象——JsonElement

JSON字符串和JSON对象的区别

JSON 字符串是由双引号包围的文本,其格式严格遵循 JSON 规范。它其实就是 JSON 对象的字符串表示,在网络传输或者数据存储时经常会用到。

1
2
// 这是一个JSON字符串
"{\"name\":\"Alice\",\"age\":30,\"hobbies\":[\"Reading\",\"Swimming\"]}"

特点

  • 必须使用双引号来包裹键和字符串值。
  • 不能包含函数、undefined 或者 NaN 等特殊值。
  • 可以被序列化为二进制格式,例如 MessagePack。

JSON 对象是内存中的数据结构,在不同的编程语言里有不同的表现形式,像 JavaScript 对象、Java 中的 Map、Python 中的字典等。

1
2
3
4
5
6
// JavaScript中的JSON对象
const person = {
"name": "Alice",
"age": 30,
"hobbies": ["Reading", "Swimming"]
};

特点

  • 可以直接对属性进行访问,例如person.name
  • 能够包含特殊值,比如null、函数、undefined
  • 生命周期和内存管理由宿主语言负责。

JSON 字符串是数据的文本表示形式,主要用于数据的传输和存储。

JSON 对象是内存中的数据结构,用于数据的操作和处理。

两者通过解析(parse)和序列化(stringify)进行转换。

顺便说一下,JSON 数组其实就是包含了多个 JSON 对象的一个集合,数组是以 数组括号 [] 括起来的

1
2
3
4
5
6
7
8
9
10
11
12
[{
"area": "浙江杭州


"name": "李师傅",
"age": 25
},
{
"area": "北京海淀
"name": "小李",
"age": 26
}]

JSON 数组并一定是要相同的 JSON 对象的集合,也可以是不同的对象

JSON 在 Java 开发中的作用

JSON 以什么作用出现在开发中

  1. 数据交换的核心格式

    在现代 Web 应用里,JSON 是前后端进行数据交互的主要格式。前端发送请求时,会把数据封装成 JSON 字符串;后端处理完请求后,也会把响应数据序列化为 JSON 字符串返回给前端。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 后端返回JSON响应示例(Spring MVC)
    @RestController
    @RequestMapping("/api")
    public class UserController {
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable Long id) {
    // 从数据库获取用户
    return userService.getUserById(id); // 自动序列化为JSON
    }
    }
  2. 服务间通信

    在微服务架构中,各个服务之间通过 RESTful API 进行通信,JSON 是最常用的数据格式。像 Spring Cloud 这样的框架,默认就支持 JSON 格式的数据传输。

    1
    2
    3
    4
    // 使用RestTemplate消费REST API
    RestTemplate restTemplate = new RestTemplate();
    String url = "http://user-service/api/users/{id}";
    User user = restTemplate.getForObject(url, User.class, 1L);
  3. 配置文件的标准选择

    尽管,Spring的配置文件标准是xml,yaml和properties(其实也可以支持),但是还是有很多 Java 框架和工具都采用 JSON 作为配置文件的格式,比如:

    • Elasticsearch:使用 JSON 来定义索引结构和查询条件。

    1
    2
    3
    4
    5
    6
    7
    8
    // Elasticsearch查询示例
    {
    "query": {
    "match": {
    "title": "Java Programming"
    }
    }
    }

  4. NoSQL 的存储格式

    像 MongoDB、Elasticsearch 这类 NoSQL 数据库,直接以 JSON(BSON)格式存储数据,这与 Java 对象的映射非常自然。Redis 等缓存系统也常以 JSON 格式存储复杂对象,这样可以避免频繁查询数据库

  5. 日志与监控

    JSON 格式在日志记录和监控系统中应用广泛,因为它便于机器解析和分析。

    1
    2
    3
    4
    5
    6
    7
    8
    // 使用Logback生成JSON日志
    {
    "timestamp": "2023-06-15T10:30:00",
    "level": "INFO",
    "message": "User logged in",
    "user": "Alice",
    "duration": 123
    }

Spring 开发中 JSON 的地位

在现代 Spring 开发中,JSON 已成为数据交互的核心标准,深度融入 Spring 生态系统的各个层面。

在 Spring MVC 的自动序列化中,Spring 通过 @RestController@ResponseBody 自动将 Java 对象序列化为 JSON:

Spring 框架的默认 JSON 处理是 Jackson,当然可以改成 GSON 等

1
2
3
4
5
6
7
8
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id); // 自动序列化为 JSON
}
}

而请求参数绑定中,直接将 JSON 请求体映射到 Java 对象,结合 @Valid 注解和 Bean Validation 进行 JSON 数据校验。

1
2
3
4
@PostMapping
public User createUser(@RequestBody User user) {
return userService.save(user);
}

在响应式架构中,JSON 作为数据流的载体:

1
2
3
4
@GetMapping(produces = MediaType.APPLICATION_NDJSON_VALUE)
public Flux<User> streamUsers() {
return userService.findAll(); // 返回 JSON 流
}

现代前端框架(React、Vue.js)与 Spring 后端通过 JSON 进行数据交互:

1
2
3
4
// React 组件调用 Spring API
fetch('/api/users')
.then(res => res.json())
.then(data => this.setState({ users: data }));

Spring 集成 GraphQL 后,JSON 也会成为查询和响应的标准格式

各种外部配置也是支持 JSON 的

1
2
3
4
5
6
7
8
9
// config.json
{
"server": {
"port": 8080
},
"database": {
"url": "jdbc:mysql://localhost:3306/test"
}
}

而且,Spring Security 集成 JWT,使用 JSON 格式传递身份信息:

1
2
3
4
5
6
{
"sub": "user123",
"iat": 1623456789,
"exp": 1623460389,
"roles": ["USER", "ADMIN"]
}

还有跨域请求,消息队列,MonogoDB等,也都是用到了 JSON,从其中的共同特点我们可以理解到

JSON 主要用于数据传输

这一点不要忘记

JSON在java中的使用和一些操作

Java中并没有内置JSON的解析,因此使用JSON需要借助第三方类库。

使用JSON官方提供的JSON

官方提供的JSON具有通用性,但是性能不好

1
2
3
4
5
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180130</version>
</dependency>

生成 JSON 对象

JSONObject是一个在Java中表示JSON对象的类,通常是由像org.json, Gson, Jackson或其他处理JSON的库提供。以org.json库为例,JSONObject是一个封装了JSON数据的键值映射的类。这个类允许你创建新的JSON对象、从字符串解析JSON数据以及像操作普通Java对象一样访问或修改JSON对象中的数据。

  1. 创建JSON对象: 使用 new 来创建一个空的JSONObject,或者通过传递一个JSON字符串来构造一个已填充的JSONObject
  2. 添加键值对: 使用put方法可以添加键值对,如果键已经存在,将替换键对应的值。
  3. 获取数据: 提供了getopt系列方法来获取键对应的值。get方法在键不存在时会抛出异常,而opt方法在找不到键时会返回一个默认值(例如null)或指定的默认值。
  4. 转换为字符串: toString()方法将JSONObject转换成JSON格式的字符串。
  5. 检查键: has方法可以检查JSONObject是否包含特定的键。

使用 JSONObject 添加对象是一种常见的创建 JSON 数据的方式。JSONObject 类提供了丰富的方法来操作 JSON 对象。下面是一个示例代码,展示了如何创建一个 JSON 对象并向其中添加不同类型的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void Test3(){
//创建JSON对象
JSONObject jsonObject = new JSONObject();
Object obj = null;
//像JSON对象中添加数据
jsonObject.put("name", "ErgouTree");
jsonObject.put("age", 20);
jsonObject.put("birth", "1998-01-01");
jsonObject.put("haveCar", obj);
jsonObject.put("hasPlayOsu", true);
jsonObject.put("likes", new String[]{"玩galgareeme", "打4k"});
//将JSON对象以字符串的形式打印
System.out.println(jsonObject.toString());
}

在字符串输出的时候会发现,输出的字符串与数据存储的顺序可能是不一致的。原因是调用jsonObject.put()方法的时候,其底层是一个 HashMap,数据被存到了HashMap 中。 HashMap根据键的哈希码来决定键的位置,所以可能会出现上面的问题。 既然 jsonObject.put() 方法的底层是对 HashMap 的操作,那么能否将一个 HashMap 转换成 JSON 数据呢?答案是可以的。原因是 JSONObject 类提供了一个构造函数,这个构造函数中接收的正是一个 Map。以下是一个将 HashMap 转换为 JSONObject 的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;

public class MapToJsonTest {
public static void main(String[] args) {
Map<String, Object> map = new HashMap<>();
map.put("name", "ErgouTree");
map.put("age", 20);
map.put("birth", "1998-01-01");
map.put("hasPlayOsu", true);
map.put("likes", new String[]{"玩galgareeme", "打4k"});

JSONObject jsonObject = new JSONObject(map);
System.out.println(jsonObject.toString());
}
}
img

生成JSONArray数组类

JSONArray 是Java中用于表示JSON数组的类,类似于JSONObject,它是用来处理JSON数据中的数组类型。JSON数组是一个有序的元素集合,每个元素可以是任意类型,如字符串、数字、JSON对象、其他数组等。这个类提供了一系列的方法来创建、解析和操作JSON数组。

  • 添加元素: 使用 put 方法来向数组中添加元素。
  • 提取元素: 可以通过索引来取数组中的元素,使用诸如 getString(index), getJSONObject(index) 等方法。
  • 修改元素: 可以通过传递索引和新值到 put 方法来修改数组中的元素。
  • 数组长度: 使用 length 方法可以获取数组的长度。
  • 遍历数组: 对JSONArray进行遍历,通常使用for循环结合 get 方法访问每个元素。
  • 转换为字符串: toString 方法可以将整个数组转换成JSON格式的字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.json.JSONArray;
import org.json.JSONObject;
public class JSONArrayExample {
public static void main(String[] args) {
// 创建(构造)一个JSONArray实例
JSONArray array = new JSONArray();
// 向数组中添加元素
array.put("apple");
array.put(100);
array.put(new JSONObject().put("key", "value"));
// 获取数组长度
int length = array.length();
// 遍历JSONArray并输出每个元素
for (int i = 0; i < length; i++) {
System.out.println(array.get(i));
}
// 输出整个JSONArray
System.out.println(array.toString());
}
}

JavaBean 转 json

1
2
3
4
5
6
public class User {
private String name;
private int age;
private String[] likes;
// 省略 get 与 set 方法
}

JavaBean To JSON

1
2
3
4
5
6
7
8
9
10
public static void beanToJsonTest() {
User user = new User();

user.setName("ErgouTree");
user.setAge(20);
user.setLikes(new String[]{"玩galgareeme", "打4k"});

JSONObject jsonObject = new JSONObject(user);
System.out.println(jsonObject.toString());
}

解析JSON

除了生成 JSON 数据,我们还经常需要解析 JSON 数据。JSONObject 类提供了一系列方法来获取 JSON 对象中的数据。以下是一个解析 JSON 字符串的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import org.json.JSONArray;
import org.json.JSONObject;

public class JsonParsingTest {
public static void main(String[] args) {
String jsonStr = "{\"name\":\"ErgouTree\",\"age\":20,\"birth\":\"1998-01-01\",\"hasPlayOsu\":true,\"likes\":[\"玩galgareeme\",\"打4k\"]}";

// 解析 JSON 字符串为 JSONObject
JSONObject jsonObject = new JSONObject(jsonStr);

// 获取 JSON 对象中的数据
String name = jsonObject.getString("name");
int age = jsonObject.getInt("age");
String birth = jsonObject.getString("birth");
boolean hasPlayOsu = jsonObject.getBoolean("hasPlayOsu");
JSONArray likesArray = jsonObject.getJSONArray("likes");

// 打印解析结果
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Birth: " + birth);
System.out.println("Has Play Osu: " + hasPlayOsu);

// 遍历 JSON 数组
System.out.print("Likes: ");
for (int i = 0; i < likesArray.length(); i++) {
System.out.print(likesArray.getString(i) + " ");
}
}
}

我们首先定义了一个 JSON 字符串,然后使用 JSONObject 的构造函数将其解析为 JSONObject 对象。接着,我们使用 getStringgetIntgetBoolean 等方法获取 JSON 对象中的数据。对于 JSON 数组,我们使用 getJSONArray 方法获取,并通过循环遍历数组中的元素。

虽然官方的 JSON 库具有通用性,但由于其底层使用 HashMap 存储数据,可能会导致输出顺序与存储顺序不一致的问题。同时,该库的性能相对较差,在处理大量数据时可能会有性能瓶颈。在实际开发中,可以根据具体需求选择合适的 JSON 处理库,如 Jackson、Gson 等,它们在性能和功能上都有更好的表现。

使用 GSON

GSON 是由 Google 开发的库,能简便地实现 Java 对象和 JSON 数据间的相互转换。

我更推荐大家在做开发的时候使用 GSON,感觉简单好学还不难写

导入依赖

1
2
3
4
5
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>

Java 对象转 JSON (序列化)

借助Gson.toJson()方法,把 Java 对象序列化为 JSON 字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import com.google.gson.Gson;

public class User {
private String name;
private int age;
private String[] hobbies;

// 构造方法、Getter和Setter
public User(String name, int age, String[] hobbies) {
this.name = name;
this.age = age;
this.hobbies = hobbies;
}
}

// 使用示例
User user = new User("Alice", 30, new String[]{"Reading", "Swimming"});
Gson gson = new Gson();
String json = gson.toJson(user);
System.out.println(json);
// 输出结果: {"name":"Alice","age":30,"hobbies":["Reading","Swimming"]}

JSON 转 Java 对象(反序列化)

利用Gson.fromJson()方法,可以将 JSON 字符串反序列化为 Java 对象。

1
2
3
String json = "{\"name\":\"Bob\",\"age\":25,\"hobbies\":[\"Running\",\"Cooking\"]}";
User user = gson.fromJson(json, User.class);
System.out.println(user.getName()); // 输出: Bob

使用 FastJSON

FastJSON是阿里巴巴的产品,效率最高

序列化和反序列化等使用操作不介绍了,因为我没用过

Spring默认使用的JSON工具–Jackson

二遍:讲的东西不够多,不够细,应该单开一篇

Jackson 是处理 JSON 数据的顶尖库,它以高性能、丰富功能和广泛的社区支持闻名。

Spring 一系列框架的JSON处理就是默认使用的 JackSon,JackSon是Spring的御用工具,和Spring无缝集成,提供灵活的定制化开发的注解。如果使用Spring框架进行开发,建议使用JackSon。

Jackson包含两个不同的解析器:

  • Jackson ObjectMapper,将JSON转化为Java对象,或者转换为Jackson特定的树结构
  • Jackson JsonParser,JSON流解析器,每次只解析一个JSON标记

Jackson还包含两个不同的生成器:

  • Jackson ObjectMapper,可以从Java对象生成JSON,或者是从Jackson的树结构生成JSON
  • Jackson Generator,每次生成一个JSON标记

若使用 Maven 项目,在 pom.xml 文件中添加如下依赖

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.2</version> <!-- 可按需调整版本 -->
</dependency>

Java 对象与 JSON 的转换:核心类 ObjectMapper 可实现 Java 对象与 JSON 的相互转换

ObjectMapper 解析JSON的原理:

默认情况下,Jackson 通过 Java bean 的 get,set 方法,通过去掉 get,set 再把首字母小写得到的名字去和 JSON 的属性进行匹配。

例如对于 getBrand()setBrand()经过处理得到 brand,就会匹配到 JSON 的brand属性,从而把JSON brand属性的值赋给beanbrand字段。通过一系列这样的处理,就将 JSON 转换成了 Java bean。如果需要以不同的方式来匹配,那么就得使用定制的serializerdeserializer,或者使用Jackson提供的众多的注解。

而配置ObjectMapper的工作方式也就是反序列化并不是完全直接反着来的:

有时JSON会拥有比Java对象更多的属性,这种情况下Jackson默认会抛出异常,大概意思是说JSON某个属性未知因为在Java bean中未找到该属性。这种情况下,Jackson允许通过配置来忽略未知的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import com.fasterxml.jackson.databind.ObjectMapper;

public class User {
private String name;
private int age;
// 构造方法、Getter和Setter
}

// 序列化
ObjectMapper mapper = new ObjectMapper();
User user = new User("Alice", 30);
String json = mapper.writeValueAsString(user);
// 输出: {"name":"Alice","age":30}

// 反序列化
String json = "{\"name\":\"Bob\",\"age\":25}";
User user = mapper.readValue(json, User.class);

Jackson 提供了丰富的注解,用于精确控制序列化和反序列化过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class User {
@JsonProperty("fullName") // 指定JSON字段名
private String name;

@JsonIgnore // 忽略该字段
private String password;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private Date birthDate;

@JsonCreator // 自定义构造方法
public User(@JsonProperty("name") String name) {
this.name = name;
}
}

对于大型 JSON 文件,使用 JsonParserJsonGenerator 进行高效处理:

1
2
3
4
5
6
7
8
9
10
11
12
// 写入JSON
JsonGenerator gen = mapper.getFactory().createGenerator(System.out);
gen.writeStartObject();
gen.writeStringField("name", "Alice");
gen.writeEndObject();
gen.close();

// 读取JSON
JsonParser parser = mapper.getFactory().createParser(json);
while (parser.nextToken() != null) {
// 处理令牌
}