| 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.Reader` 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 | |
| 12 | const builtin = @import("builtin"); |
| 13 | const std = @import("std"); |
| 14 | const testing = std.testing; |
| 15 | |
| 16 | test 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 | |
| 26 | test 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 | |
| 38 | test 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 | |
| 44 | test 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 | |
| 63 | pub const ObjectMap = @import("json/dynamic.zig").ObjectMap; |
| 64 | pub const Array = @import("json/dynamic.zig").Array; |
| 65 | pub const Value = @import("json/dynamic.zig").Value; |
| 66 | |
| 67 | pub const ArrayHashMap = @import("json/hashmap.zig").ArrayHashMap; |
| 68 | |
| 69 | pub const Scanner = @import("json/Scanner.zig"); |
| 70 | pub const validate = Scanner.validate; |
| 71 | pub const Error = Scanner.Error; |
| 72 | pub const default_buffer_size = Scanner.default_buffer_size; |
| 73 | pub const Token = Scanner.Token; |
| 74 | pub const TokenType = Scanner.TokenType; |
| 75 | pub const Diagnostics = Scanner.Diagnostics; |
| 76 | pub const AllocWhen = Scanner.AllocWhen; |
| 77 | pub const default_max_value_len = Scanner.default_max_value_len; |
| 78 | pub const Reader = Scanner.Reader; |
| 79 | pub const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger; |
| 80 | |
| 81 | pub const ParseOptions = @import("json/static.zig").ParseOptions; |
| 82 | pub const Parsed = @import("json/static.zig").Parsed; |
| 83 | pub const parseFromSlice = @import("json/static.zig").parseFromSlice; |
| 84 | pub const parseFromSliceLeaky = @import("json/static.zig").parseFromSliceLeaky; |
| 85 | pub const parseFromTokenSource = @import("json/static.zig").parseFromTokenSource; |
| 86 | pub const parseFromTokenSourceLeaky = @import("json/static.zig").parseFromTokenSourceLeaky; |
| 87 | pub const innerParse = @import("json/static.zig").innerParse; |
| 88 | pub const parseFromValue = @import("json/static.zig").parseFromValue; |
| 89 | pub const parseFromValueLeaky = @import("json/static.zig").parseFromValueLeaky; |
| 90 | pub const innerParseFromValue = @import("json/static.zig").innerParseFromValue; |
| 91 | pub const ParseError = @import("json/static.zig").ParseError; |
| 92 | pub const ParseFromValueError = @import("json/static.zig").ParseFromValueError; |
| 93 | |
| 94 | pub const Stringify = @import("json/Stringify.zig"); |
| 95 | |
| 96 | /// Returns a formatter that formats the given value using stringify. |
| 97 | pub fn fmt(value: anytype, options: Stringify.Options) Formatter(@TypeOf(value)) { |
| 98 | return Formatter(@TypeOf(value)){ .value = value, .options = options }; |
| 99 | } |
| 100 | |
| 101 | test 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. |
| 120 | pub 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 | |
| 131 | test { |
| 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 | } |