How to parse arbitrary JSON data with std.json?

Hi,

How can I make this code work? I want to be able to basically create a dynamic object on the fly from my JSON data, access fields from it, manipulate it, etc. How can I do this?

const std = @import("std");

const json_data =
    \\{
    \\  "name": "Quin",
    \\  "age": 19
    \\}
;

pub fn main() void {
    const value = try std.json.Value.jsonParse(std.heap.page_allocator, json_data, .{});
    std.debug.print("{}\n", .{value});
}
quin@minix:~$ zig build-exe test.zig
.zvm/0.15.2/lib/std/json/static.zig:194:41: error: type '[33:0]u8' has no members
    return ParseFromValueError || Source.NextError || Source.PeekError || Source.AllocError;
                                  ~~~~~~^~~~~~~~~~
.zvm/0.15.2/lib/std/json/static.zig:194:41: note: array values have 'len' member
.zvm/0.15.2/lib/std/json/dynamic.zig:77:94: note: called at comptime here
    pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) ParseError(@TypeOf(source.*))!@This() {
                                                                                   ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~
test.zig:11:47: note: generic function instantiated here
    const value = try std.json.Value.jsonParse(std.heap.page_allocator, json_data, .{});
                      ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
referenced by:
    callMain [inlined]: .zvm/0.15.2/lib/std/start.zig:618:22
    callMainWithArgs [inlined]: .zvm/0.15.2/lib/std/start.zig:587:20
    posixCallMainAndExit: .zvm/0.15.2/lib/std/start.zig:542:36
    2 reference(s) hidden; use '-freference-trace=5' to see all references

Thanks in advance for any help!

On the latest master branch, you can do:

test "parse dynamic JSON data" {
    const gpa = std.testing.allocator;

    const data =
        \\{
        \\   "foo": "bar",
        \\   "empty": false,
        \\   "numbers": [3, 4, 5, 6],
        \\   "arr": {"hello":"world"}
        \\}
    ;

    const parsed = try std.json.parseFromSlice(std.json.Value, gpa, data, .{});
    defer parsed.deinit();

    const object = parsed.value.object;

    try std.testing.expectEqualStrings(object.get("foo").?.string, "bar");
    try std.testing.expect(object.get("empty").?.bool == false);
    try std.testing.expectEqualStrings(object.get("arr").?.object.get("hello").?.string, "world");
    try std.testing.expect(object.get("numbers").?.array.items.len == 4);
}

const std = @import("std");

3 Likes

To expand on what @saurabh wrote: You used the wrong function for parsing.

The jsonParse functions expect a token source, not a string.

So you can either supply them a token source, or you use a convenience function like std.json.parseFromSlice which does this for you.

If you want to go and use a *std.Io.Reader instead of the fully read string as source, you can init a std.json.Reader and supply it as an argument to e.g. std.json.parseFromTokenSource.

1 Like

For example:

test "parse dynamic JSON data (2)" {
    const gpa = std.testing.allocator;

    const data =
        \\{
        \\   "foo": "bar",
        \\   "empty": false,
        \\   "numbers": [3, 4, 5, 6],
        \\   "arr": {"hello":"world"}
        \\}
    ;

    var reader: std.Io.Reader = .fixed(data);
    var json_reader: std.json.Reader = .init(gpa, &reader);
    defer json_reader.deinit();

    const parsed = try std.json.parseFromTokenSource(std.json.Value, gpa, &json_reader, .{});
    defer parsed.deinit();

    const object = parsed.value.object;

    try std.testing.expectEqualStrings(object.get("foo").?.string, "bar");
    try std.testing.expect(object.get("empty").?.bool == false);
    try std.testing.expectEqualStrings(object.get("arr").?.object.get("hello").?.string, "world");
    try std.testing.expect(object.get("numbers").?.array.items.len == 4);
}

const std = @import("std");

4 Likes

Thanks a ton all! This makes perfect sense now that I’ve seen it written out, but I was really really struggling to fill in the comptime T: type parameter in that function. It never occurred to me to just pass std.json.Value, I thought it must be a struct I define.

Thanks again, and for whatever it’s worth, this works on 0.15.2 as well, which I’m sticking to until 0.16 hits stable :slight_smile: Even if there are no release notes or anything on what 0.15.2 did, but that’s probably an issue for another topic :wink: