As I was building my Waybar Wise FX Rate module, I found out the Zig JSON docs could have been better for newcomers to the language, such as myself. I couldn’t figure out how to parse an array of objects. But it was great when I asked the question in Zig’s Discord, and people helped me!
Table of contents
Open Table of contents
Intro
JSON in Zig works differently from other popular programming languages. You have the option to decode the JSON into
primitives with json.Value
, or decode directly to a Struct
.
When working with primitives, the decoded values are inside the type json.Value
. This type has different fields to
access each key value depending on the underlying object (See docs). For example, having this parsed JSON:
{ "id": 125, "name": "real name" }
You can manipulate it in the following way:
// Access the root object.
parsed.value.object;
// Access the ID as `i64`. The field `object` is a StringArrayHashMap(Value); the `get` function returns `?Value`.
parsed.value.object.get("id").?.integer;
// Access the name as `[]const u8`.
parsed.value.object.get("name").?.string;
Using decoded arrays works similarly. To iterate over an array, you do the following:
for (parsed.value.array.items) |item| { ... };
Now let’s see proper examples using different techniques + how to decode into structs.
Parsing JSON into a Zig primitive
The following code parses a JSON object and retrieves the "id"
value.
test "parse object.id into an ?i64" {
const allocator = std.testing.allocator;
const json_str =
\\ {
\\ "id": 125,
\\ "name": "real name"
\\ }
;
const parsed = try json.parseFromSlice(json.Value, allocator, json_str, .{});
defer parsed.deinit();
const id: i64 = parsed.value.object.get("id").?.integer;
try testing.expectEqual(125, id);
}
The type json.Value
holds the parsed string and can represent any JSON value:
null
bool
integer (i64)
float (f64)
string
array
object
To get the value, you must to access one of the exposed fields from the type. For example, to get the parsed "id"
integer, I had to call .integer
on the value.
Parsing a JSON Array of objects with ID to an integer ArrayList
Going from this [ { "id": 125 }, { "id": 126 } ]
to [125, 126]
.
test "parse array ids into an ArrayList" {
const allocator = std.testing.allocator;
const json_str =
\\ [
\\ { "id": 125 },
\\ { "id": 126 }
\\ ]
;
const parsed = try json.parseFromSlice(json.Value, allocator, json_str, .{});
defer parsed.deinit();
var ids = std.ArrayList(i64).init(allocator);
defer ids.deinit();
for (parsed.value.array.items) |json_object| {
if (json_object.object.get("id")) |id_value| {
try ids.append(id_value.integer);
}
}
try testing.expectEqual(125, ids.items[0]);
try testing.expectEqual(126, ids.items[1]);
}
Parsing JSON Objects into Zig Structs
test "parse array of objects with ids into an Array of Structs" {
const IdStruct = struct {
id: i64,
name: []const u8,
};
const allocator = std.testing.allocator;
const json_str =
\\ [
\\ { "id": 125, "name": "Romario" },
\\ { "id": 126, "name": "Goku" }
\\ ]
;
const parsed = try json.parseFromSlice([]IdStruct, allocator, json_str, .{});
defer parsed.deinit();
try testing.expectEqual(125, parsed.value[0].id);
try testing.expectEqualStrings("Romario", parsed.value[0].name);
try testing.expectEqual(126, parsed.value[1].id);
try testing.expectEqualStrings("Goku", parsed.value[1].name);
}
Caveats
The documentation states that when using an std.heap.ArennaAllocator
, you should use json.parseFromSliceLeaky
instead. From this excellent post:
In addition to
parseFromSlice
there’s also aparseFromSliceLeaky.
The “leaky” version returns T directly. This version is written assuming that the provided allocator is able to free all allocations, without having to track every individual allocations. It essentially assumes that the provided allocator is something like anArenaAllocator
or aFixedBufferAllocator
. In practical terms,parseFromSlice
internally creates anArenaAllocator
and returns that allocator with the parsed value whereasparseFromSliceLeaky
takes anArenaAllocator
(or something like it) and returns the parsed value. In both cases, you end up with a value of type T tied to an allocator.
Useful links
- https://zig.guide/standard-library/json
- https://ziglang.org/documentation/master/std/#std.json
- https://www.openmymind.net/Zigs-json-parseFromSlice/
- https://ziglang.org/documentation/master/#Arrays
A post like this would’ve helped me a ton when starting my journey with Zig. I hope it is helpful to you, too :).
Let me know if you want to parse a specific JSON, and we can figure it out together.