master
1//! JSON parsing and stringification conforming to RFC 8259. https://datatracker.ietf.org/doc/html/rfc8259
2//!
3//! The low-level `Scanner` API produces `Token`s from an input slice or successive slices of inputs,
4//! The `Reader` API connects a `std.Io.GenericReader` to a `Scanner`.
5//!
6//! The high-level `parseFromSlice` and `parseFromTokenSource` deserialize a JSON document into a Zig type.
7//! Parse into a dynamically-typed `Value` to load any JSON value for runtime inspection.
8//!
9//! The low-level `writeStream` emits syntax-conformant JSON tokens to a `std.Io.Writer`.
10//! The high-level `stringify` serializes a Zig or `Value` type into JSON.
11
12const builtin = @import("builtin");
13const std = @import("std");
14const testing = std.testing;
15
16test Scanner {
17 var scanner = Scanner.initCompleteInput(testing.allocator, "{\"foo\": 123}\n");
18 defer scanner.deinit();
19 try testing.expectEqual(Token.object_begin, try scanner.next());
20 try testing.expectEqualSlices(u8, "foo", (try scanner.next()).string);
21 try testing.expectEqualSlices(u8, "123", (try scanner.next()).number);
22 try testing.expectEqual(Token.object_end, try scanner.next());
23 try testing.expectEqual(Token.end_of_document, try scanner.next());
24}
25
26test parseFromSlice {
27 var parsed_str = try parseFromSlice([]const u8, testing.allocator, "\"a\\u0020b\"", .{});
28 defer parsed_str.deinit();
29 try testing.expectEqualSlices(u8, "a b", parsed_str.value);
30
31 const T = struct { a: i32 = -1, b: [2]u8 };
32 var parsed_struct = try parseFromSlice(T, testing.allocator, "{\"b\":\"xy\"}", .{});
33 defer parsed_struct.deinit();
34 try testing.expectEqual(@as(i32, -1), parsed_struct.value.a); // default value
35 try testing.expectEqualSlices(u8, "xy", parsed_struct.value.b[0..]);
36}
37
38test Value {
39 var parsed = try parseFromSlice(Value, testing.allocator, "{\"anything\": \"goes\"}", .{});
40 defer parsed.deinit();
41 try testing.expectEqualSlices(u8, "goes", parsed.value.object.get("anything").?.string);
42}
43
44test Stringify {
45 var out: std.Io.Writer.Allocating = .init(testing.allocator);
46 var write_stream: Stringify = .{
47 .writer = &out.writer,
48 .options = .{ .whitespace = .indent_2 },
49 };
50 defer out.deinit();
51 try write_stream.beginObject();
52 try write_stream.objectField("foo");
53 try write_stream.write(123);
54 try write_stream.endObject();
55 const expected =
56 \\{
57 \\ "foo": 123
58 \\}
59 ;
60 try testing.expectEqualSlices(u8, expected, out.written());
61}
62
63pub const ObjectMap = @import("json/dynamic.zig").ObjectMap;
64pub const Array = @import("json/dynamic.zig").Array;
65pub const Value = @import("json/dynamic.zig").Value;
66
67pub const ArrayHashMap = @import("json/hashmap.zig").ArrayHashMap;
68
69pub const Scanner = @import("json/Scanner.zig");
70pub const validate = Scanner.validate;
71pub const Error = Scanner.Error;
72pub const default_buffer_size = Scanner.default_buffer_size;
73pub const Token = Scanner.Token;
74pub const TokenType = Scanner.TokenType;
75pub const Diagnostics = Scanner.Diagnostics;
76pub const AllocWhen = Scanner.AllocWhen;
77pub const default_max_value_len = Scanner.default_max_value_len;
78pub const Reader = Scanner.Reader;
79pub const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger;
80
81pub const ParseOptions = @import("json/static.zig").ParseOptions;
82pub const Parsed = @import("json/static.zig").Parsed;
83pub const parseFromSlice = @import("json/static.zig").parseFromSlice;
84pub const parseFromSliceLeaky = @import("json/static.zig").parseFromSliceLeaky;
85pub const parseFromTokenSource = @import("json/static.zig").parseFromTokenSource;
86pub const parseFromTokenSourceLeaky = @import("json/static.zig").parseFromTokenSourceLeaky;
87pub const innerParse = @import("json/static.zig").innerParse;
88pub const parseFromValue = @import("json/static.zig").parseFromValue;
89pub const parseFromValueLeaky = @import("json/static.zig").parseFromValueLeaky;
90pub const innerParseFromValue = @import("json/static.zig").innerParseFromValue;
91pub const ParseError = @import("json/static.zig").ParseError;
92pub const ParseFromValueError = @import("json/static.zig").ParseFromValueError;
93
94pub const Stringify = @import("json/Stringify.zig");
95
96/// Returns a formatter that formats the given value using stringify.
97pub fn fmt(value: anytype, options: Stringify.Options) Formatter(@TypeOf(value)) {
98 return Formatter(@TypeOf(value)){ .value = value, .options = options };
99}
100
101test fmt {
102 const expectFmt = std.testing.expectFmt;
103 try expectFmt("123", "{f}", .{fmt(@as(u32, 123), .{})});
104 try expectFmt(
105 \\{"num":927,"msg":"hello","sub":{"mybool":true}}
106 , "{f}", .{fmt(struct {
107 num: u32,
108 msg: []const u8,
109 sub: struct {
110 mybool: bool,
111 },
112 }{
113 .num = 927,
114 .msg = "hello",
115 .sub = .{ .mybool = true },
116 }, .{})});
117}
118
119/// Formats the given value using stringify.
120pub fn Formatter(comptime T: type) type {
121 return struct {
122 value: T,
123 options: Stringify.Options,
124
125 pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void {
126 try Stringify.value(self.value, self.options, writer);
127 }
128 };
129}
130
131test {
132 _ = @import("json/test.zig");
133 _ = Scanner;
134 _ = @import("json/dynamic.zig");
135 _ = @import("json/hashmap.zig");
136 _ = @import("json/static.zig");
137 _ = Stringify;
138 _ = @import("json/JSONTestSuite_test.zig");
139}