master
  1const std = @import("std");
  2const testing = std.testing;
  3const ArenaAllocator = std.heap.ArenaAllocator;
  4const Allocator = std.mem.Allocator;
  5
  6const parseFromSlice = @import("./static.zig").parseFromSlice;
  7const parseFromSliceLeaky = @import("./static.zig").parseFromSliceLeaky;
  8const parseFromTokenSource = @import("./static.zig").parseFromTokenSource;
  9const parseFromTokenSourceLeaky = @import("./static.zig").parseFromTokenSourceLeaky;
 10const innerParse = @import("./static.zig").innerParse;
 11const parseFromValue = @import("./static.zig").parseFromValue;
 12const parseFromValueLeaky = @import("./static.zig").parseFromValueLeaky;
 13const ParseOptions = @import("./static.zig").ParseOptions;
 14
 15const Scanner = @import("Scanner.zig");
 16
 17const Value = @import("./dynamic.zig").Value;
 18
 19const Primitives = struct {
 20    bool: bool,
 21    // f16, f80, f128: don't work in std.fmt.parseFloat(T).
 22    f32: f32,
 23    f64: f64,
 24    u0: u0,
 25    i0: i0,
 26    u1: u1,
 27    i1: i1,
 28    u8: u8,
 29    i8: i8,
 30    i130: i130,
 31};
 32
 33const primitives_0 = Primitives{
 34    .bool = false,
 35    .f32 = 0,
 36    .f64 = 0,
 37    .u0 = 0,
 38    .i0 = 0,
 39    .u1 = 0,
 40    .i1 = 0,
 41    .u8 = 0,
 42    .i8 = 0,
 43    .i130 = 0,
 44};
 45const primitives_0_doc_0 =
 46    \\{
 47    \\  "bool": false,
 48    \\  "f32": 0,
 49    \\  "f64": 0,
 50    \\  "u0": 0,
 51    \\  "i0": 0,
 52    \\  "u1": 0,
 53    \\  "i1": 0,
 54    \\  "u8": 0,
 55    \\  "i8": 0,
 56    \\  "i130": 0
 57    \\}
 58;
 59const primitives_0_doc_1 = // looks like a float.
 60    \\{
 61    \\  "bool": false,
 62    \\  "f32": 0.0,
 63    \\  "f64": 0.0,
 64    \\  "u0": 0.0,
 65    \\  "i0": 0.0,
 66    \\  "u1": 0.0,
 67    \\  "i1": 0.0,
 68    \\  "u8": 0.0,
 69    \\  "i8": 0.0,
 70    \\  "i130": 0.0
 71    \\}
 72;
 73
 74const primitives_1 = Primitives{
 75    .bool = true,
 76    .f32 = 1073741824,
 77    .f64 = 1152921504606846976,
 78    .u0 = 0,
 79    .i0 = 0,
 80    .u1 = 1,
 81    .i1 = -1,
 82    .u8 = 255,
 83    .i8 = -128,
 84    .i130 = -680564733841876926926749214863536422911,
 85};
 86const primitives_1_doc_0 =
 87    \\{
 88    \\  "bool": true,
 89    \\  "f32": 1073741824,
 90    \\  "f64": 1152921504606846976,
 91    \\  "u0": 0,
 92    \\  "i0": 0,
 93    \\  "u1": 1,
 94    \\  "i1": -1,
 95    \\  "u8": 255,
 96    \\  "i8": -128,
 97    \\  "i130": -680564733841876926926749214863536422911
 98    \\}
 99;
