Skip to content

Day 32: std.json模块:JSON解析

欢迎来到第三十二天!JSON (JavaScript Object Notation) 是当今Web API和配置文件中最流行的数据交换格式。它轻量、易于人类阅读和编写,也易于机器解析和生成。Zig标准库通过 std.json 模块提供了强大的JSON处理能力。今天,我们将学习如何将JSON字符串解析成Zig中的数据结构,以及如何将Zig的数据结构序列化(或“字符串化”)为JSON字符串。

std.json 的核心解析函数是 parseFromSlice。它接收一个包含JSON文本的字节切片和一个分配器,然后尝试将其解析成一个 std.json.Value 树。

const std = @import("std");
const json_text =
\{
\ "name": "Alice",
\ "age": 30,
\ "is_active": true,
\ "courses": ["Zig", "Rust"]
\}
;
pub fn main() !void {
const allocator = std.heap.page_allocator;
var tree = try std.json.parseFromSlice(u8, allocator, json_text, .{});
defer tree.deinit();
// ... 操作 tree ...
}

parseFromSlice 返回一个 ParsedJson 对象,它拥有已解析数据的内存。defer tree.deinit() 用于在最后释放这块内存。

解析的结果是一个 std.json.Value 树。Value 是一个标签联合体(tagged union),它可以代表任何一种JSON数据类型:

  • object: 一个 std.json.ObjectMap (HashMap)
  • array: 一个 std.json.Array (ArrayList)
  • string: 一个 []const u8
  • number: 一个 f64
  • bool: 一个 bool
  • null: 一个 void

你可以使用 switch 来处理一个 Value。

const root = tree.root;
switch (root) {
.object => |obj| { // obj 是一个 ObjectMap
const name_val = obj.get("name").?;
if (name_val == .string) |name| {
std.debug.print("Name: {s}\n", .{name});
}
},
else => { // 处理其他情况 },
}

要将Zig的数据结构转换成JSON字符串,你可以使用 std.json.stringify。

const data = .{
.name = "Bob",
.age = 42,
};
// 将一个匿名结构体序列化
try std.json.stringify(data, .{}, std.io.getStdOut().writer());
// 输出: {"name":"Bob","age":42}

stringify 函数非常强大,它可以序列化结构体、数组、哈希表等多种Zig类型。它接收三个参数:

  1. 要序列化的数据。
  2. 一个选项结构体(例如,用于美化输出的 .whitespace)。
  3. 一个 Writer,用于写入生成的JSON字符串。

5. 示例:解析JSON对象并填充结构体

Section titled “5. 示例:解析JSON对象并填充结构体”

虽然可以手动遍历 Value 树,但 std.json 提供了一个更自动化的方式,可以直接将JSON对象“反序列化”到一个Zig结构体中。

const std = @import("std");
const User = struct {
name: []const u8,
age: u32,
};
const json_text = "{\"name\":\"Charlie\",\"age\":28}";
pub fn main() !void {
const allocator = std.heap.page_allocator;
var user: User = undefined;
// 解析并自动填充User结构体
const parsed = try std.json.parseFromSlice(User, allocator, json_text, .{});
defer parsed.deinit();
user = parsed.root;
std.debug.print("User: {s}, Age: {d}\n", .{ user.name, user.age });
}

注意,parseFromSlice 的第一个参数我们传入了 User 类型而不是 u8。这告诉解析器,我们期望JSON的顶层结构能匹配 User 结构体的字段,并自动进行填充。

6. 实践练习:解析一个简单的API响应

Section titled “6. 实践练习:解析一个简单的API响应”

假设你从一个API收到了以下JSON响应:

{
"product_id": "xyz-123",
"quantity": 100,
"in_stock": true
}
  1. 创建一个 InventoryItem 结构体,使其字段与JSON的键匹配。
  2. 编写一个程序,将上面的JSON字符串解析到你的 InventoryItem 结构体实例中。
  3. 打印出结构体的字段以验证解析是否成功。

问:解析JSON时遇到UTF-8错误怎么办?

答:std.json 严格遵守JSON标准,要求字符串必须是有效的UTF-8编码。如果解析器遇到无效的UTF-8序列,parseFromSlice 会返回 error.InvalidUtf8。你需要在调用处 catch 这个错误并进行处理,例如记录错误日志或拒绝该非法输入。

问:如何处理非常大的JSON文件或流式解析?

答:parseFromSlice 将整个JSON加载到内存中,这对于非常大的文件可能会有问题。对于这种情况,std.json 提供了一个基于事件的流式解析器 std.json.Scanner。它会逐个地读取JSON的令牌(token,如{, `