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}