This allows runtime type inference of random key-value pairs, with an intuitive syntax. Given any JSON data, return the entire string or the value of any key with 2 statements:
val = json.get("hello");
try val.unpack();
The first statement accepts a key as query and obtains the value to a specified depth by chaining get functions as needed. The second processes and outputs the value. If the value is an array you can use the index function built into the structure:
val = json.get("hello").get("hash").index(0);
try val.unpack();
So from here I need to see about what I can do to optimize this. And also what Iām going to use it for. Iām leaning towards REST framework or some kind of scripting or query language. Also my test data is relatively small and only goes 2 or 3 levels deep, so Iām not sure what other limitations there are here.
This was quite a lot of work since I was also learning Zig fundamentals along the way. So Iām relieved that I was able to finally come up with a simple implementation. Also a big thank you to everybody who helped me along the way. My motivation for this was trying to implement some basic ideas of JavaScript into Zig. So the intent is to use this with some kind of web server or proxy server to process request bodies formatted as JavaScript objects. Here is the full code:
const std = @import("std");
const my_json =
\\{
\\ "hello": { "name":"hello", "id": "okay", "hash": [null, 2.33e+77, false, -5] },
\\ "okay": [0, 1, "maybe", { "name":"hello", "id": "okay", "hash": [true, 2.55e+99, null, -7] }]
\\}
;
const T = struct {
x: ?std.json.Value,
pub fn init(self: std.json.Value) T {
return T {
.x = self
};
}
pub fn get(self: T, query: []const u8) T {
if (self.x.?.object.get(query) == null) {
std.debug.print("ERROR::{s}::", .{ "invalid" });
return T.init(self.x.?);
}
return T.init(self.x.?.object.get(query).?);
}
pub fn unpack(self: T) !void {
var out = std.ArrayList(u8).init(std.testing.allocator);
defer out.deinit();
switch (self.x.?) {
.string => |i| {
const P = struct { value: []const u8 };
try std.json.stringify(P{ .value = i }, .{ }, out.writer());
},
.integer => |i| {
const P = struct { value: i64 };
try std.json.stringify(P{ .value = i }, .{ }, out.writer());
},
.bool => |i| {
const P = struct { value: bool };
try std.json.stringify(P{ .value = i }, .{ }, out.writer());
},
.float => |i| {
const P = struct { value: f64 };
try std.json.stringify(P{ .value = i }, .{ }, out.writer());
},
.null => {
const P = struct { value: ?usize = null };
try std.json.stringify(P{ }, .{ }, out.writer());
},
.array => |i| {
const P = struct { value: []std.json.Value };
try std.json.stringify(P{ .value = i.items }, .{ }, out.writer());
},
.object => {
const i = self.x.?;
const P = struct { value: ?std.json.Value };
try std.json.stringify(P{ .value = i }, .{ }, out.writer());
},
else => { }
}
std.debug.print("{s}\n", .{ out.items });
}
pub fn index(self: T, i: usize) T {
if (i > self.x.?.array.items.len) {
std.debug.print("ERROR::{s}::", .{ "index out of bounds" });
return T.init(self.x.?);
}
return T.init(self.x.?.array.items[i]);
}
};
test {
var val: T = undefined;
const parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, my_json, .{ });
defer parsed.deinit();
std.debug.print("\n", .{ });
const json = T.init(parsed.value);
try json.unpack();
val = json.get("hello");
try val.unpack();
val = json.get("okay");
try val.unpack();
val = json.get("okay").index(2);
try val.unpack();
val = json.get("okay").index(3);
try val.unpack();
val = json.get("hello").get("id");
try val.unpack();
val = json.get("hello").get("hash");
try val.unpack();
val = json.get("hello").get("hash").index(0);
try val.unpack();
val = json.get("hello").get("hash").index(1);
try val.unpack();
val = json.get("invalid");
try val.unpack();
val = json.get("hello").get("hash").index(8);
try val.unpack();
}
Any feedback is appreciated