1//! ZON can be serialized with `serialize`.
2//!
3//! The following functions are provided for serializing recursive types:
4//! * `serializeMaxDepth`
5//! * `serializeArbitraryDepth`
6//!
7//! For additional control over serialization, see `Serializer`.
8//!
9//! The following types and any types that contain them may not be serialized:
10//! * `type`
11//! * `void`, except as a union payload
12//! * `noreturn`
13//! * Error sets/error unions
14//! * Untagged unions
15//! * Non-exhaustive enums
16//! * Many-pointers or C-pointers
17//! * Opaque types, including `anyopaque`
18//! * Async frame types, including `anyframe` and `anyframe->T`
19//! * Functions
20//!
21//! All other types are valid. Unsupported types will fail to serialize at compile time. Pointers
22//! are followed.
23
24const std = @import("std");
25const assert = std.debug.assert;
26const Writer = std.Io.Writer;
27const Serializer = @import("Serializer.zig");
28
29pub const SerializeOptions = struct {
30 /// If false, whitespace is omitted. Otherwise whitespace is emitted in standard Zig style.
31 whitespace: bool = true,
32 /// Determines when to emit Unicode code point literals as opposed to integer literals.
33 emit_codepoint_literals: Serializer.EmitCodepointLiterals = .never,
34 /// If true, slices of `u8`s, and pointers to arrays of `u8` are serialized as containers.
35 /// Otherwise they are serialized as string literals.
36 emit_strings_as_containers: bool = false,
37 /// If false, struct fields are not written if they are equal to their default value. Comparison
38 /// is done by `std.meta.eql`.
39 emit_default_optional_fields: bool = true,
40 /// If true, non-ASCII unicode characters are escaped.
41 escape_non_ascii: bool = false,
42};
43
44/// Serialize the given value as ZON.
45///
46/// It is asserted at comptime that `@TypeOf(val)` is not a recursive type.
47pub fn serialize(val: anytype, options: SerializeOptions, writer: *Writer) Writer.Error!void {
48 var s: Serializer = .{
49 .writer = writer,
50 .options = .{ .whitespace = options.whitespace },
51 };
52 try s.value(val, .{
53 .emit_codepoint_literals = options.emit_codepoint_literals,
54 .emit_strings_as_containers = options.emit_strings_as_containers,
55 .emit_default_optional_fields = options.emit_default_optional_fields,
56 .escape_non_ascii = options.escape_non_ascii,
57 });
58}
59
60/// Like `serialize`, but recursive types are allowed.
61///
62/// Returns `error.ExceededMaxDepth` if `depth` is exceeded. Every nested value adds one to a
63/// value's depth.
64pub fn serializeMaxDepth(
65 val: anytype,
66 options: SerializeOptions,
67 writer: *Writer,
68 depth: usize,
69) Serializer.DepthError!void {
70 var s: Serializer = .{
71 .writer = writer,
72 .options = .{ .whitespace = options.whitespace },
73 };
74 try s.valueMaxDepth(val, .{
75 .emit_codepoint_literals = options.emit_codepoint_literals,
76 .emit_strings_as_containers = options.emit_strings_as_containers,
77 .emit_default_optional_fields = options.emit_default_optional_fields,
78 .escape_non_ascii = options.escape_non_ascii,
79 }, depth);
80}
81
82/// Like `serialize`, but recursive types are allowed.
83///
84/// It is the caller's responsibility to ensure that `val` does not contain cycles.
85pub fn serializeArbitraryDepth(
86 val: anytype,
87 options: SerializeOptions,
88 writer: *Writer,
89) Serializer.Error!void {
90 var s: Serializer = .{
91 .writer = writer,
92 .options = .{ .whitespace = options.whitespace },
93 };
94 try s.valueArbitraryDepth(val, .{
95 .emit_codepoint_literals = options.emit_codepoint_literals,
96 .emit_strings_as_containers = options.emit_strings_as_containers,
97 .emit_default_optional_fields = options.emit_default_optional_fields,
98 .escape_non_ascii = options.escape_non_ascii,
99 });
100}
101
102fn isNestedOptional(T: type) bool {
103 comptime switch (@typeInfo(T)) {
104 .optional => |optional| return isNestedOptionalInner(optional.child),
105 else => return false,
106 };
107}
108
109fn isNestedOptionalInner(T: type) bool {
110 switch (@typeInfo(T)) {
111 .pointer => |pointer| {
112 if (pointer.size == .one) {
113 return isNestedOptionalInner(pointer.child);
114 } else {
115 return false;
116 }
117 },
118 .optional => return true,
119 else => return false,
120 }
121}
122
123fn expectSerializeEqual(
124 expected: []const u8,
125 value: anytype,
126 options: SerializeOptions,
127) !void {
128 var aw: Writer.Allocating = .init(std.testing.allocator);
129 const bw = &aw.writer;
130 defer aw.deinit();
131
132 try serialize(value, options, bw);
133 try std.testing.expectEqualStrings(expected, aw.written());
134}
135
136test "std.zon stringify whitespace, high level API" {
137 try expectSerializeEqual(".{}", .{}, .{});
138 try expectSerializeEqual(".{}", .{}, .{ .whitespace = false });
139
140 try expectSerializeEqual(".{1}", .{1}, .{});
141 try expectSerializeEqual(".{1}", .{1}, .{ .whitespace = false });
142
143 try expectSerializeEqual(".{1}", @as([1]u32, .{1}), .{});
144 try expectSerializeEqual(".{1}", @as([1]u32, .{1}), .{ .whitespace = false });
145
146 try expectSerializeEqual(".{1}", @as([]const u32, &.{1}), .{});
147 try expectSerializeEqual(".{1}", @as([]const u32, &.{1}), .{ .whitespace = false });
148
149 try expectSerializeEqual(".{ .x = 1 }", .{ .x = 1 }, .{});
150 try expectSerializeEqual(".{.x=1}", .{ .x = 1 }, .{ .whitespace = false });
151
152 try expectSerializeEqual(".{ 1, 2 }", .{ 1, 2 }, .{});
153 try expectSerializeEqual(".{1,2}", .{ 1, 2 }, .{ .whitespace = false });
154
155 try expectSerializeEqual(".{ 1, 2 }", @as([2]u32, .{ 1, 2 }), .{});
156 try expectSerializeEqual(".{1,2}", @as([2]u32, .{ 1, 2 }), .{ .whitespace = false });
157
158 try expectSerializeEqual(".{ 1, 2 }", @as([]const u32, &.{ 1, 2 }), .{});
159 try expectSerializeEqual(".{1,2}", @as([]const u32, &.{ 1, 2 }), .{ .whitespace = false });
160
161 try expectSerializeEqual(".{ .x = 1, .y = 2 }", .{ .x = 1, .y = 2 }, .{});
162 try expectSerializeEqual(".{.x=1,.y=2}", .{ .x = 1, .y = 2 }, .{ .whitespace = false });
163
164 try expectSerializeEqual(
165 \\.{
166 \\ 1,
167 \\ 2,
168 \\ 3,
169 \\}
170 , .{ 1, 2, 3 }, .{});
171 try expectSerializeEqual(".{1,2,3}", .{ 1, 2, 3 }, .{ .whitespace = false });
172
173 try expectSerializeEqual(
174 \\.{
175 \\ 1,
176 \\ 2,
177 \\ 3,
178 \\}
179 , @as([3]u32, .{ 1, 2, 3 }), .{});
180 try expectSerializeEqual(".{1,2,3}", @as([3]u32, .{ 1, 2, 3 }), .{ .whitespace = false });
181
182 try expectSerializeEqual(
183 \\.{
184 \\ 1,
185 \\ 2,
186 \\ 3,
187 \\}
188 , @as([]const u32, &.{ 1, 2, 3 }), .{});
189 try expectSerializeEqual(
190 ".{1,2,3}",
191 @as([]const u32, &.{ 1, 2, 3 }),
192 .{ .whitespace = false },
193 );
194
195 try expectSerializeEqual(
196 \\.{
197 \\ .x = 1,
198 \\ .y = 2,
199 \\ .z = 3,
200 \\}
201 , .{ .x = 1, .y = 2, .z = 3 }, .{});
202 try expectSerializeEqual(
203 ".{.x=1,.y=2,.z=3}",
204 .{ .x = 1, .y = 2, .z = 3 },
205 .{ .whitespace = false },
206 );
207
208 const Union = union(enum) { a: bool, b: i32, c: u8 };
209
210 try expectSerializeEqual(".{ .b = 1 }", Union{ .b = 1 }, .{});
211 try expectSerializeEqual(".{.b=1}", Union{ .b = 1 }, .{ .whitespace = false });
212
213 // Nested indentation where outer object doesn't wrap
214 try expectSerializeEqual(
215 \\.{ .inner = .{
216 \\ 1,
217 \\ 2,
218 \\ 3,
219 \\} }
220 , .{ .inner = .{ 1, 2, 3 } }, .{});
221
222 const UnionWithVoid = union(enum) { a, b: void, c: u8 };
223
224 try expectSerializeEqual(
225 \\.a
226 , UnionWithVoid.a, .{});
227}
228
229test "std.zon stringify whitespace, low level API" {
230 var aw: Writer.Allocating = .init(std.testing.allocator);
231 var s: Serializer = .{ .writer = &aw.writer };
232 defer aw.deinit();
233
234 for ([2]bool{ true, false }) |whitespace| {
235 s.options = .{ .whitespace = whitespace };
236
237 // Empty containers
238 {
239 var container = try s.beginStruct(.{});
240 try container.end();
241 try std.testing.expectEqualStrings(".{}", aw.written());
242 aw.clearRetainingCapacity();
243 }
244
245 {
246 var container = try s.beginTuple(.{});
247 try container.end();
248 try std.testing.expectEqualStrings(".{}", aw.written());
249 aw.clearRetainingCapacity();
250 }
251
252 {
253 var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
254 try container.end();
255 try std.testing.expectEqualStrings(".{}", aw.written());
256 aw.clearRetainingCapacity();
257 }
258
259 {
260 var container = try s.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
261 try container.end();
262 try std.testing.expectEqualStrings(".{}", aw.written());
263 aw.clearRetainingCapacity();
264 }
265
266 {
267 var container = try s.beginStruct(.{ .whitespace_style = .{ .fields = 0 } });
268 try container.end();
269 try std.testing.expectEqualStrings(".{}", aw.written());
270 aw.clearRetainingCapacity();
271 }
272
273 {
274 var container = try s.beginTuple(.{ .whitespace_style = .{ .fields = 0 } });
275 try container.end();
276 try std.testing.expectEqualStrings(".{}", aw.written());
277 aw.clearRetainingCapacity();
278 }
279
280 // Size 1
281 {
282 var container = try s.beginStruct(.{});
283 try container.field("a", 1, .{});
284 try container.end();
285 if (whitespace) {
286 try std.testing.expectEqualStrings(
287 \\.{
288 \\ .a = 1,
289 \\}
290 , aw.written());
291 } else {
292 try std.testing.expectEqualStrings(".{.a=1}", aw.written());
293 }
294 aw.clearRetainingCapacity();
295 }
296
297 {
298 var container = try s.beginTuple(.{});
299 try container.field(1, .{});
300 try container.end();
301 if (whitespace) {
302 try std.testing.expectEqualStrings(
303 \\.{
304 \\ 1,
305 \\}
306 , aw.written());
307 } else {
308 try std.testing.expectEqualStrings(".{1}", aw.written());
309 }
310 aw.clearRetainingCapacity();
311 }
312
313 {
314 var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
315 try container.field("a", 1, .{});
316 try container.end();
317 if (whitespace) {
318 try std.testing.expectEqualStrings(".{ .a = 1 }", aw.written());
319 } else {
320 try std.testing.expectEqualStrings(".{.a=1}", aw.written());
321 }
322 aw.clearRetainingCapacity();
323 }
324
325 {
326 // We get extra spaces here, since we didn't know up front that there would only be one
327 // field.
328 var container = try s.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
329 try container.field(1, .{});
330 try container.end();
331 if (whitespace) {
332 try std.testing.expectEqualStrings(".{ 1 }", aw.written());
333 } else {
334 try std.testing.expectEqualStrings(".{1}", aw.written());
335 }
336 aw.clearRetainingCapacity();
337 }
338
339 {
340 var container = try s.beginStruct(.{ .whitespace_style = .{ .fields = 1 } });
341 try container.field("a", 1, .{});
342 try container.end();
343 if (whitespace) {
344 try std.testing.expectEqualStrings(".{ .a = 1 }", aw.written());
345 } else {
346 try std.testing.expectEqualStrings(".{.a=1}", aw.written());
347 }
348 aw.clearRetainingCapacity();
349 }
350
351 {
352 var container = try s.beginTuple(.{ .whitespace_style = .{ .fields = 1 } });
353 try container.field(1, .{});
354 try container.end();
355 try std.testing.expectEqualStrings(".{1}", aw.written());
356 aw.clearRetainingCapacity();
357 }
358
359 // Size 2
360 {
361 var container = try s.beginStruct(.{});
362 try container.field("a", 1, .{});
363 try container.field("b", 2, .{});
364 try container.end();
365 if (whitespace) {
366 try std.testing.expectEqualStrings(
367 \\.{
368 \\ .a = 1,
369 \\ .b = 2,
370 \\}
371 , aw.written());
372 } else {
373 try std.testing.expectEqualStrings(".{.a=1,.b=2}", aw.written());
374 }
375 aw.clearRetainingCapacity();
376 }
377
378 {
379 var container = try s.beginTuple(.{});
380 try container.field(1, .{});
381 try container.field(2, .{});
382 try container.end();
383 if (whitespace) {
384 try std.testing.expectEqualStrings(
385 \\.{
386 \\ 1,
387 \\ 2,
388 \\}
389 , aw.written());
390 } else {
391 try std.testing.expectEqualStrings(".{1,2}", aw.written());
392 }
393 aw.clearRetainingCapacity();
394 }
395
396 {
397 var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
398 try container.field("a", 1, .{});
399 try container.field("b", 2, .{});
400 try container.end();
401 if (whitespace) {
402 try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", aw.written());
403 } else {
404 try std.testing.expectEqualStrings(".{.a=1,.b=2}", aw.written());
405 }
406 aw.clearRetainingCapacity();
407 }
408
409 {
410 var container = try s.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
411 try container.field(1, .{});
412 try container.field(2, .{});
413 try container.end();
414 if (whitespace) {
415 try std.testing.expectEqualStrings(".{ 1, 2 }", aw.written());
416 } else {
417 try std.testing.expectEqualStrings(".{1,2}", aw.written());
418 }
419 aw.clearRetainingCapacity();
420 }
421
422 {
423 var container = try s.beginStruct(.{ .whitespace_style = .{ .fields = 2 } });
424 try container.field("a", 1, .{});
425 try container.field("b", 2, .{});
426 try container.end();
427 if (whitespace) {
428 try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", aw.written());
429 } else {
430 try std.testing.expectEqualStrings(".{.a=1,.b=2}", aw.written());
431 }
432 aw.clearRetainingCapacity();
433 }
434
435 {
436 var container = try s.beginTuple(.{ .whitespace_style = .{ .fields = 2 } });
437 try container.field(1, .{});
438 try container.field(2, .{});
439 try container.end();
440 if (whitespace) {
441 try std.testing.expectEqualStrings(".{ 1, 2 }", aw.written());
442 } else {
443 try std.testing.expectEqualStrings(".{1,2}", aw.written());
444 }
445 aw.clearRetainingCapacity();
446 }
447
448 // Size 3
449 {
450 var container = try s.beginStruct(.{});
451 try container.field("a", 1, .{});
452 try container.field("b", 2, .{});
453 try container.field("c", 3, .{});
454 try container.end();
455 if (whitespace) {
456 try std.testing.expectEqualStrings(
457 \\.{
458 \\ .a = 1,
459 \\ .b = 2,
460 \\ .c = 3,
461 \\}
462 , aw.written());
463 } else {
464 try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", aw.written());
465 }
466 aw.clearRetainingCapacity();
467 }
468
469 {
470 var container = try s.beginTuple(.{});
471 try container.field(1, .{});
472 try container.field(2, .{});
473 try container.field(3, .{});
474 try container.end();
475 if (whitespace) {
476 try std.testing.expectEqualStrings(
477 \\.{
478 \\ 1,
479 \\ 2,
480 \\ 3,
481 \\}
482 , aw.written());
483 } else {
484 try std.testing.expectEqualStrings(".{1,2,3}", aw.written());
485 }
486 aw.clearRetainingCapacity();
487 }
488
489 {
490 var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
491 try container.field("a", 1, .{});
492 try container.field("b", 2, .{});
493 try container.field("c", 3, .{});
494 try container.end();
495 if (whitespace) {
496 try std.testing.expectEqualStrings(".{ .a = 1, .b = 2, .c = 3 }", aw.written());
497 } else {
498 try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", aw.written());
499 }
500 aw.clearRetainingCapacity();
501 }
502
503 {
504 var container = try s.beginTuple(.{ .whitespace_style = .{ .wrap = false } });
505 try container.field(1, .{});
506 try container.field(2, .{});
507 try container.field(3, .{});
508 try container.end();
509 if (whitespace) {
510 try std.testing.expectEqualStrings(".{ 1, 2, 3 }", aw.written());
511 } else {
512 try std.testing.expectEqualStrings(".{1,2,3}", aw.written());
513 }
514 aw.clearRetainingCapacity();
515 }
516
517 {
518 var container = try s.beginStruct(.{ .whitespace_style = .{ .fields = 3 } });
519 try container.field("a", 1, .{});
520 try container.field("b", 2, .{});
521 try container.field("c", 3, .{});
522 try container.end();
523 if (whitespace) {
524 try std.testing.expectEqualStrings(
525 \\.{
526 \\ .a = 1,
527 \\ .b = 2,
528 \\ .c = 3,
529 \\}
530 , aw.written());
531 } else {
532 try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", aw.written());
533 }
534 aw.clearRetainingCapacity();
535 }
536
537 {
538 var container = try s.beginTuple(.{ .whitespace_style = .{ .fields = 3 } });
539 try container.field(1, .{});
540 try container.field(2, .{});
541 try container.field(3, .{});
542 try container.end();
543 if (whitespace) {
544 try std.testing.expectEqualStrings(
545 \\.{
546 \\ 1,
547 \\ 2,
548 \\ 3,
549 \\}
550 , aw.written());
551 } else {
552 try std.testing.expectEqualStrings(".{1,2,3}", aw.written());
553 }
554 aw.clearRetainingCapacity();
555 }
556
557 // Nested objects where the outer container doesn't wrap but the inner containers do
558 {
559 var container = try s.beginStruct(.{ .whitespace_style = .{ .wrap = false } });
560 try container.field("first", .{ 1, 2, 3 }, .{});
561 try container.field("second", .{ 4, 5, 6 }, .{});
562 try container.end();
563 if (whitespace) {
564 try std.testing.expectEqualStrings(
565 \\.{ .first = .{
566 \\ 1,
567 \\ 2,
568 \\ 3,
569 \\}, .second = .{
570 \\ 4,
571 \\ 5,
572 \\ 6,
573 \\} }
574 , aw.written());
575 } else {
576 try std.testing.expectEqualStrings(
577 ".{.first=.{1,2,3},.second=.{4,5,6}}",
578 aw.written(),
579 );
580 }
581 aw.clearRetainingCapacity();
582 }
583 }
584}
585
586test "std.zon stringify utf8 codepoints" {
587 var aw: Writer.Allocating = .init(std.testing.allocator);
588 var s: Serializer = .{ .writer = &aw.writer };
589 defer aw.deinit();
590
591 // Printable ASCII
592 try s.int('a');
593 try std.testing.expectEqualStrings("97", aw.written());
594 aw.clearRetainingCapacity();
595
596 try s.codePoint('a', .{});
597 try std.testing.expectEqualStrings("'a'", aw.written());
598 aw.clearRetainingCapacity();
599
600 try s.value('a', .{ .emit_codepoint_literals = .always });
601 try std.testing.expectEqualStrings("'a'", aw.written());
602 aw.clearRetainingCapacity();
603
604 try s.value('a', .{ .emit_codepoint_literals = .printable_ascii });
605 try std.testing.expectEqualStrings("'a'", aw.written());
606 aw.clearRetainingCapacity();
607
608 try s.value('a', .{ .emit_codepoint_literals = .never });
609 try std.testing.expectEqualStrings("97", aw.written());
610 aw.clearRetainingCapacity();
611
612 // Short escaped codepoint
613 try s.int('\n');
614 try std.testing.expectEqualStrings("10", aw.written());
615 aw.clearRetainingCapacity();
616
617 try s.codePoint('\n', .{});
618 try std.testing.expectEqualStrings("'\\n'", aw.written());
619 aw.clearRetainingCapacity();
620
621 try s.value('\n', .{ .emit_codepoint_literals = .always });
622 try std.testing.expectEqualStrings("'\\n'", aw.written());
623 aw.clearRetainingCapacity();
624
625 try s.value('\n', .{ .emit_codepoint_literals = .printable_ascii });
626 try std.testing.expectEqualStrings("10", aw.written());
627 aw.clearRetainingCapacity();
628
629 try s.value('\n', .{ .emit_codepoint_literals = .never });
630 try std.testing.expectEqualStrings("10", aw.written());
631 aw.clearRetainingCapacity();
632
633 // Large codepoint
634 try s.int('⚡');
635 try std.testing.expectEqualStrings("9889", aw.written());
636 aw.clearRetainingCapacity();
637
638 try s.codePoint('⚡', .{ .escape_non_ascii = true });
639 try std.testing.expectEqualStrings("'\\u{26a1}'", aw.written());
640 aw.clearRetainingCapacity();
641
642 try s.value('⚡', .{ .emit_codepoint_literals = .always, .escape_non_ascii = true });
643 try std.testing.expectEqualStrings("'\\u{26a1}'", aw.written());
644 aw.clearRetainingCapacity();
645
646 try s.value('⚡', .{ .emit_codepoint_literals = .printable_ascii });
647 try std.testing.expectEqualStrings("9889", aw.written());
648 aw.clearRetainingCapacity();
649
650 try s.value('⚡', .{ .emit_codepoint_literals = .never });
651 try std.testing.expectEqualStrings("9889", aw.written());
652 aw.clearRetainingCapacity();
653
654 // Invalid codepoint
655 try std.testing.expectError(error.InvalidCodepoint, s.codePoint(0x110000 + 1, .{ .escape_non_ascii = true }));
656 aw.clearRetainingCapacity();
657
658 try s.int(0x110000 + 1);
659 try std.testing.expectEqualStrings("1114113", aw.written());
660 aw.clearRetainingCapacity();
661
662 try s.value(0x110000 + 1, .{ .emit_codepoint_literals = .always });
663 try std.testing.expectEqualStrings("1114113", aw.written());
664 aw.clearRetainingCapacity();
665
666 try s.value(0x110000 + 1, .{ .emit_codepoint_literals = .printable_ascii });
667 try std.testing.expectEqualStrings("1114113", aw.written());
668 aw.clearRetainingCapacity();
669
670 try s.value(0x110000 + 1, .{ .emit_codepoint_literals = .never });
671 try std.testing.expectEqualStrings("1114113", aw.written());
672 aw.clearRetainingCapacity();
673
674 // Valid codepoint, not a codepoint type
675 try s.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .always });
676 try std.testing.expectEqualStrings("97", aw.written());
677 aw.clearRetainingCapacity();
678
679 try s.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .printable_ascii });
680 try std.testing.expectEqualStrings("97", aw.written());
681 aw.clearRetainingCapacity();
682
683 try s.value(@as(i32, 'a'), .{ .emit_codepoint_literals = .never });
684 try std.testing.expectEqualStrings("97", aw.written());
685 aw.clearRetainingCapacity();
686
687 // Make sure value options are passed to children
688 try s.value(.{ .c = '⚡' }, .{ .emit_codepoint_literals = .always, .escape_non_ascii = true });
689 try std.testing.expectEqualStrings(".{ .c = '\\u{26a1}' }", aw.written());
690 aw.clearRetainingCapacity();
691
692 try s.value(.{ .c = '⚡' }, .{ .emit_codepoint_literals = .never });
693 try std.testing.expectEqualStrings(".{ .c = 9889 }", aw.written());
694 aw.clearRetainingCapacity();
695}
696
697test "std.zon stringify strings" {
698 var aw: Writer.Allocating = .init(std.testing.allocator);
699 var s: Serializer = .{ .writer = &aw.writer };
700 defer aw.deinit();
701
702 // Minimal case
703 try s.string("abc⚡ÿ\n", .{ .escape_non_ascii = true });
704 try std.testing.expectEqualStrings("\"abc\\u{26a1}\\u{ff}\\n\"", aw.written());
705 aw.clearRetainingCapacity();
706
707 try s.tuple("abc⚡ÿ\n", .{});
708 try std.testing.expectEqualStrings(
709 \\.{
710 \\ 97,
711 \\ 98,
712 \\ 99,
713 \\ 226,
714 \\ 154,
715 \\ 161,
716 \\ 195,
717 \\ 191,
718 \\ 10,
719 \\}
720 , aw.written());
721 aw.clearRetainingCapacity();
722
723 try s.value("abc⚡ÿ\n", .{ .escape_non_ascii = false });
724 try std.testing.expectEqualStrings("\"abc⚡ÿ\\n\"", aw.written());
725 aw.clearRetainingCapacity();
726
727 try s.value("abc⚡ÿ\n", .{ .emit_strings_as_containers = true });
728 try std.testing.expectEqualStrings(
729 \\.{
730 \\ 97,
731 \\ 98,
732 \\ 99,
733 \\ 226,
734 \\ 154,
735 \\ 161,
736 \\ 195,
737 \\ 191,
738 \\ 10,
739 \\}
740 , aw.written());
741 aw.clearRetainingCapacity();
742
743 // Value options are inherited by children
744 try s.value(.{ .str = "abc" }, .{});
745 try std.testing.expectEqualStrings(".{ .str = \"abc\" }", aw.written());
746 aw.clearRetainingCapacity();
747
748 try s.value(.{ .str = "abc" }, .{ .emit_strings_as_containers = true });
749 try std.testing.expectEqualStrings(
750 \\.{ .str = .{
751 \\ 97,
752 \\ 98,
753 \\ 99,
754 \\} }
755 , aw.written());
756 aw.clearRetainingCapacity();
757
758 // Arrays (rather than pointers to arrays) of u8s are not considered strings, so that data can
759 // round trip correctly.
760 try s.value("abc".*, .{});
761 try std.testing.expectEqualStrings(
762 \\.{
763 \\ 97,
764 \\ 98,
765 \\ 99,
766 \\}
767 , aw.written());
768 aw.clearRetainingCapacity();
769}
770
771test "std.zon stringify multiline strings" {
772 var aw: Writer.Allocating = .init(std.testing.allocator);
773 var s: Serializer = .{ .writer = &aw.writer };
774 defer aw.deinit();
775
776 inline for (.{ true, false }) |whitespace| {
777 s.options.whitespace = whitespace;
778
779 {
780 try s.multilineString("", .{ .top_level = true });
781 try std.testing.expectEqualStrings("\\\\", aw.written());
782 aw.clearRetainingCapacity();
783 }
784
785 {
786 try s.multilineString("abc⚡", .{ .top_level = true });
787 try std.testing.expectEqualStrings("\\\\abc⚡", aw.written());
788 aw.clearRetainingCapacity();
789 }
790
791 {
792 try s.multilineString("abc⚡\ndef", .{ .top_level = true });
793 try std.testing.expectEqualStrings("\\\\abc⚡\n\\\\def", aw.written());
794 aw.clearRetainingCapacity();
795 }
796
797 {
798 try s.multilineString("abc⚡\r\ndef", .{ .top_level = true });
799 try std.testing.expectEqualStrings("\\\\abc⚡\n\\\\def", aw.written());
800 aw.clearRetainingCapacity();
801 }
802
803 {
804 try s.multilineString("\nabc⚡", .{ .top_level = true });
805 try std.testing.expectEqualStrings("\\\\\n\\\\abc⚡", aw.written());
806 aw.clearRetainingCapacity();
807 }
808
809 {
810 try s.multilineString("\r\nabc⚡", .{ .top_level = true });
811 try std.testing.expectEqualStrings("\\\\\n\\\\abc⚡", aw.written());
812 aw.clearRetainingCapacity();
813 }
814
815 {
816 try s.multilineString("abc\ndef", .{});
817 if (whitespace) {
818 try std.testing.expectEqualStrings("\n\\\\abc\n\\\\def\n", aw.written());
819 } else {
820 try std.testing.expectEqualStrings("\\\\abc\n\\\\def\n", aw.written());
821 }
822 aw.clearRetainingCapacity();
823 }
824
825 {
826 const str: []const u8 = &.{ 'a', '\r', 'c' };
827 try s.string(str, .{ .escape_non_ascii = false });
828 try std.testing.expectEqualStrings("\"a\\rc\"", aw.written());
829 aw.clearRetainingCapacity();
830 }
831
832 {
833 try std.testing.expectError(
834 error.InnerCarriageReturn,
835 s.multilineString(@as([]const u8, &.{ 'a', '\r', 'c' }), .{}),
836 );
837 try std.testing.expectError(
838 error.InnerCarriageReturn,
839 s.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\n' }), .{}),
840 );
841 try std.testing.expectError(
842 error.InnerCarriageReturn,
843 s.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\r', '\n' }), .{}),
844 );
845 try std.testing.expectEqualStrings("", aw.written());
846 aw.clearRetainingCapacity();
847 }
848 }
849}
850
851test "std.zon stringify skip default fields" {
852 const Struct = struct {
853 x: i32 = 2,
854 y: i8,
855 z: u32 = 4,
856 inner1: struct { a: u8 = 'z', b: u8 = 'y', c: u8 } = .{
857 .a = '1',
858 .b = '2',
859 .c = '3',
860 },
861 inner2: struct { u8, u8, u8 } = .{
862 'a',
863 'b',
864 'c',
865 },
866 inner3: struct { u8, u8, u8 } = .{
867 'a',
868 'b',
869 'c',
870 },
871 };
872
873 // Not skipping if not set
874 try expectSerializeEqual(
875 \\.{
876 \\ .x = 2,
877 \\ .y = 3,
878 \\ .z = 4,
879 \\ .inner1 = .{
880 \\ .a = '1',
881 \\ .b = '2',
882 \\ .c = '3',
883 \\ },
884 \\ .inner2 = .{
885 \\ 'a',
886 \\ 'b',
887 \\ 'c',
888 \\ },
889 \\ .inner3 = .{
890 \\ 'a',
891 \\ 'b',
892 \\ 'd',
893 \\ },
894 \\}
895 ,
896 Struct{
897 .y = 3,
898 .z = 4,
899 .inner1 = .{
900 .a = '1',
901 .b = '2',
902 .c = '3',
903 },
904 .inner3 = .{
905 'a',
906 'b',
907 'd',
908 },
909 },
910 .{ .emit_codepoint_literals = .always },
911 );
912
913 // Top level defaults
914 try expectSerializeEqual(
915 \\.{ .y = 3, .inner3 = .{
916 \\ 'a',
917 \\ 'b',
918 \\ 'd',
919 \\} }
920 ,
921 Struct{
922 .y = 3,
923 .z = 4,
924 .inner1 = .{
925 .a = '1',
926 .b = '2',
927 .c = '3',
928 },
929 .inner3 = .{
930 'a',
931 'b',
932 'd',
933 },
934 },
935 .{
936 .emit_default_optional_fields = false,
937 .emit_codepoint_literals = .always,
938 },
939 );
940
941 // Inner types having defaults, and defaults changing the number of fields affecting the
942 // formatting
943 try expectSerializeEqual(
944 \\.{
945 \\ .y = 3,
946 \\ .inner1 = .{ .b = '2', .c = '3' },
947 \\ .inner3 = .{
948 \\ 'a',
949 \\ 'b',
950 \\ 'd',
951 \\ },
952 \\}
953 ,
954 Struct{
955 .y = 3,
956 .z = 4,
957 .inner1 = .{
958 .a = 'z',
959 .b = '2',
960 .c = '3',
961 },
962 .inner3 = .{
963 'a',
964 'b',
965 'd',
966 },
967 },
968 .{
969 .emit_default_optional_fields = false,
970 .emit_codepoint_literals = .always,
971 },
972 );
973
974 const DefaultStrings = struct {
975 foo: []const u8 = "abc",
976 };
977 try expectSerializeEqual(
978 \\.{}
979 ,
980 DefaultStrings{ .foo = "abc" },
981 .{ .emit_default_optional_fields = false },
982 );
983 try expectSerializeEqual(
984 \\.{ .foo = "abcd" }
985 ,
986 DefaultStrings{ .foo = "abcd" },
987 .{ .emit_default_optional_fields = false },
988 );
989}
990
991test "std.zon depth limits" {
992 var aw: Writer.Allocating = .init(std.testing.allocator);
993 const bw = &aw.writer;
994 defer aw.deinit();
995
996 const Recurse = struct { r: []const @This() };
997
998 // Normal operation
999 try serializeMaxDepth(.{ 1, .{ 2, 3 } }, .{}, bw, 16);
1000 try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", aw.written());
1001 aw.clearRetainingCapacity();
1002
1003 try serializeArbitraryDepth(.{ 1, .{ 2, 3 } }, .{}, bw);
1004 try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", aw.written());
1005 aw.clearRetainingCapacity();
1006
1007 // Max depth failing on non recursive type
1008 try std.testing.expectError(
1009 error.ExceededMaxDepth,
1010 serializeMaxDepth(.{ 1, .{ 2, .{ 3, 4 } } }, .{}, bw, 3),
1011 );
1012 try std.testing.expectEqualStrings("", aw.written());
1013 aw.clearRetainingCapacity();
1014
1015 // Max depth passing on recursive type
1016 {
1017 const maybe_recurse = Recurse{ .r = &.{} };
1018 try serializeMaxDepth(maybe_recurse, .{}, bw, 2);
1019 try std.testing.expectEqualStrings(".{ .r = .{} }", aw.written());
1020 aw.clearRetainingCapacity();
1021 }
1022
1023 // Unchecked passing on recursive type
1024 {
1025 const maybe_recurse = Recurse{ .r = &.{} };
1026 try serializeArbitraryDepth(maybe_recurse, .{}, bw);
1027 try std.testing.expectEqualStrings(".{ .r = .{} }", aw.written());
1028 aw.clearRetainingCapacity();
1029 }
1030
1031 // Max depth failing on recursive type due to depth
1032 {
1033 var maybe_recurse = Recurse{ .r = &.{} };
1034 maybe_recurse.r = &.{.{ .r = &.{} }};
1035 try std.testing.expectError(
1036 error.ExceededMaxDepth,
1037 serializeMaxDepth(maybe_recurse, .{}, bw, 2),
1038 );
1039 try std.testing.expectEqualStrings("", aw.written());
1040 aw.clearRetainingCapacity();
1041 }
1042
1043 // Same but for a slice
1044 {
1045 var temp: [1]Recurse = .{.{ .r = &.{} }};
1046 const maybe_recurse: []const Recurse = &temp;
1047
1048 try std.testing.expectError(
1049 error.ExceededMaxDepth,
1050 serializeMaxDepth(maybe_recurse, .{}, bw, 2),
1051 );
1052 try std.testing.expectEqualStrings("", aw.written());
1053 aw.clearRetainingCapacity();
1054
1055 var s: Serializer = .{ .writer = bw };
1056
1057 try std.testing.expectError(
1058 error.ExceededMaxDepth,
1059 s.tupleMaxDepth(maybe_recurse, .{}, 2),
1060 );
1061 try std.testing.expectEqualStrings("", aw.written());
1062 aw.clearRetainingCapacity();
1063
1064 try s.tupleArbitraryDepth(maybe_recurse, .{});
1065 try std.testing.expectEqualStrings(".{.{ .r = .{} }}", aw.written());
1066 aw.clearRetainingCapacity();
1067 }
1068
1069 // A slice succeeding
1070 {
1071 var temp: [1]Recurse = .{.{ .r = &.{} }};
1072 const maybe_recurse: []const Recurse = &temp;
1073
1074 try serializeMaxDepth(maybe_recurse, .{}, bw, 3);
1075 try std.testing.expectEqualStrings(".{.{ .r = .{} }}", aw.written());
1076 aw.clearRetainingCapacity();
1077
1078 var s: Serializer = .{ .writer = bw };
1079
1080 try s.tupleMaxDepth(maybe_recurse, .{}, 3);
1081 try std.testing.expectEqualStrings(".{.{ .r = .{} }}", aw.written());
1082 aw.clearRetainingCapacity();
1083
1084 try s.tupleArbitraryDepth(maybe_recurse, .{});
1085 try std.testing.expectEqualStrings(".{.{ .r = .{} }}", aw.written());
1086 aw.clearRetainingCapacity();
1087 }
1088
1089 // Max depth failing on recursive type due to recursion
1090 {
1091 var temp: [1]Recurse = .{.{ .r = &.{} }};
1092 temp[0].r = &temp;
1093 const maybe_recurse: []const Recurse = &temp;
1094
1095 try std.testing.expectError(
1096 error.ExceededMaxDepth,
1097 serializeMaxDepth(maybe_recurse, .{}, bw, 128),
1098 );
1099 try std.testing.expectEqualStrings("", aw.written());
1100 aw.clearRetainingCapacity();
1101
1102 var s: Serializer = .{ .writer = bw };
1103 try std.testing.expectError(
1104 error.ExceededMaxDepth,
1105 s.tupleMaxDepth(maybe_recurse, .{}, 128),
1106 );
1107 try std.testing.expectEqualStrings("", aw.written());
1108 aw.clearRetainingCapacity();
1109 }
1110
1111 // Max depth on other parts of the lower level API
1112 {
1113 var s: Serializer = .{ .writer = bw };
1114
1115 const maybe_recurse: []const Recurse = &.{};
1116
1117 try std.testing.expectError(error.ExceededMaxDepth, s.valueMaxDepth(1, .{}, 0));
1118 try s.valueMaxDepth(2, .{}, 1);
1119 try s.value(3, .{});
1120 try s.valueArbitraryDepth(maybe_recurse, .{});
1121
1122 var wip_struct = try s.beginStruct(.{});
1123 try std.testing.expectError(error.ExceededMaxDepth, wip_struct.fieldMaxDepth("a", 1, .{}, 0));
1124 try wip_struct.fieldMaxDepth("b", 4, .{}, 1);
1125 try wip_struct.field("c", 5, .{});
1126 try wip_struct.fieldArbitraryDepth("d", maybe_recurse, .{});
1127 try wip_struct.end();
1128
1129 var t = try s.beginTuple(.{});
1130 try std.testing.expectError(error.ExceededMaxDepth, t.fieldMaxDepth(1, .{}, 0));
1131 try t.fieldMaxDepth(6, .{}, 1);
1132 try t.field(7, .{});
1133 try t.fieldArbitraryDepth(maybe_recurse, .{});
1134 try t.end();
1135
1136 var a = try s.beginTuple(.{});
1137 try std.testing.expectError(error.ExceededMaxDepth, a.fieldMaxDepth(1, .{}, 0));
1138 try a.fieldMaxDepth(8, .{}, 1);
1139 try a.field(9, .{});
1140 try a.fieldArbitraryDepth(maybe_recurse, .{});
1141 try a.end();
1142
1143 try std.testing.expectEqualStrings(
1144 \\23.{}.{
1145 \\ .b = 4,
1146 \\ .c = 5,
1147 \\ .d = .{},
1148 \\}.{
1149 \\ 6,
1150 \\ 7,
1151 \\ .{},
1152 \\}.{
1153 \\ 8,
1154 \\ 9,
1155 \\ .{},
1156 \\}
1157 , aw.written());
1158 }
1159}
1160
1161test "std.zon stringify primitives" {
1162 try expectSerializeEqual(
1163 \\.{
1164 \\ .a = 1.5,
1165 \\ .b = 0.3333333333333333333333333333333333,
1166 \\ .c = 3.1415926535897932384626433832795028,
1167 \\ .d = 0,
1168 \\ .e = 0,
1169 \\ .f = -0.0,
1170 \\ .g = inf,
1171 \\ .h = -inf,
1172 \\ .i = nan,
1173 \\}
1174 ,
1175 .{
1176 .a = @as(f128, 1.5), // Make sure explicit f128s work
1177 .b = 1.0 / 3.0,
1178 .c = std.math.pi,
1179 .d = 0.0,
1180 .e = -0.0,
1181 .f = @as(f128, -0.0),
1182 .g = std.math.inf(f32),
1183 .h = -std.math.inf(f32),
1184 .i = std.math.nan(f32),
1185 },
1186 .{},
1187 );
1188
1189 try expectSerializeEqual(
1190 \\.{
1191 \\ .a = 18446744073709551616,
1192 \\ .b = -18446744073709551616,
1193 \\ .c = 680564733841876926926749214863536422912,
1194 \\ .d = -680564733841876926926749214863536422912,
1195 \\ .e = 0,
1196 \\}
1197 ,
1198 .{
1199 .a = 18446744073709551616,
1200 .b = -18446744073709551616,
1201 .c = 680564733841876926926749214863536422912,
1202 .d = -680564733841876926926749214863536422912,
1203 .e = 0,
1204 },
1205 .{},
1206 );
1207
1208 try expectSerializeEqual(
1209 \\.{
1210 \\ .a = true,
1211 \\ .b = false,
1212 \\ .c = .foo,
1213 \\ .e = null,
1214 \\}
1215 ,
1216 .{
1217 .a = true,
1218 .b = false,
1219 .c = .foo,
1220 .e = null,
1221 },
1222 .{},
1223 );
1224
1225 const Struct = struct { x: f32, y: f32 };
1226 try expectSerializeEqual(
1227 ".{ .a = .{ .x = 1, .y = 2 }, .b = null }",
1228 .{
1229 .a = @as(?Struct, .{ .x = 1, .y = 2 }),
1230 .b = @as(?Struct, null),
1231 },
1232 .{},
1233 );
1234
1235 const E = enum(u8) {
1236 foo,
1237 bar,
1238 };
1239 try expectSerializeEqual(
1240 ".{ .a = .foo, .b = .foo }",
1241 .{
1242 .a = .foo,
1243 .b = E.foo,
1244 },
1245 .{},
1246 );
1247}
1248
1249test "std.zon stringify ident" {
1250 var aw: Writer.Allocating = .init(std.testing.allocator);
1251 var s: Serializer = .{ .writer = &aw.writer };
1252 defer aw.deinit();
1253
1254 try expectSerializeEqual(".{ .a = 0 }", .{ .a = 0 }, .{});
1255 try s.ident("a");
1256 try std.testing.expectEqualStrings(".a", aw.written());
1257 aw.clearRetainingCapacity();
1258
1259 try s.ident("foo_1");
1260 try std.testing.expectEqualStrings(".foo_1", aw.written());
1261 aw.clearRetainingCapacity();
1262
1263 try s.ident("_foo_1");
1264 try std.testing.expectEqualStrings("._foo_1", aw.written());
1265 aw.clearRetainingCapacity();
1266
1267 try s.ident("foo bar");
1268 try std.testing.expectEqualStrings(".@\"foo bar\"", aw.written());
1269 aw.clearRetainingCapacity();
1270
1271 try s.ident("1foo");
1272 try std.testing.expectEqualStrings(".@\"1foo\"", aw.written());
1273 aw.clearRetainingCapacity();
1274
1275 try s.ident("var");
1276 try std.testing.expectEqualStrings(".@\"var\"", aw.written());
1277 aw.clearRetainingCapacity();
1278
1279 try s.ident("true");
1280 try std.testing.expectEqualStrings(".true", aw.written());
1281 aw.clearRetainingCapacity();
1282
1283 try s.ident("_");
1284 try std.testing.expectEqualStrings("._", aw.written());
1285 aw.clearRetainingCapacity();
1286
1287 const Enum = enum {
1288 @"foo bar",
1289 };
1290 try expectSerializeEqual(".{ .@\"var\" = .@\"foo bar\", .@\"1\" = .@\"foo bar\" }", .{
1291 .@"var" = .@"foo bar",
1292 .@"1" = Enum.@"foo bar",
1293 }, .{});
1294}
1295
1296test "std.zon stringify as tuple" {
1297 var aw: Writer.Allocating = .init(std.testing.allocator);
1298 var s: Serializer = .{ .writer = &aw.writer };
1299 defer aw.deinit();
1300
1301 // Tuples
1302 try s.tuple(.{ 1, 2 }, .{});
1303 try std.testing.expectEqualStrings(".{ 1, 2 }", aw.written());
1304 aw.clearRetainingCapacity();
1305
1306 // Slice
1307 try s.tuple(@as([]const u8, &.{ 1, 2 }), .{});
1308 try std.testing.expectEqualStrings(".{ 1, 2 }", aw.written());
1309 aw.clearRetainingCapacity();
1310
1311 // Array
1312 try s.tuple([2]u8{ 1, 2 }, .{});
1313 try std.testing.expectEqualStrings(".{ 1, 2 }", aw.written());
1314 aw.clearRetainingCapacity();
1315}
1316
1317test "std.zon stringify as float" {
1318 var aw: Writer.Allocating = .init(std.testing.allocator);
1319 var s: Serializer = .{ .writer = &aw.writer };
1320 defer aw.deinit();
1321
1322 // Comptime float
1323 try s.float(2.5);
1324 try std.testing.expectEqualStrings("2.5", aw.written());
1325 aw.clearRetainingCapacity();
1326
1327 // Sized float
1328 try s.float(@as(f32, 2.5));
1329 try std.testing.expectEqualStrings("2.5", aw.written());
1330 aw.clearRetainingCapacity();
1331}
1332
1333test "std.zon stringify vector" {
1334 try expectSerializeEqual(
1335 \\.{
1336 \\ .{},
1337 \\ .{
1338 \\ true,
1339 \\ false,
1340 \\ true,
1341 \\ },
1342 \\ .{},
1343 \\ .{
1344 \\ 1.5,
1345 \\ 2.5,
1346 \\ 3.5,
1347 \\ },
1348 \\ .{},
1349 \\ .{
1350 \\ 2,
1351 \\ 4,
1352 \\ 6,
1353 \\ },
1354 \\ .{ 1, 2 },
1355 \\ .{
1356 \\ 3,
1357 \\ 4,
1358 \\ null,
1359 \\ },
1360 \\}
1361 ,
1362 .{
1363 @Vector(0, bool){},
1364 @Vector(3, bool){ true, false, true },
1365 @Vector(0, f32){},
1366 @Vector(3, f32){ 1.5, 2.5, 3.5 },
1367 @Vector(0, u8){},
1368 @Vector(3, u8){ 2, 4, 6 },
1369 @Vector(2, *const u8){ &1, &2 },
1370 @Vector(3, ?*const u8){ &3, &4, null },
1371 },
1372 .{},
1373 );
1374}
1375
1376test "std.zon pointers" {
1377 // Primitive with varying levels of pointers
1378 try expectSerializeEqual("10", &@as(u32, 10), .{});
1379 try expectSerializeEqual("10", &&@as(u32, 10), .{});
1380 try expectSerializeEqual("10", &&&@as(u32, 10), .{});
1381
1382 // Primitive optional with varying levels of pointers
1383 try expectSerializeEqual("10", @as(?*const u32, &10), .{});
1384 try expectSerializeEqual("null", @as(?*const u32, null), .{});
1385 try expectSerializeEqual("10", @as(?*const u32, &10), .{});
1386 try expectSerializeEqual("null", @as(*const ?u32, &null), .{});
1387
1388 try expectSerializeEqual("10", @as(?*const *const u32, &&10), .{});
1389 try expectSerializeEqual("null", @as(?*const *const u32, null), .{});
1390 try expectSerializeEqual("10", @as(*const ?*const u32, &&10), .{});
1391 try expectSerializeEqual("null", @as(*const ?*const u32, &null), .{});
1392 try expectSerializeEqual("10", @as(*const *const ?u32, &&10), .{});
1393 try expectSerializeEqual("null", @as(*const *const ?u32, &&null), .{});
1394
1395 try expectSerializeEqual(".{ 1, 2 }", &[2]u32{ 1, 2 }, .{});
1396
1397 // A complicated type with nested internal pointers and string allocations
1398 {
1399 const Inner = struct {
1400 f1: *const ?*const []const u8,
1401 f2: *const ?*const []const u8,
1402 };
1403 const Outer = struct {
1404 f1: *const ?*const Inner,
1405 f2: *const ?*const Inner,
1406 };
1407 const val: ?*const Outer = &.{
1408 .f1 = &&.{
1409 .f1 = &null,
1410 .f2 = &&"foo",
1411 },
1412 .f2 = &null,
1413 };
1414
1415 try expectSerializeEqual(
1416 \\.{ .f1 = .{ .f1 = null, .f2 = "foo" }, .f2 = null }
1417 , val, .{});
1418 }
1419}
1420
1421test "std.zon tuple/struct field" {
1422 var aw: Writer.Allocating = .init(std.testing.allocator);
1423 var s: Serializer = .{ .writer = &aw.writer };
1424 defer aw.deinit();
1425
1426 // Test on structs
1427 {
1428 var root = try s.beginStruct(.{});
1429 {
1430 var tuple = try root.beginTupleField("foo", .{});
1431 try tuple.field(0, .{});
1432 try tuple.field(1, .{});
1433 try tuple.end();
1434 }
1435 {
1436 var strct = try root.beginStructField("bar", .{});
1437 try strct.field("a", 0, .{});
1438 try strct.field("b", 1, .{});
1439 try strct.end();
1440 }
1441 try root.end();
1442
1443 try std.testing.expectEqualStrings(
1444 \\.{
1445 \\ .foo = .{
1446 \\ 0,
1447 \\ 1,
1448 \\ },
1449 \\ .bar = .{
1450 \\ .a = 0,
1451 \\ .b = 1,
1452 \\ },
1453 \\}
1454 , aw.written());
1455 aw.clearRetainingCapacity();
1456 }
1457
1458 // Test on tuples
1459 {
1460 var root = try s.beginTuple(.{});
1461 {
1462 var tuple = try root.beginTupleField(.{});
1463 try tuple.field(0, .{});
1464 try tuple.field(1, .{});
1465 try tuple.end();
1466 }
1467 {
1468 var strct = try root.beginStructField(.{});
1469 try strct.field("a", 0, .{});
1470 try strct.field("b", 1, .{});
1471 try strct.end();
1472 }
1473 try root.end();
1474
1475 try std.testing.expectEqualStrings(
1476 \\.{
1477 \\ .{
1478 \\ 0,
1479 \\ 1,
1480 \\ },
1481 \\ .{
1482 \\ .a = 0,
1483 \\ .b = 1,
1484 \\ },
1485 \\}
1486 , aw.written());
1487 aw.clearRetainingCapacity();
1488 }
1489}