Parsing JSON in Zig isn’t Hard
> zig version
0.13.0
While working on a project of mine, I needed to add functionality to parse JSON. As it turns out, it’s not very hard once you figure out how std.json
wants to do it.
Q: What do you mean, “it’s not very hard”? I know std.json
exists, but it’s very tedious, isn’t it? You get an iterator of “tokens” that you have to match to your preferred type, yourself, right?
A: Yes, but actually no.
You could handle the token stream yourself if you really wanted to, but if your JSON document is relatively small, you could pass it as a []u8
to std.json.parseFromSlice
, or ...parseFromSliceLeaky
, depending or your memory situation, like this:
const std = @import("std");
test "parseFromSliceLeaky u16" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const result = try std.json.parseFromSliceLeaky(u16, allocator, "1234", .{});
try std.testing.expect(result == 1234);
}
In fact, it even handles structs:
const std = @import("std");
const Contact = struct {
id: u64,
first_name: []u8,
last_name: ?[]u8 = null,
phone_numbers: []struct {
type: enum { home, mobile, work },
number: []u8,
},
custom_fields: std.json.ArrayHashMap([]u8),
};
test "parseFromSliceLeaky Contact" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const result = try std.json.parseFromSliceLeaky(Contact, allocator,
\\{
\\ "id": 1234,
\\ "first_name": "John",
\\ "phone_numbers": [
\\ { "type": "home", "number": "+18885550123" },
\\ { "type": "mobile", "number": "+18885550189" }
\\ ],
\\ "custom_fields": {
\\ "Hat size": "7\u00bc"
\\ }
\\}
, .{});
try std.testing.expect(result.id == 1234);
try std.testing.expect(std.mem.eql(u8, result.first_name, "John"));
try std.testing.expect(result.last_name == null);
try std.testing.expect(result.phone_numbers[0].type == .home);
try std.testing.expect(std.mem.eql(u8, result.phone_numbers[0].number, "+18885550123"));
try std.testing.expect(result.phone_numbers[1].type == .mobile);
try std.testing.expect(std.mem.eql(u8, result.phone_numbers[1].number, "+18885550189"));
try std.testing.expect(std.mem.eql(u8, result.custom_fields.map.get("Hat size") orelse "", "7¼"));
}
I tried to include every type of data that you’d want to parse from a JSON document.
TIP
There are some gotchas here: Nothing has a default value out-of-the-box. Even if you define something as being optional, if the JSON document doesn’t specify a value for it, it won’t default to null
; it’ll cause std.json.<whatever>
to return a MissingField
error.