100const primitives_1_doc_1 = // float rounding.
101    \\{
102    \\  "bool": true,
103    \\  "f32": 1073741825,
104    \\  "f64": 1152921504606846977,
105    \\  "u0": 0,
106    \\  "i0": 0,
107    \\  "u1": 1,
108    \\  "i1": -1,
109    \\  "u8": 255,
110    \\  "i8": -128,
111    \\  "i130": -680564733841876926926749214863536422911
112    \\}
113;
114
115const Aggregates = struct {
116    optional: ?i32,
117    array: [4]i32,
118    vector: @Vector(4, i32),
119    pointer: *i32,
120    pointer_const: *const i32,
121    slice: []i32,
122    slice_const: []const i32,
123    slice_sentinel: [:0]i32,
124    slice_sentinel_const: [:0]const i32,
125};
126
127var zero: i32 = 0;
128const zero_const: i32 = 0;
129var array_of_zeros: [4:0]i32 = [_:0]i32{ 0, 0, 0, 0 };
130var one: i32 = 1;
131const one_const: i32 = 1;
132var array_countdown: [4:0]i32 = [_:0]i32{ 4, 3, 2, 1 };
133
134const aggregates_0 = Aggregates{
135    .optional = null,
136    .array = [4]i32{ 0, 0, 0, 0 },
137    .vector = @Vector(4, i32){ 0, 0, 0, 0 },
138    .pointer = &zero,
139    .pointer_const = &zero_const,
140    .slice = array_of_zeros[0..0],
141    .slice_const = &[_]i32{},
142    .slice_sentinel = array_of_zeros[0..0 :0],
143    .slice_sentinel_const = &[_:0]i32{},
144};
145const aggregates_0_doc =
146    \\{
147    \\  "optional": null,
148    \\  "array": [0, 0, 0, 0],
149    \\  "vector": [0, 0, 0, 0],
150    \\  "pointer": 0,
151    \\  "pointer_const": 0,
152    \\  "slice": [],
153    \\  "slice_const": [],
154    \\  "slice_sentinel": [],
155    \\  "slice_sentinel_const": []
156    \\}
157;
158
159const aggregates_1 = Aggregates{
160    .optional = 1,
161    .array = [4]i32{ 1, 2, 3, 4 },
162    .vector = @Vector(4, i32){ 1, 2, 3, 4 },
163    .pointer = &one,
164    .pointer_const = &one_const,
165    .slice = array_countdown[0..],
166    .slice_const = array_countdown[0..],
167    .slice_sentinel = array_countdown[0.. :0],
168    .slice_sentinel_const = array_countdown[0.. :0],
169};
170const aggregates_1_doc =
171    \\{
172    \\  "optional": 1,
173    \\  "array": [1, 2, 3, 4],
174    \\  "vector": [1, 2, 3, 4],
175    \\  "pointer": 1,
176    \\  "pointer_const": 1,
177    \\  "slice": [4, 3, 2, 1],
178    \\  "slice_const": [4, 3, 2, 1],
179    \\  "slice_sentinel": [4, 3, 2, 1],
180    \\  "slice_sentinel_const": [4, 3, 2, 1]
181    \\}
182;
183
184const Strings = struct {
185    slice_u8: []u8,
186    slice_const_u8: []const u8,
187    array_u8: [4]u8,
188    slice_sentinel_u8: [:0]u8,
189    slice_const_sentinel_u8: [:0]const u8,
190    array_sentinel_u8: [4:0]u8,
191};
192
193var abcd = [4:0]u8{ 'a', 'b', 'c', 'd' };
194const strings_0 = Strings{
195    .slice_u8 = abcd[0..],
196    .slice_const_u8 = "abcd",
197    .array_u8 = [4]u8{ 'a', 'b', 'c', 'd' },
198    .slice_sentinel_u8 = abcd[0..],
199    .slice_const_sentinel_u8 = "abcd",
200    .array_sentinel_u8 = [4:0]u8{ 'a', 'b', 'c', 'd' },
201};
202const strings_0_doc_0 =
203    \\{
204    \\  "slice_u8": "abcd",
205    \\  "slice_const_u8": "abcd",
206    \\  "array_u8": "abcd",
207    \\  "slice_sentinel_u8": "abcd",
208    \\  "slice_const_sentinel_u8": "abcd",
209    \\  "array_sentinel_u8": "abcd"
210    \\}
211;
212const strings_0_doc_1 =
213    \\{
214    \\  "slice_u8": [97, 98, 99, 100],
215    \\  "slice_const_u8": [97, 98, 99, 100],
216    \\  "array_u8": [97, 98, 99, 100],
217    \\  "slice_sentinel_u8": [97, 98, 99, 100],
218    \\  "slice_const_sentinel_u8": [97, 98, 99, 100],
219    \\  "array_sentinel_u8": [97, 98, 99, 100]
220    \\}
221;
222
223const Subnamespaces = struct {
224    packed_struct: packed struct { a: u32, b: u32 },
225    union_enum: union(enum) { i: i32, s: []const u8, v },
226    inferred_enum: enum { a, b },
227    explicit_enum: enum(u8) { a = 0, b = 1 },
228
229    custom_struct: struct {
230        pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) !@This() {
231            _ = allocator;
232            _ = options;
233            try source.skipValue();
234            return @This(){};
235        }
236        pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() {
237            _ = allocator;
238            _ = source;
239            _ = options;
240            return @This(){};
241        }
242    },
243    custom_union: union(enum) {
244        i: i32,
245        s: []const u8,
246        pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) !@This() {
247            _ = allocator;
248            _ = options;
249            try source.skipValue();
250            return @This(){ .i = 0 };
251        }
252        pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() {
253            _ = allocator;
254            _ = source;
255            _ = options;
256            return @This(){ .i = 0 };
257        }
258    },
259    custom_enum: enum {
260        a,
261        b,
262        pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) !@This() {
263            _ = allocator;
264            _ = options;
265            try source.skipValue();
266            return .a;
267        }
268        pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() {
269            _ = allocator;
270            _ = source;
271            _ = options;
272            return .a;
273        }
274    },
275};
276
277const subnamespaces_0 = Subnamespaces{
278    .packed_struct = .{ .a = 0, .b = 0 },
279    .union_enum = .{ .i = 0 },
280    .inferred_enum = .a,
281    .explicit_enum = .a,
282    .custom_struct = .{},
283    .custom_union = .{ .i = 0 },
284    .custom_enum = .a,
285};
286const subnamespaces_0_doc =
287    \\{
288    \\  "packed_struct": {"a": 0, "b": 0},
289    \\  "union_enum": {"i": 0},
290    \\  "inferred_enum": "a",
291    \\  "explicit_enum": "a",
292    \\  "custom_struct": null,
293    \\  "custom_union": null,
294    \\  "custom_enum": null
295    \\}
296;
297
298fn testAllParseFunctions(comptime T: type, expected: T, doc: []const u8) !void {
299    // First do the one with the debug info in case we get a SyntaxError or something.
300    {
301        var scanner = Scanner.initCompleteInput(testing.allocator, doc);
302        defer scanner.deinit();
303        var diagnostics = Scanner.Diagnostics{};
304        scanner.enableDiagnostics(&diagnostics);
305        var parsed = parseFromTokenSource(T, testing.allocator, &scanner, .{}) catch |e| {
306            std.debug.print("at line,col: {}:{}\n", .{ diagnostics.getLine(), diagnostics.getColumn() });
307            return e;
308        };
309        defer parsed.deinit();
310        try testing.expectEqualDeep(expected, parsed.value);
311    }
312    {
313        const parsed = try parseFromSlice(T, testing.allocator, doc, .{});
314        defer parsed.deinit();
315        try testing.expectEqualDeep(expected, parsed.value);
316    }
317    {
318        var stream: std.Io.Reader = .fixed(doc);
319        var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream);
320        defer json_reader.deinit();
321        var parsed = try parseFromTokenSource(T, testing.allocator, &json_reader, .{});
322        defer parsed.deinit();
323        try testing.expectEqualDeep(expected, parsed.value);
324    }
325
326    var arena = ArenaAllocator.init(testing.allocator);
327    defer arena.deinit();
328    {
329        try testing.expectEqualDeep(expected, try parseFromSliceLeaky(T, arena.allocator(), doc, .{}));
330    }
331    {
332        var scanner = Scanner.initCompleteInput(testing.allocator, doc);
333        defer scanner.deinit();
334        try testing.expectEqualDeep(expected, try parseFromTokenSourceLeaky(T, arena.allocator(), &scanner, .{}));
335    }
336    {
337        var stream: std.Io.Reader = .fixed(doc);
338        var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream);
339        defer json_reader.deinit();
340        try testing.expectEqualDeep(expected, try parseFromTokenSourceLeaky(T, arena.allocator(), &json_reader, .{}));
341    }
342
343    const parsed_dynamic = try parseFromSlice(Value, testing.allocator, doc, .{});
344    defer parsed_dynamic.deinit();
345    {
346        const parsed = try parseFromValue(T, testing.allocator, parsed_dynamic.value, .{});
347        defer parsed.deinit();
348        try testing.expectEqualDeep(expected, parsed.value);
349    }
350    {
351        try testing.expectEqualDeep(expected, try parseFromValueLeaky(T, arena.allocator(), parsed_dynamic.value, .{}));
352    }
353}
354
355test "test all types" {
356    if (true) return error.SkipZigTest; // See https://github.com/ziglang/zig/issues/16108
357    try testAllParseFunctions(Primitives, primitives_0, primitives_0_doc_0);
358    try testAllParseFunctions(Primitives, primitives_0, primitives_0_doc_1);
359    try testAllParseFunctions(Primitives, primitives_1, primitives_1_doc_0);
360    try testAllParseFunctions(Primitives, primitives_1, primitives_1_doc_1);
361
362    try testAllParseFunctions(Aggregates, aggregates_0, aggregates_0_doc);
363    try testAllParseFunctions(Aggregates, aggregates_1, aggregates_1_doc);
364
365    try testAllParseFunctions(Strings, strings_0, strings_0_doc_0);
366    try testAllParseFunctions(Strings, strings_0, strings_0_doc_1);
367
368    try testAllParseFunctions(Subnamespaces, subnamespaces_0, subnamespaces_0_doc);
369}
370
371test "parse" {
372    try testing.expectEqual(false, try parseFromSliceLeaky(bool, testing.allocator, "false", .{}));
373    try testing.expectEqual(true, try parseFromSliceLeaky(bool, testing.allocator, "true", .{}));
374    try testing.expectEqual(1, try parseFromSliceLeaky(u1, testing.allocator, "1", .{}));
375    try testing.expectError(error.Overflow, parseFromSliceLeaky(u1, testing.allocator, "50", .{}));
376    try testing.expectEqual(42, try parseFromSliceLeaky(u64, testing.allocator, "42", .{}));
377    try testing.expectEqual(42, try parseFromSliceLeaky(f64, testing.allocator, "42.0", .{}));
378    try testing.expectEqual(null, try parseFromSliceLeaky(?bool, testing.allocator, "null", .{}));
379    try testing.expectEqual(true, try parseFromSliceLeaky(?bool, testing.allocator, "true", .{}));
380
381    try testing.expectEqual("foo".*, try parseFromSliceLeaky([3]u8, testing.allocator, "\"foo\"", .{}));
382    try testing.expectEqual("foo".*, try parseFromSliceLeaky([3]u8, testing.allocator, "[102, 111, 111]", .{}));
383    try testing.expectEqual(undefined, try parseFromSliceLeaky([0]u8, testing.allocator, "[]", .{}));
384
385    try testing.expectEqual(12345678901234567890, try parseFromSliceLeaky(u64, testing.allocator, "\"12345678901234567890\"", .{}));
386    try testing.expectEqual(123.456, try parseFromSliceLeaky(f64, testing.allocator, "\"123.456\"", .{}));
387}
388
389test "parse into enum" {
390    const T = enum(u32) {
391        Foo = 42,
392        Bar,
393        @"with\\escape",
394    };
395    try testing.expectEqual(.Foo, try parseFromSliceLeaky(T, testing.allocator, "\"Foo\"", .{}));
396    try testing.expectEqual(.Foo, try parseFromSliceLeaky(T, testing.allocator, "42", .{}));
397    try testing.expectEqual(.@"with\\escape", try parseFromSliceLeaky(T, testing.allocator, "\"with\\\\escape\"", .{}));
398    try testing.expectError(error.InvalidEnumTag, parseFromSliceLeaky(T, testing.allocator, "5", .{}));
399    try testing.expectError(error.InvalidEnumTag, parseFromSliceLeaky(T, testing.allocator, "\"Qux\"", .{}));
400}
401
402test "parse into that allocates a slice" {
403    {
404        // string as string
405        const parsed = try parseFromSlice([]u8, testing.allocator, "\"foo\"", .{});
406        defer parsed.deinit();
407        try testing.expectEqualSlices(u8, "foo", parsed.value);
408    }
409    {
410        // string as array of u8 integers
411        const parsed = try parseFromSlice([]u8, testing.allocator, "[102, 111, 111]", .{});
412        defer parsed.deinit();
413        try testing.expectEqualSlices(u8, "foo", parsed.value);
414    }
415    {
416        const parsed = try parseFromSlice([]u8, testing.allocator, "\"with\\\\escape\"", .{});
417        defer parsed.deinit();
418        try testing.expectEqualSlices(u8, "with\\escape", parsed.value);
419    }
420}
421
422test "parse into sentinel slice" {
423    const parsed = try parseFromSlice([:0]const u8, testing.allocator, "\"\\n\"", .{});
424    defer parsed.deinit();
425    try testing.expect(std.mem.eql(u8, parsed.value, "\n"));
426}
427
428test "parse into tagged union" {
429    const T = union(enum) {
430        nothing,
431        int: i32,
432        float: f64,
433        string: []const u8,
434    };
435    try testing.expectEqual(T{ .float = 1.5 }, try parseFromSliceLeaky(T, testing.allocator, "{\"float\":1.5}", .{}));
436    try testing.expectEqual(T{ .int = 1 }, try parseFromSliceLeaky(T, testing.allocator, "{\"int\":1}", .{}));
437    try testing.expectEqual(T{ .nothing = {} }, try parseFromSliceLeaky(T, testing.allocator, "{\"nothing\":{}}", .{}));
438    const parsed = try parseFromSlice(T, testing.allocator, "{\"string\":\"foo\"}", .{});
439    defer parsed.deinit();
440    try testing.expectEqualSlices(u8, "foo", parsed.value.string);
441}
442
443test "parse into tagged union errors" {
444    const T = union(enum) {
445        nothing,
446        int: i32,
447        float: f64,
448        string: []const u8,
449    };
450    var arena = ArenaAllocator.init(testing.allocator);
451    defer arena.deinit();
452    try testing.expectError(error.UnexpectedToken, parseFromSliceLeaky(T, arena.allocator(), "42", .{}));
453    try testing.expectError(error.SyntaxError, parseFromSliceLeaky(T, arena.allocator(), "{\"int\":1} 42", .{}));
454    try testing.expectError(error.UnexpectedToken, parseFromSliceLeaky(T, arena.allocator(), "{}", .{}));
455    try testing.expectError(error.UnknownField, parseFromSliceLeaky(T, arena.allocator(), "{\"bogus\":1}", .{}));
456    try testing.expectError(error.UnexpectedToken, parseFromSliceLeaky(T, arena.allocator(), "{\"int\":1, \"int\":1", .{}));
457    try testing.expectError(error.UnexpectedToken, parseFromSliceLeaky(T, arena.allocator(), "{\"int\":1, \"float\":1.0}", .{}));
458    try testing.expectError(error.UnexpectedToken, parseFromSliceLeaky(T, arena.allocator(), "{\"nothing\":null}", .{}));
459    try testing.expectError(error.UnexpectedToken, parseFromSliceLeaky(T, arena.allocator(), "{\"nothing\":{\"no\":0}}", .{}));
460
461    // Allocator failure
462    try testing.expectError(error.OutOfMemory, parseFromSlice(T, testing.failing_allocator, "{\"string\"\"foo\"}", .{}));
463}
464
465test "parse into struct with no fields" {
466    const T = struct {};
467    const parsed = try parseFromSlice(T, testing.allocator, "{}", .{});
468    defer parsed.deinit();
469    try testing.expectEqual(T{}, parsed.value);
470}
471
472const test_const_value: usize = 123;
473
474test "parse into struct with default const pointer field" {
475    const T = struct { a: *const usize = &test_const_value };
476    const parsed = try parseFromSlice(T, testing.allocator, "{}", .{});
477    defer parsed.deinit();
478    try testing.expectEqual(T{}, parsed.value);
479}
480
481const test_default_usize: usize = 123;
482const test_default_usize_ptr: *align(1) const usize = &test_default_usize;
483const test_default_str: []const u8 = "test str";
484const test_default_str_slice: [2][]const u8 = [_][]const u8{
485    "test1",
486    "test2",
487};
488
489test "freeing parsed structs with pointers to default values" {
490    const T = struct {
491        int: *const usize = &test_default_usize,
492        int_ptr: *allowzero align(1) const usize = test_default_usize_ptr,
493        str: []const u8 = test_default_str,
494        str_slice: []const []const u8 = &test_default_str_slice,
495    };
496
497    var parsed = try parseFromSlice(T, testing.allocator, "{}", .{});
498    try testing.expectEqual(T{}, parsed.value);
499    defer parsed.deinit();
500}
501
502test "parse into struct where destination and source lengths mismatch" {
503    const T = struct { a: [2]u8 };
504    try testing.expectError(error.LengthMismatch, parseFromSlice(T, testing.allocator, "{\"a\": \"bbb\"}", .{}));
505}
506
507test "parse into struct with misc fields" {
508    const T = struct {
509        int: i64,
510        float: f64,
511        @"with\\escape": bool,
512        @"withąunicode😂": bool,
513        language: []const u8,
514        optional: ?bool,
515        default_field: i32 = 42,
516        static_array: [3]f64,
517        dynamic_array: []f64,
518
519        complex: struct {
520            nested: []const u8,
521        },
522
523        veryComplex: []struct {
524            foo: []const u8,
525        },
526
527        a_union: Union,
528        const Union = union(enum) {
529            x: u8,
530            float: f64,
531            string: []const u8,
532        };
533    };
534    const document_str =
535        \\{
536        \\  "int": 420,
537        \\  "float": 3.14,
538        \\  "with\\escape": true,
539        \\  "with\u0105unicode\ud83d\ude02": false,
540        \\  "language": "zig",
541        \\  "optional": null,
542        \\  "static_array": [66.6, 420.420, 69.69],
543        \\  "dynamic_array": [66.6, 420.420, 69.69],
544        \\  "complex": {
545        \\    "nested": "zig"
546        \\  },
547        \\  "veryComplex": [
548        \\    {
549        \\      "foo": "zig"
550        \\    }, {
551        \\      "foo": "rocks"
552        \\    }
553        \\  ],
554        \\  "a_union": {
555        \\    "float": 100000
556        \\  }
557        \\}
558    ;
559    const parsed = try parseFromSlice(T, testing.allocator, document_str, .{});
560    defer parsed.deinit();
561    const r = &parsed.value;
562    try testing.expectEqual(@as(i64, 420), r.int);
563    try testing.expectEqual(@as(f64, 3.14), r.float);
564    try testing.expectEqual(true, r.@"with\\escape");
565    try testing.expectEqual(false, r.@"withąunicode😂");
566    try testing.expectEqualSlices(u8, "zig", r.language);
567    try testing.expectEqual(@as(?bool, null), r.optional);
568    try testing.expectEqual(@as(i32, 42), r.default_field);
569    try testing.expectEqual(@as(f64, 66.6), r.static_array[0]);
570    try testing.expectEqual(@as(f64, 420.420), r.static_array[1]);
571    try testing.expectEqual(@as(f64, 69.69), r.static_array[2]);
572    try testing.expectEqual(@as(usize, 3), r.dynamic_array.len);
573    try testing.expectEqual(@as(f64, 66.6), r.dynamic_array[0]);
574    try testing.expectEqual(@as(f64, 420.420), r.dynamic_array[1]);
575    try testing.expectEqual(@as(f64, 69.69), r.dynamic_array[2]);
576    try testing.expectEqualSlices(u8, r.complex.nested, "zig");
577    try testing.expectEqualSlices(u8, "zig", r.veryComplex[0].foo);
578    try testing.expectEqualSlices(u8, "rocks", r.veryComplex[1].foo);
579    try testing.expectEqual(T.Union{ .float = 100000 }, r.a_union);
580}
581
582test "parse into struct with strings and arrays with sentinels" {
583    const T = struct {
584        language: [:0]const u8,
585        language_without_sentinel: []const u8,
586        data: [:99]const i32,
587        simple_data: []const i32,
588    };
589    const document_str =
590        \\{
591        \\  "language": "zig",
592        \\  "language_without_sentinel": "zig again!",
593        \\  "data": [1, 2, 3],
594        \\  "simple_data": [4, 5, 6]
595        \\}
596    ;
597    const parsed = try parseFromSlice(T, testing.allocator, document_str, .{});
598    defer parsed.deinit();
599
600    try testing.expectEqualSentinel(u8, 0, "zig", parsed.value.language);
601
602    const data = [_:99]i32{ 1, 2, 3 };
603    try testing.expectEqualSentinel(i32, 99, data[0..data.len], parsed.value.data);
604
605    // Make sure that arrays who aren't supposed to have a sentinel still parse without one.
606    try testing.expectEqual(@as(?i32, null), std.meta.sentinel(@TypeOf(parsed.value.simple_data)));
607    try testing.expectEqual(@as(?u8, null), std.meta.sentinel(@TypeOf(parsed.value.language_without_sentinel)));
608}
609
610test "parse into struct with duplicate field" {
611    const options_first = ParseOptions{ .duplicate_field_behavior = .use_first };
612    const options_last = ParseOptions{ .duplicate_field_behavior = .use_last };
613
614    const str = "{ \"a\": 1, \"a\": 0.25 }";
615
616    const T1 = struct { a: *u64 };
617    // both .use_first and .use_last should fail because second "a" value isn't a u64
618    try testing.expectError(error.InvalidNumber, parseFromSlice(T1, testing.allocator, str, options_first));
619    try testing.expectError(error.InvalidNumber, parseFromSlice(T1, testing.allocator, str, options_last));
620
621    var arena = ArenaAllocator.init(testing.allocator);
622    defer arena.deinit();
623
624    const T2 = struct { a: f64 };
625    try testing.expectEqual(T2{ .a = 1.0 }, try parseFromSliceLeaky(T2, arena.allocator(), str, options_first));
626    try testing.expectEqual(T2{ .a = 0.25 }, try parseFromSliceLeaky(T2, arena.allocator(), str, options_last));
627}
628
629test "parse into struct ignoring unknown fields" {
630    const T = struct {
631        int: i64,
632        language: []const u8,
633    };
634
635    const str =
636        \\{
637        \\  "int": 420,
638        \\  "float": 3.14,
639        \\  "with\\escape": true,
640        \\  "with\u0105unicode\ud83d\ude02": false,
641        \\  "optional": null,
642        \\  "static_array": [66.6, 420.420, 69.69],
643        \\  "dynamic_array": [66.6, 420.420, 69.69],
644        \\  "complex": {
645        \\    "nested": "zig"
646        \\  },
647        \\  "veryComplex": [
648        \\    {
649        \\      "foo": "zig"
650        \\    }, {
651        \\      "foo": "rocks"
652        \\    }
653        \\  ],
654        \\  "a_union": {
655        \\    "float": 100000
656        \\  },
657        \\  "language": "zig"
658        \\}
659    ;
660    const parsed = try parseFromSlice(T, testing.allocator, str, .{ .ignore_unknown_fields = true });
661    defer parsed.deinit();
662
663    try testing.expectEqual(@as(i64, 420), parsed.value.int);
664    try testing.expectEqualSlices(u8, "zig", parsed.value.language);
665}
666
667test "parse into tuple" {
668    const Union = union(enum) {
669        char: u8,
670        float: f64,
671        string: []const u8,
672    };
673    const T = std.meta.Tuple(&.{
674        i64,
675        f64,
676        bool,
677        []const u8,
678        ?bool,
679        struct {
680            foo: i32,
681            bar: []const u8,
682        },
683        std.meta.Tuple(&.{ u8, []const u8, u8 }),
684        Union,
685    });
686    const str =
687        \\[
688        \\  420,
689        \\  3.14,
690        \\  true,
691        \\  "zig",
692        \\  null,
693        \\  {
694        \\    "foo": 1,
695        \\    "bar": "zero"
696        \\  },
697        \\  [4, "två", 42],
698        \\  {"float": 12.34}
699        \\]
700    ;
701    const parsed = try parseFromSlice(T, testing.allocator, str, .{});
702    defer parsed.deinit();
703    const r = parsed.value;
704    try testing.expectEqual(@as(i64, 420), r[0]);
705    try testing.expectEqual(@as(f64, 3.14), r[1]);
706    try testing.expectEqual(true, r[2]);
707    try testing.expectEqualSlices(u8, "zig", r[3]);
708    try testing.expectEqual(@as(?bool, null), r[4]);
709    try testing.expectEqual(@as(i32, 1), r[5].foo);
710    try testing.expectEqualSlices(u8, "zero", r[5].bar);
711    try testing.expectEqual(@as(u8, 4), r[6][0]);
712    try testing.expectEqualSlices(u8, "två", r[6][1]);
713    try testing.expectEqual(@as(u8, 42), r[6][2]);
714    try testing.expectEqual(Union{ .float = 12.34 }, r[7]);
715}
716
717const ParseIntoRecursiveUnionDefinitionValue = union(enum) {
718    integer: i64,
719    array: []const ParseIntoRecursiveUnionDefinitionValue,
720};
721
722test "parse into recursive union definition" {
723    const T = struct {
724        values: ParseIntoRecursiveUnionDefinitionValue,
725    };
726
727    const parsed = try parseFromSlice(T, testing.allocator, "{\"values\":{\"array\":[{\"integer\":58}]}}", .{});
728    defer parsed.deinit();
729
730    try testing.expectEqual(@as(i64, 58), parsed.value.values.array[0].integer);
731}
732
733const ParseIntoDoubleRecursiveUnionValueFirst = union(enum) {
734    integer: i64,
735    array: []const ParseIntoDoubleRecursiveUnionValueSecond,
736};
737
738const ParseIntoDoubleRecursiveUnionValueSecond = union(enum) {
739    boolean: bool,
740    array: []const ParseIntoDoubleRecursiveUnionValueFirst,
741};
742
743test "parse into double recursive union definition" {
744    const T = struct {
745        values: ParseIntoDoubleRecursiveUnionValueFirst,
746    };
747
748    const parsed = try parseFromSlice(T, testing.allocator, "{\"values\":{\"array\":[{\"array\":[{\"integer\":58}]}]}}", .{});
749    defer parsed.deinit();
750
751    try testing.expectEqual(@as(i64, 58), parsed.value.values.array[0].array[0].integer);
752}
753
754test "parse exponential into int" {
755    const T = struct { int: i64 };
756    const r = try parseFromSliceLeaky(T, testing.allocator, "{ \"int\": 4.2e2 }", .{});
757    try testing.expectEqual(@as(i64, 420), r.int);
758    try testing.expectError(error.InvalidNumber, parseFromSliceLeaky(T, testing.allocator, "{ \"int\": 0.042e2 }", .{}));
759    try testing.expectError(error.Overflow, parseFromSliceLeaky(T, testing.allocator, "{ \"int\": 18446744073709551616.0 }", .{}));
760}
761
762test "parseFromTokenSource" {
763    {
764        var scanner = Scanner.initCompleteInput(testing.allocator, "123");
765        defer scanner.deinit();
766        var parsed = try parseFromTokenSource(u32, testing.allocator, &scanner, .{});
767        defer parsed.deinit();
768        try testing.expectEqual(@as(u32, 123), parsed.value);
769    }
770
771    {
772        var stream: std.Io.Reader = .fixed("123");
773        var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream);
774        defer json_reader.deinit();
775        var parsed = try parseFromTokenSource(u32, testing.allocator, &json_reader, .{});
776        defer parsed.deinit();
777        try testing.expectEqual(@as(u32, 123), parsed.value);
778    }
779}
780
781test "max_value_len" {
782    try testing.expectError(error.ValueTooLong, parseFromSlice([]u8, testing.allocator, "\"0123456789\"", .{ .max_value_len = 5 }));
783}
784
785test "parse into vector" {
786    const T = struct {
787        vec_i32: @Vector(4, i32),
788        vec_f32: @Vector(2, f32),
789    };
790    const s =
791        \\{
792        \\  "vec_f32": [1.5, 2.5],
793        \\  "vec_i32": [4, 5, 6, 7]
794        \\}
795    ;
796    const parsed = try parseFromSlice(T, testing.allocator, s, .{});
797    defer parsed.deinit();
798    try testing.expectApproxEqAbs(@as(f32, 1.5), parsed.value.vec_f32[0], 0.0000001);
799    try testing.expectApproxEqAbs(@as(f32, 2.5), parsed.value.vec_f32[1], 0.0000001);
800    try testing.expectEqual(@Vector(4, i32){ 4, 5, 6, 7 }, parsed.value.vec_i32);
801}
802
803fn assertKey(
804    allocator: Allocator,
805    test_string: []const u8,
806    scanner: anytype,
807) !void {
808    const token_outer = try scanner.nextAlloc(allocator, .alloc_always);
809    switch (token_outer) {
810        .allocated_string => |string| {
811            try testing.expectEqualSlices(u8, string, test_string);
812            allocator.free(string);
813        },
814        else => return error.UnexpectedToken,
815    }
816}
817test "json parse partial" {
818    const Inner = struct {
819        num: u32,
820        yes: bool,
821    };
822    const str =
823        \\{
824        \\  "outer": {
825        \\    "key1": {
826        \\      "num": 75,
827        \\      "yes": true
828        \\    },
829        \\    "key2": {
830        \\      "num": 95,
831        \\      "yes": false
832        \\    }
833        \\  }
834        \\}
835    ;
836    const allocator = testing.allocator;
837    var scanner = Scanner.initCompleteInput(allocator, str);
838    defer scanner.deinit();
839
840    var arena = ArenaAllocator.init(allocator);
841    defer arena.deinit();
842
843    // Peel off the outer object
844    try testing.expectEqual(try scanner.next(), .object_begin);
845    try assertKey(allocator, "outer", &scanner);
846    try testing.expectEqual(try scanner.next(), .object_begin);
847    try assertKey(allocator, "key1", &scanner);
848
849    // Parse the inner object to an Inner struct
850    const inner_token = try innerParse(
851        Inner,
852        arena.allocator(),
853        &scanner,
854        .{ .max_value_len = scanner.input.len },
855    );
856    try testing.expectEqual(inner_token.num, 75);
857    try testing.expectEqual(inner_token.yes, true);
858
859    // Get they next key
860    try assertKey(allocator, "key2", &scanner);
861    const inner_token_2 = try innerParse(
862        Inner,
863        arena.allocator(),
864        &scanner,
865        .{ .max_value_len = scanner.input.len },
866    );
867    try testing.expectEqual(inner_token_2.num, 95);
868    try testing.expectEqual(inner_token_2.yes, false);
869    try testing.expectEqual(try scanner.next(), .object_end);
870}
871
872test "json parse allocate when streaming" {
873    const T = struct {
874        not_const: []u8,
875        is_const: []const u8,
876    };
877    const str =
878        \\{
879        \\  "not_const": "non const string",
880        \\  "is_const": "const string"
881        \\}
882    ;
883    const allocator = testing.allocator;
884    var arena = ArenaAllocator.init(allocator);
885    defer arena.deinit();
886
887    var stream: std.Io.Reader = .fixed(str);
888    var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream);
889
890    const parsed = parseFromTokenSourceLeaky(T, arena.allocator(), &json_reader, .{}) catch |err| {
891        json_reader.deinit();
892        return err;
893    };
894    // Deinit our reader to invalidate its buffer
895    json_reader.deinit();
896
897    // If either of these was invalidated, it would be full of '0xAA'
898    try testing.expectEqualSlices(u8, parsed.not_const, "non const string");
899    try testing.expectEqualSlices(u8, parsed.is_const, "const string");
900}
901
902test "parse at comptime" {
903    const doc =
904        \\{
905        \\    "vals": {
906        \\        "testing": 1,
907        \\        "production": 42
908        \\    },
909        \\    "uptime": 9999
910        \\}
911    ;
912    const Config = struct {
913        vals: struct { testing: u8, production: u8 },
914        uptime: u64,
915    };
916    const config = comptime x: {
917        var buf: [300]u8 = undefined;
918        var fba = std.heap.FixedBufferAllocator.init(&buf);
919        const res = parseFromSliceLeaky(Config, fba.allocator(), doc, .{});
920        // Assert no error can occur since we are
921        // parsing this JSON at comptime!
922        break :x res catch unreachable;
923    };
924    comptime testing.expectEqual(@as(u64, 9999), config.uptime) catch unreachable;
925}
926
927test "parse with zero-bit field" {
928    const str =
929        \\{
930        \\    "a": ["a", "a"],
931        \\    "b": "a"
932        \\}
933    ;
934    const ZeroSizedEnum = enum { a };
935    try testing.expectEqual(0, @sizeOf(ZeroSizedEnum));
936
937    const Inner = struct { a: []const ZeroSizedEnum, b: ZeroSizedEnum };
938    const expected: Inner = .{ .a = &.{ .a, .a }, .b = .a };
939
940    try testAllParseFunctions(Inner, expected, str);
941}