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