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}