authorgravatar for thejoshwolfe@gmail.comJosh Wolfe <thejoshwolfe@gmail.com> 2023-06-20 19:01:34-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-06-20 19:01:34-04:00
log0f2339f55b2fa45a8af94c26a7ceb8377e3acfef
tree6e7f3ab97ef1ddafb648f12ce60caec76a5e3cca
parentd2b256711944f75007199ea65141642511b21d8d
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

std: json.parseFromValue() (#15981)


3 files changed, 750 insertions(+), 58 deletions(-)

lib/std/json/dynamic_test.zig+49
......@@ -1,6 +1,8 @@
11const std = @import("std");
22const mem = std.mem;
33const testing = std.testing;
4const ArenaAllocator = std.heap.ArenaAllocator;
5const Allocator = std.mem.Allocator;
46
57const ObjectMap = @import("dynamic.zig").ObjectMap;
68const Array = @import("dynamic.zig").Array;
......@@ -9,6 +11,8 @@ const Value = @import("dynamic.zig").Value;
911const parseFromSlice = @import("static.zig").parseFromSlice;
1012const parseFromSliceLeaky = @import("static.zig").parseFromSliceLeaky;
1113const parseFromTokenSource = @import("static.zig").parseFromTokenSource;
14const parseFromValueLeaky = @import("static.zig").parseFromValueLeaky;
15const ParseOptions = @import("static.zig").ParseOptions;
1216
1317const jsonReader = @import("scanner.zig").reader;
1418
......@@ -240,3 +244,48 @@ test "Value.jsonStringify" {
240244 try testing.expectEqualSlices(u8, fbs.getWritten(), "{\"a\":\"b\"}");
241245 }
242246}
247
248test "polymorphic parsing" {
249 if (true) return error.SkipZigTest; // See https://github.com/ziglang/zig/issues/16108
250 const doc =
251 \\{ "type": "div",
252 \\ "color": "blue",
253 \\ "children": [
254 \\ { "type": "button",
255 \\ "caption": "OK" },
256 \\ { "type": "button",
257 \\ "caption": "Cancel" } ] }
258 ;
259 const Node = union(enum) {
260 div: Div,
261 button: Button,
262 const Self = @This();
263 const Div = struct {
264 color: enum { red, blue },
265 children: []Self,
266 };
267 const Button = struct {
268 caption: []const u8,
269 };
270
271 pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() {
272 if (source != .object) return error.UnexpectedToken;
273 const type_value = source.object.get("type") orelse return error.UnexpectedToken; // Missing "type" field.
274 if (type_value != .string) return error.UnexpectedToken; // "type" expected to be string.
275 const type_str = type_value.string;
276 var child_options = options;
277 child_options.ignore_unknown_fields = true;
278 if (std.mem.eql(u8, type_str, "div")) return .{ .div = try parseFromValueLeaky(Div, allocator, source, child_options) };
279 if (std.mem.eql(u8, type_str, "button")) return .{ .button = try parseFromValueLeaky(Button, allocator, source, child_options) };
280 return error.UnexpectedToken; // unknown type.
281 }
282 };
283
284 var arena = ArenaAllocator.init(testing.allocator);
285 defer arena.deinit();
286 const dynamic_tree = try parseFromSliceLeaky(Value, arena.allocator(), doc, .{});
287 const tree = try parseFromValueLeaky(Node, arena.allocator(), dynamic_tree, .{});
288
289 try testing.expect(tree.div.color == .blue);
290 try testing.expectEqualStrings("Cancel", tree.div.children[1].button.caption);
291}
lib/std/json/static.zig+342-58
......@@ -10,21 +10,29 @@ const AllocWhen = @import("./scanner.zig").AllocWhen;
1010const default_max_value_len = @import("./scanner.zig").default_max_value_len;
1111const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger;
1212
13const Value = @import("./dynamic.zig").Value;
14const Array = @import("./dynamic.zig").Array;
15
16/// Controls how to deal with various inconsistencies between the JSON document and the Zig struct type passed in.
17/// For duplicate fields or unknown fields, set options in this struct.
18/// For missing fields, give the Zig struct fields default values.
1319pub const ParseOptions = struct {
1420 /// Behaviour when a duplicate field is encountered.
21 /// The default is to return `error.DuplicateField`.
1522 duplicate_field_behavior: enum {
1623 use_first,
1724 @"error",
1825 use_last,
1926 } = .@"error",
2027
21 /// If false, finding an unknown field returns an error.
28 /// If false, finding an unknown field returns `error.UnknownField`.
2229 ignore_unknown_fields: bool = false,
2330
24 /// Passed to json.Scanner.nextAllocMax() or json.Reader.nextAllocMax().
25 /// The default for parseFromSlice() or parseFromTokenSource() with a *json.Scanner input
26 /// is the length of the input slice, which means error.ValueTooLong will never be returned.
27 /// The default for parseFromTokenSource() with a *json.Reader is default_max_value_len.
31 /// Passed to `std.json.Scanner.nextAllocMax` or `std.json.Reader.nextAllocMax`.
32 /// The default for `parseFromSlice` or `parseFromTokenSource` with a `*std.json.Scanner` input
33 /// is the length of the input slice, which means `error.ValueTooLong` will never be returned.
34 /// The default for `parseFromTokenSource` with a `*std.json.Reader` is `std.json.default_max_value_len`.
35 /// Ignored for `parseFromValue` and `parseFromValueLeaky`.
2836 max_value_len: ?usize = null,
2937};
3038
......@@ -43,6 +51,7 @@ pub fn Parsed(comptime T: type) type {
4351
4452/// Parses the json document from `s` and returns the result packaged in a `std.json.Parsed`.
4553/// You must call `deinit()` of the returned object to clean up allocated resources.
54/// If you are using a `std.heap.ArenaAllocator` or similar, consider calling `parseFromSliceLeaky` instead.
4655/// Note that `error.BufferUnderrun` is not actually possible to return from this function.
4756pub fn parseFromSlice(
4857 comptime T: type,
......@@ -114,33 +123,65 @@ pub fn parseFromTokenSourceLeaky(
114123 }
115124 }
116125
117 const value = try parseInternal(T, allocator, scanner_or_reader, resolved_options);
126 const value = try internalParse(T, allocator, scanner_or_reader, resolved_options);
118127
119128 assert(.end_of_document == try scanner_or_reader.next());
120129
121130 return value;
122131}
123132
133/// Like `parseFromSlice`, but the input is an already-parsed `std.json.Value` object.
134pub fn parseFromValue(
135 comptime T: type,
136 allocator: Allocator,
137 source: Value,
138 options: ParseOptions,
139) ParseFromValueError!Parsed(T) {
140 var parsed = Parsed(T){
141 .arena = try allocator.create(ArenaAllocator),
142 .value = undefined,
143 };
144 errdefer allocator.destroy(parsed.arena);
145 parsed.arena.* = ArenaAllocator.init(allocator);
146 errdefer parsed.arena.deinit();
147
148 parsed.value = try parseFromValueLeaky(T, parsed.arena.allocator(), source, options);
149
150 return parsed;
151}
152
153pub fn parseFromValueLeaky(
154 comptime T: type,
155 allocator: Allocator,
156 source: Value,
157 options: ParseOptions,
158) ParseFromValueError!T {
159 // I guess this function doesn't need to exist,
160 // but the flow of the sourcecode is easy to follow and grouped nicely with
161 // this pub redirect function near the top and the implementation near the bottom.
162 return internalParseFromValue(T, allocator, source, options);
163}
164
124165/// The error set that will be returned when parsing from `*Source`.
125166/// Note that this may contain `error.BufferUnderrun`, but that error will never actually be returned.
126167pub fn ParseError(comptime Source: type) type {
127168 // A few of these will either always be present or present enough of the time that
128169 // omitting them is more confusing than always including them.
129 return error{
130 UnexpectedToken,
131 InvalidNumber,
132 Overflow,
133 InvalidEnumTag,
134 DuplicateField,
135 UnknownField,
136 MissingField,
137 LengthMismatch,
138 } ||
139 std.fmt.ParseIntError || std.fmt.ParseFloatError ||
140 Source.NextError || Source.PeekError || Source.AllocError;
170 return ParseFromValueError || Source.NextError || Source.PeekError || Source.AllocError;
141171}
142172
143fn parseInternal(
173pub const ParseFromValueError = std.fmt.ParseIntError || std.fmt.ParseFloatError || Allocator.Error || error{
174 UnexpectedToken,
175 InvalidNumber,
176 Overflow,
177 InvalidEnumTag,
178 DuplicateField,
179 UnknownField,
180 MissingField,
181 LengthMismatch,
182};
183
184fn internalParse(
144185 comptime T: type,
145186 allocator: Allocator,
146187 source: anytype,
......@@ -170,13 +211,7 @@ fn parseInternal(
170211 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,
171212 else => return error.UnexpectedToken,
172213 };
173 if (isNumberFormattedLikeAnInteger(slice))
174 return std.fmt.parseInt(T, slice, 10);
175 // Try to coerce a float to an integer.
176 const float = try std.fmt.parseFloat(f128, slice);
177 if (@round(float) != float) return error.InvalidNumber;
178 if (float > std.math.maxInt(T) or float < std.math.minInt(T)) return error.Overflow;
179 return @intFromFloat(T, float);
214 return sliceToInt(T, slice);
180215 },
181216 .Optional => |optionalInfo| {
182217 switch (try source.peekNextTokenType()) {
......@@ -185,11 +220,11 @@ fn parseInternal(
185220 return null;
186221 },
187222 else => {
188 return try parseInternal(optionalInfo.child, allocator, source, options);
223 return try internalParse(optionalInfo.child, allocator, source, options);
189224 },
190225 }
191226 },
192 .Enum => |enumInfo| {
227 .Enum => {
193228 if (comptime std.meta.trait.hasFn("jsonParse")(T)) {
194229 return T.jsonParse(allocator, source, options);
195230 }
......@@ -200,12 +235,7 @@ fn parseInternal(
200235 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,
201236 else => return error.UnexpectedToken,
202237 };
203 // Check for a named value.
204 if (std.meta.stringToEnum(T, slice)) |value| return value;
205 // Check for a numeric value.
206 if (!isNumberFormattedLikeAnInteger(slice)) return error.InvalidEnumTag;
207 const n = std.fmt.parseInt(enumInfo.tag_type, slice, 10) catch return error.InvalidEnumTag;
208 return try std.meta.intToEnum(T, n);
238 return sliceToEnum(T, slice);
209239 },
210240 .Union => |unionInfo| {
211241 if (comptime std.meta.trait.hasFn("jsonParse")(T)) {
......@@ -226,7 +256,7 @@ fn parseInternal(
226256 inline for (unionInfo.fields) |u_field| {
227257 if (std.mem.eql(u8, u_field.name, field_name)) {
228258 // Free the name token now in case we're using an allocator that optimizes freeing the last allocated object.
229 // (Recursing into parseInternal() might trigger more allocations.)
259 // (Recursing into internalParse() might trigger more allocations.)
230260 freeAllocated(allocator, name_token.?);
231261 name_token = null;
232262
......@@ -237,7 +267,7 @@ fn parseInternal(
237267 result = @unionInit(T, u_field.name, {});
238268 } else {
239269 // Recurse.
240 result = @unionInit(T, u_field.name, try parseInternal(u_field.type, allocator, source, options));
270 result = @unionInit(T, u_field.name, try internalParse(u_field.type, allocator, source, options));
241271 }
242272 break;
243273 }
......@@ -256,10 +286,8 @@ fn parseInternal(
256286 if (.array_begin != try source.next()) return error.UnexpectedToken;
257287
258288 var r: T = undefined;
259 var fields_seen: usize = 0;
260289 inline for (0..structInfo.fields.len) |i| {
261 r[i] = try parseInternal(structInfo.fields[i].type, allocator, source, options);
262 fields_seen = i + 1;
290 r[i] = try internalParse(structInfo.fields[i].type, allocator, source, options);
263291 }
264292
265293 if (.array_end != try source.next()) return error.UnexpectedToken;
......@@ -288,7 +316,7 @@ fn parseInternal(
288316 if (field.is_comptime) @compileError("comptime fields are not supported: " ++ @typeName(T) ++ "." ++ field.name);
289317 if (std.mem.eql(u8, field.name, field_name)) {
290318 // Free the name token now in case we're using an allocator that optimizes freeing the last allocated object.
291 // (Recursing into parseInternal() might trigger more allocations.)
319 // (Recursing into internalParse() might trigger more allocations.)
292320 freeAllocated(allocator, name_token.?);
293321 name_token = null;
294322
......@@ -297,14 +325,14 @@ fn parseInternal(
297325 .use_first => {
298326 // Parse and ignore the redundant value.
299327 // We don't want to skip the value, because we want type checking.
300 _ = try parseInternal(field.type, allocator, source, options);
328 _ = try internalParse(field.type, allocator, source, options);
301329 break;
302330 },
303331 .@"error" => return error.DuplicateField,
304332 .use_last => {},
305333 }
306334 }
307 @field(r, field.name) = try parseInternal(field.type, allocator, source, options);
335 @field(r, field.name) = try internalParse(field.type, allocator, source, options);
308336 fields_seen[i] = true;
309337 break;
310338 }
......@@ -318,16 +346,7 @@ fn parseInternal(
318346 }
319347 }
320348 }
321 inline for (structInfo.fields, 0..) |field, i| {
322 if (!fields_seen[i]) {
323 if (field.default_value) |default_ptr| {
324 const default = @ptrCast(*align(1) const field.type, default_ptr).*;
325 @field(r, field.name) = default;
326 } else {
327 return error.MissingField;
328 }
329 }
330 }
349 try fillDefaultStructValues(T, &r, &fields_seen);
331350 return r;
332351 },
333352
......@@ -335,7 +354,7 @@ fn parseInternal(
335354 switch (try source.peekNextTokenType()) {
336355 .array_begin => {
337356 // Typical array.
338 return parseInternalArray(T, arrayInfo.child, arrayInfo.len, allocator, source, options);
357 return internalParseArray(T, arrayInfo.child, arrayInfo.len, allocator, source, options);
339358 },
340359 .string => {
341360 if (arrayInfo.child != u8) return error.UnexpectedToken;
......@@ -389,7 +408,7 @@ fn parseInternal(
389408 .Vector => |vecInfo| {
390409 switch (try source.peekNextTokenType()) {
391410 .array_begin => {
392 return parseInternalArray(T, vecInfo.child, vecInfo.len, allocator, source, options);
411 return internalParseArray(T, vecInfo.child, vecInfo.len, allocator, source, options);
393412 },
394413 else => return error.UnexpectedToken,
395414 }
......@@ -399,7 +418,7 @@ fn parseInternal(
399418 switch (ptrInfo.size) {
400419 .One => {
401420 const r: *ptrInfo.child = try allocator.create(ptrInfo.child);
402 r.* = try parseInternal(ptrInfo.child, allocator, source, options);
421 r.* = try internalParse(ptrInfo.child, allocator, source, options);
403422 return r;
404423 },
405424 .Slice => {
......@@ -419,7 +438,7 @@ fn parseInternal(
419438 }
420439
421440 try arraylist.ensureUnusedCapacity(1);
422 arraylist.appendAssumeCapacity(try parseInternal(ptrInfo.child, allocator, source, options));
441 arraylist.appendAssumeCapacity(try internalParse(ptrInfo.child, allocator, source, options));
423442 }
424443
425444 if (ptrInfo.sentinel) |some| {
......@@ -463,7 +482,7 @@ fn parseInternal(
463482 unreachable;
464483}
465484
466fn parseInternalArray(
485fn internalParseArray(
467486 comptime T: type,
468487 comptime Child: type,
469488 comptime len: comptime_int,
......@@ -476,7 +495,7 @@ fn parseInternalArray(
476495 var r: T = undefined;
477496 var i: usize = 0;
478497 while (i < len) : (i += 1) {
479 r[i] = try parseInternal(Child, allocator, source, options);
498 r[i] = try internalParse(Child, allocator, source, options);
480499 }
481500
482501 if (.array_end != try source.next()) return error.UnexpectedToken;
......@@ -484,6 +503,271 @@ fn parseInternalArray(
484503 return r;
485504}
486505
506fn internalParseFromValue(
507 comptime T: type,
508 allocator: Allocator,
509 source: Value,
510 options: ParseOptions,
511) ParseFromValueError!T {
512 switch (@typeInfo(T)) {
513 .Bool => {
514 switch (source) {
515 .bool => |b| return b,
516 else => return error.UnexpectedToken,
517 }
518 },
519 .Float, .ComptimeFloat => {
520 switch (source) {
521 .float => |f| return @floatCast(T, f),
522 .integer => |i| return @floatFromInt(T, i),
523 .number_string, .string => |s| return std.fmt.parseFloat(T, s),
524 else => return error.UnexpectedToken,
525 }
526 },
527 .Int, .ComptimeInt => {
528 switch (source) {
529 .float => |f| {
530 if (@round(f) != f) return error.InvalidNumber;
531 if (f > std.math.maxInt(T)) return error.Overflow;
532 if (f < std.math.minInt(T)) return error.Overflow;
533 return @intFromFloat(T, f);
534 },
535 .integer => |i| {
536 if (i > std.math.maxInt(T)) return error.Overflow;
537 if (i < std.math.minInt(T)) return error.Overflow;
538 return @intCast(T, i);
539 },
540 .number_string, .string => |s| {
541 return sliceToInt(T, s);
542 },
543 else => return error.UnexpectedToken,
544 }
545 },
546 .Optional => |optionalInfo| {
547 switch (source) {
548 .null => return null,
549 else => return try internalParseFromValue(optionalInfo.child, allocator, source, options),
550 }
551 },
552 .Enum => {
553 if (comptime std.meta.trait.hasFn("jsonParseFromValue")(T)) {
554 return T.jsonParseFromValue(allocator, source, options);
555 }
556
557 switch (source) {
558 .float => return error.InvalidEnumTag,
559 .integer => |i| return std.meta.intToEnum(T, i),
560 .number_string, .string => |s| return sliceToEnum(T, s),
561 else => return error.UnexpectedToken,
562 }
563 },
564 .Union => |unionInfo| {
565 if (comptime std.meta.trait.hasFn("jsonParseFromValue")(T)) {
566 return T.jsonParseFromValue(allocator, source, options);
567 }
568
569 if (unionInfo.tag_type == null) @compileError("Unable to parse into untagged union '" ++ @typeName(T) ++ "'");
570
571 if (source != .object) return error.UnexpectedToken;
572 if (source.object.count() != 1) return error.UnexpectedToken;
573
574 var it = source.object.iterator();
575 const kv = it.next().?;
576 const field_name = kv.key_ptr.*;
577
578 inline for (unionInfo.fields) |u_field| {
579 if (std.mem.eql(u8, u_field.name, field_name)) {
580 if (u_field.type == void) {
581 // void isn't really a json type, but we can support void payload union tags with {} as a value.
582 if (kv.value_ptr.* != .object) return error.UnexpectedToken;
583 if (kv.value_ptr.*.object.count() != 0) return error.UnexpectedToken;
584 return @unionInit(T, u_field.name, {});
585 }
586 // Recurse.
587 return @unionInit(T, u_field.name, try internalParseFromValue(u_field.type, allocator, kv.value_ptr.*, options));
588 }
589 }
590 // Didn't match anything.
591 return error.UnknownField;
592 },
593
594 .Struct => |structInfo| {
595 if (structInfo.is_tuple) {
596 if (source != .array) return error.UnexpectedToken;
597 if (source.array.items.len != structInfo.fields.len) return error.UnexpectedToken;
598
599 var r: T = undefined;
600 inline for (0..structInfo.fields.len, source.array.items) |i, item| {
601 r[i] = try internalParseFromValue(structInfo.fields[i].type, allocator, item, options);
602 }
603
604 return r;
605 }
606
607 if (comptime std.meta.trait.hasFn("jsonParseFromValue")(T)) {
608 return T.jsonParseFromValue(allocator, source, options);
609 }
610
611 if (source != .object) return error.UnexpectedToken;
612
613 var r: T = undefined;
614 var fields_seen = [_]bool{false} ** structInfo.fields.len;
615
616 var it = source.object.iterator();
617 while (it.next()) |kv| {
618 const field_name = kv.key_ptr.*;
619
620 inline for (structInfo.fields, 0..) |field, i| {
621 if (field.is_comptime) @compileError("comptime fields are not supported: " ++ @typeName(T) ++ "." ++ field.name);
622 if (std.mem.eql(u8, field.name, field_name)) {
623 if (fields_seen[i]) {
624 switch (options.duplicate_field_behavior) {
625 .use_first => {
626 // Parse and ignore the redundant value.
627 // We don't want to skip the value, because we want type checking.
628 _ = try internalParseFromValue(field.type, allocator, kv.value_ptr.*, options);
629 break;
630 },
631 .@"error" => return error.DuplicateField,
632 .use_last => {},
633 }
634 }
635 @field(r, field.name) = try internalParseFromValue(field.type, allocator, kv.value_ptr.*, options);
636 fields_seen[i] = true;
637 break;
638 }
639 } else {
640 // Didn't match anything.
641 if (!options.ignore_unknown_fields) return error.UnknownField;
642 }
643 }
644 try fillDefaultStructValues(T, &r, &fields_seen);
645 return r;
646 },
647
648 .Array => |arrayInfo| {
649 switch (source) {
650 .array => |array| {
651 // Typical array.
652 return internalParseArrayFromArrayValue(T, arrayInfo.child, arrayInfo.len, allocator, array, options);
653 },
654 .string => |s| {
655 if (arrayInfo.child != u8) return error.UnexpectedToken;
656 // Fixed-length string.
657
658 if (s.len != arrayInfo.len) return error.LengthMismatch;
659
660 var r: T = undefined;
661 @memcpy(r[0..], s);
662 return r;
663 },
664
665 else => return error.UnexpectedToken,
666 }
667 },
668
669 .Vector => |vecInfo| {
670 switch (source) {
671 .array => |array| {
672 return internalParseArrayFromArrayValue(T, vecInfo.child, vecInfo.len, allocator, array, options);
673 },
674 else => return error.UnexpectedToken,
675 }
676 },
677
678 .Pointer => |ptrInfo| {
679 switch (ptrInfo.size) {
680 .One => {
681 const r: *ptrInfo.child = try allocator.create(ptrInfo.child);
682 r.* = try internalParseFromValue(ptrInfo.child, allocator, source, options);
683 return r;
684 },
685 .Slice => {
686 switch (source) {
687 .array => |array| {
688 const r = if (ptrInfo.sentinel) |sentinel_ptr|
689 try allocator.allocSentinel(ptrInfo.child, array.items.len, @ptrCast(*align(1) const ptrInfo.child, sentinel_ptr).*)
690 else
691 try allocator.alloc(ptrInfo.child, array.items.len);
692
693 for (array.items, r) |item, *dest| {
694 dest.* = try internalParseFromValue(ptrInfo.child, allocator, item, options);
695 }
696
697 return r;
698 },
699 .string => |s| {
700 if (ptrInfo.child != u8) return error.UnexpectedToken;
701 // Dynamic length string.
702
703 const r = if (ptrInfo.sentinel) |sentinel_ptr|
704 try allocator.allocSentinel(ptrInfo.child, s.len, @ptrCast(*align(1) const ptrInfo.child, sentinel_ptr).*)
705 else
706 try allocator.alloc(ptrInfo.child, s.len);
707 @memcpy(r[0..], s);
708
709 return r;
710 },
711 else => return error.UnexpectedToken,
712 }
713 },
714 else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
715 }
716 },
717 else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
718 }
719}
720
721fn internalParseArrayFromArrayValue(
722 comptime T: type,
723 comptime Child: type,
724 comptime len: comptime_int,
725 allocator: Allocator,
726 array: Array,
727 options: ParseOptions,
728) !T {
729 if (array.items.len != len) return error.LengthMismatch;
730
731 var r: T = undefined;
732 for (array.items, 0..) |item, i| {
733 r[i] = try internalParseFromValue(Child, allocator, item, options);
734 }
735
736 return r;
737}
738
739fn sliceToInt(comptime T: type, slice: []const u8) !T {
740 if (isNumberFormattedLikeAnInteger(slice))
741 return std.fmt.parseInt(T, slice, 10);
742 // Try to coerce a float to an integer.
743 const float = try std.fmt.parseFloat(f128, slice);
744 if (@round(float) != float) return error.InvalidNumber;
745 if (float > std.math.maxInt(T) or float < std.math.minInt(T)) return error.Overflow;
746 return @intCast(T, @intFromFloat(i128, float));
747}
748
749fn sliceToEnum(comptime T: type, slice: []const u8) !T {
750 // Check for a named value.
751 if (std.meta.stringToEnum(T, slice)) |value| return value;
752 // Check for a numeric value.
753 if (!isNumberFormattedLikeAnInteger(slice)) return error.InvalidEnumTag;
754 const n = std.fmt.parseInt(@typeInfo(T).Enum.tag_type, slice, 10) catch return error.InvalidEnumTag;
755 return std.meta.intToEnum(T, n);
756}
757
758fn fillDefaultStructValues(comptime T: type, r: *T, fields_seen: *[@typeInfo(T).Struct.fields.len]bool) !void {
759 inline for (@typeInfo(T).Struct.fields, 0..) |field, i| {
760 if (!fields_seen[i]) {
761 if (field.default_value) |default_ptr| {
762 const default = @ptrCast(*align(1) const field.type, default_ptr).*;
763 @field(r, field.name) = default;
764 } else {
765 return error.MissingField;
766 }
767 }
768 }
769}
770
487771fn freeAllocated(allocator: Allocator, token: Token) void {
488772 switch (token) {
489773 .allocated_number, .allocated_string => |slice| {
lib/std/json/static_test.zig+359
......@@ -1,14 +1,373 @@
11const std = @import("std");
22const testing = std.testing;
33const ArenaAllocator = std.heap.ArenaAllocator;
4const Allocator = std.mem.Allocator;
45
56const parseFromSlice = @import("./static.zig").parseFromSlice;
67const parseFromSliceLeaky = @import("./static.zig").parseFromSliceLeaky;
78const parseFromTokenSource = @import("./static.zig").parseFromTokenSource;
89const parseFromTokenSourceLeaky = @import("./static.zig").parseFromTokenSourceLeaky;
10const parseFromValue = @import("./static.zig").parseFromValue;
11const parseFromValueLeaky = @import("./static.zig").parseFromValueLeaky;
912const ParseOptions = @import("./static.zig").ParseOptions;
13
1014const JsonScanner = @import("./scanner.zig").Scanner;
1115const jsonReader = @import("./scanner.zig").reader;
16const Diagnostics = @import("./scanner.zig").Diagnostics;
17
18const Value = @import("./dynamic.zig").Value;
19
20const Primitives = struct {
21 bool: bool,
22 // f16, f80, f128: don't work in std.fmt.parseFloat(T).
23 f32: f32,
24 f64: f64,
25 u0: u0,
26 i0: i0,
27 u1: u1,
28 i1: i1,
29 u8: u8,
30 i8: i8,
31 i130: i130,
32};
33
34const primitives_0 = Primitives{
35 .bool = false,
36 .f32 = 0,
37 .f64 = 0,
38 .u0 = 0,
39 .i0 = 0,
40 .u1 = 0,
41 .i1 = 0,
42 .u8 = 0,
43 .i8 = 0,
44 .i130 = 0,
45};
46const primitives_0_doc_0 =
47 \\{
48 \\ "bool": false,
49 \\ "f32": 0,
50 \\ "f64": 0,
51 \\ "u0": 0,
52 \\ "i0": 0,
53 \\ "u1": 0,
54 \\ "i1": 0,
55 \\ "u8": 0,
56 \\ "i8": 0,
57 \\ "i130": 0
58 \\}
59;
60const primitives_0_doc_1 = // looks like a float.
61 \\{
62 \\ "bool": false,
63 \\ "f32": 0.0,
64 \\ "f64": 0.0,
65 \\ "u0": 0.0,
66 \\ "i0": 0.0,
67 \\ "u1": 0.0,
68 \\ "i1": 0.0,
69 \\ "u8": 0.0,
70 \\ "i8": 0.0,
71 \\ "i130": 0.0
72 \\}
73;
74
75const primitives_1 = Primitives{
76 .bool = true,
77 .f32 = 1073741824,
78 .f64 = 1152921504606846976,
79 .u0 = 0,
80 .i0 = 0,
81 .u1 = 1,
82 .i1 = -1,
83 .u8 = 255,
84 .i8 = -128,
85 .i130 = -680564733841876926926749214863536422911,
86};
87const primitives_1_doc_0 =
88 \\{
89 \\ "bool": true,
90 \\ "f32": 1073741824,
91 \\ "f64": 1152921504606846976,
92 \\ "u0": 0,
93 \\ "i0": 0,
94 \\ "u1": 1,
95 \\ "i1": -1,
96 \\ "u8": 255,
97 \\ "i8": -128,
98 \\ "i130": -680564733841876926926749214863536422911
99 \\}
100;
101const primitives_1_doc_1 = // float rounding.
102 \\{
103 \\ "bool": true,
104 \\ "f32": 1073741825,
105 \\ "f64": 1152921504606846977,
106 \\ "u0": 0,
107 \\ "i0": 0,
108 \\ "u1": 1,
109 \\ "i1": -1,
110 \\ "u8": 255,
111 \\ "i8": -128,
112 \\ "i130": -680564733841876926926749214863536422911
113 \\}
114;
115
116const Aggregates = struct {
117 optional: ?i32,
118 array: [4]i32,
119 vector: @Vector(4, i32),
120 pointer: *i32,
121 pointer_const: *const i32,
122 slice: []i32,
123 slice_const: []const i32,
124 slice_sentinel: [:0]i32,
125 slice_sentinel_const: [:0]const i32,
126};
127
128var zero: i32 = 0;
129const zero_const: i32 = 0;
130var array_of_zeros: [4:0]i32 = [_:0]i32{ 0, 0, 0, 0 };
131var one: i32 = 1;
132const one_const: i32 = 1;
133var array_countdown: [4:0]i32 = [_:0]i32{ 4, 3, 2, 1 };
134
135const aggregates_0 = Aggregates{
136 .optional = null,
137 .array = [4]i32{ 0, 0, 0, 0 },
138 .vector = @Vector(4, i32){ 0, 0, 0, 0 },
139 .pointer = &zero,
140 .pointer_const = &zero_const,
141 .slice = array_of_zeros[0..0],
142 .slice_const = &[_]i32{},
143 .slice_sentinel = array_of_zeros[0..0 :0],
144 .slice_sentinel_const = &[_:0]i32{},
145};
146const aggregates_0_doc =
147 \\{
148 \\ "optional": null,
149 \\ "array": [0, 0, 0, 0],
150 \\ "vector": [0, 0, 0, 0],
151 \\ "pointer": 0,
152 \\ "pointer_const": 0,
153 \\ "slice": [],
154 \\ "slice_const": [],
155 \\ "slice_sentinel": [],
156 \\ "slice_sentinel_const": []
157 \\}
158;
159
160const aggregates_1 = Aggregates{
161 .optional = 1,
162 .array = [4]i32{ 1, 2, 3, 4 },
163 .vector = @Vector(4, i32){ 1, 2, 3, 4 },
164 .pointer = &one,
165 .pointer_const = &one_const,
166 .slice = array_countdown[0..],
167 .slice_const = array_countdown[0..],
168 .slice_sentinel = array_countdown[0.. :0],
169 .slice_sentinel_const = array_countdown[0.. :0],
170};
171const aggregates_1_doc =
172 \\{
173 \\ "optional": 1,
174 \\ "array": [1, 2, 3, 4],
175 \\ "vector": [1, 2, 3, 4],
176 \\ "pointer": 1,
177 \\ "pointer_const": 1,
178 \\ "slice": [4, 3, 2, 1],
179 \\ "slice_const": [4, 3, 2, 1],
180 \\ "slice_sentinel": [4, 3, 2, 1],
181 \\ "slice_sentinel_const": [4, 3, 2, 1]
182 \\}
183;
184
185const Strings = struct {
186 slice_u8: []u8,
187 slice_const_u8: []const u8,
188 array_u8: [4]u8,
189 slice_sentinel_u8: [:0]u8,
190 slice_const_sentinel_u8: [:0]const u8,
191 array_sentinel_u8: [4:0]u8,
192};
193
194var abcd = [4:0]u8{ 'a', 'b', 'c', 'd' };
195const strings_0 = Strings{
196 .slice_u8 = abcd[0..],
197 .slice_const_u8 = "abcd",
198 .array_u8 = [4]u8{ 'a', 'b', 'c', 'd' },
199 .slice_sentinel_u8 = abcd[0..],
200 .slice_const_sentinel_u8 = "abcd",
201 .array_sentinel_u8 = [4:0]u8{ 'a', 'b', 'c', 'd' },
202};
203const strings_0_doc_0 =
204 \\{
205 \\ "slice_u8": "abcd",
206 \\ "slice_const_u8": "abcd",
207 \\ "array_u8": "abcd",
208 \\ "slice_sentinel_u8": "abcd",
209 \\ "slice_const_sentinel_u8": "abcd",
210 \\ "array_sentinel_u8": "abcd"
211 \\}
212;
213const strings_0_doc_1 =
214 \\{
215 \\ "slice_u8": [97, 98, 99, 100],
216 \\ "slice_const_u8": [97, 98, 99, 100],
217 \\ "array_u8": [97, 98, 99, 100],
218 \\ "slice_sentinel_u8": [97, 98, 99, 100],
219 \\ "slice_const_sentinel_u8": [97, 98, 99, 100],
220 \\ "array_sentinel_u8": [97, 98, 99, 100]
221 \\}
222;
223
224const Subnamespaces = struct {
225 packed_struct: packed struct { a: u32, b: u32 },
226 union_enum: union(enum) { i: i32, s: []const u8, v },
227 inferred_enum: enum { a, b },
228 explicit_enum: enum(u8) { a = 0, b = 1 },
229
230 custom_struct: struct {
231 pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) !@This() {
232 _ = allocator;
233 _ = options;
234 try source.skipValue();
235 return @This(){};
236 }
237 pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() {
238 _ = allocator;
239 _ = source;
240 _ = options;
241 return @This(){};
242 }
243 },
244 custom_union: union(enum) {
245 i: i32,
246 s: []const u8,
247 pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) !@This() {
248 _ = allocator;
249 _ = options;
250 try source.skipValue();
251 return @This(){ .i = 0 };
252 }
253 pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() {
254 _ = allocator;
255 _ = source;
256 _ = options;
257 return @This(){ .i = 0 };
258 }
259 },
260 custom_enum: enum {
261 a,
262 b,
263 pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) !@This() {
264 _ = allocator;
265 _ = options;
266 try source.skipValue();
267 return .a;
268 }
269 pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() {
270 _ = allocator;
271 _ = source;
272 _ = options;
273 return .a;
274 }
275 },
276};
277
278const subnamespaces_0 = Subnamespaces{
279 .packed_struct = .{ .a = 0, .b = 0 },
280 .union_enum = .{ .i = 0 },
281 .inferred_enum = .a,
282 .explicit_enum = .a,
283 .custom_struct = .{},
284 .custom_union = .{ .i = 0 },
285 .custom_enum = .a,
286};
287const subnamespaces_0_doc =
288 \\{
289 \\ "packed_struct": {"a": 0, "b": 0},
290 \\ "union_enum": {"i": 0},
291 \\ "inferred_enum": "a",
292 \\ "explicit_enum": "a",
293 \\ "custom_struct": null,
294 \\ "custom_union": null,
295 \\ "custom_enum": null
296 \\}
297;
298
299fn testAllParseFunctions(comptime T: type, expected: T, doc: []const u8) !void {
300 // First do the one with the debug info in case we get a SyntaxError or something.
301 {
302 var scanner = JsonScanner.initCompleteInput(testing.allocator, doc);
303 defer scanner.deinit();
304 var diagnostics = Diagnostics{};
305 scanner.enableDiagnostics(&diagnostics);
306 var parsed = parseFromTokenSource(T, testing.allocator, &scanner, .{}) catch |e| {
307 std.debug.print("at line,col: {}:{}\n", .{ diagnostics.getLine(), diagnostics.getColumn() });
308 return e;
309 };
310 defer parsed.deinit();
311 try testing.expectEqualDeep(expected, parsed.value);
312 }
313 {
314 const parsed = try parseFromSlice(T, testing.allocator, doc, .{});
315 defer parsed.deinit();
316 try testing.expectEqualDeep(expected, parsed.value);
317 }
318 {
319 var stream = std.io.fixedBufferStream(doc);
320 var json_reader = jsonReader(std.testing.allocator, stream.reader());
321 defer json_reader.deinit();
322 var parsed = try parseFromTokenSource(T, testing.allocator, &json_reader, .{});
323 defer parsed.deinit();
324 try testing.expectEqualDeep(expected, parsed.value);
325 }
326
327 var arena = ArenaAllocator.init(testing.allocator);
328 defer arena.deinit();
329 {
330 try testing.expectEqualDeep(expected, try parseFromSliceLeaky(T, arena.allocator(), doc, .{}));
331 }
332 {
333 var scanner = JsonScanner.initCompleteInput(testing.allocator, doc);
334 defer scanner.deinit();
335 try testing.expectEqualDeep(expected, try parseFromTokenSourceLeaky(T, arena.allocator(), &scanner, .{}));
336 }
337 {
338 var stream = std.io.fixedBufferStream(doc);
339 var json_reader = jsonReader(std.testing.allocator, stream.reader());
340 defer json_reader.deinit();
341 try testing.expectEqualDeep(expected, try parseFromTokenSourceLeaky(T, arena.allocator(), &json_reader, .{}));
342 }
343
344 const parsed_dynamic = try parseFromSlice(Value, testing.allocator, doc, .{});
345 defer parsed_dynamic.deinit();
346 {
347 const parsed = try parseFromValue(T, testing.allocator, parsed_dynamic.value, .{});
348 defer parsed.deinit();
349 try testing.expectEqualDeep(expected, parsed.value);
350 }
351 {
352 try testing.expectEqualDeep(expected, try parseFromValueLeaky(T, arena.allocator(), parsed_dynamic.value, .{}));
353 }
354}
355
356test "test all types" {
357 if (true) return error.SkipZigTest; // See https://github.com/ziglang/zig/issues/16108
358 try testAllParseFunctions(Primitives, primitives_0, primitives_0_doc_0);
359 try testAllParseFunctions(Primitives, primitives_0, primitives_0_doc_1);
360 try testAllParseFunctions(Primitives, primitives_1, primitives_1_doc_0);
361 try testAllParseFunctions(Primitives, primitives_1, primitives_1_doc_1);
362
363 try testAllParseFunctions(Aggregates, aggregates_0, aggregates_0_doc);
364 try testAllParseFunctions(Aggregates, aggregates_1, aggregates_1_doc);
365
366 try testAllParseFunctions(Strings, strings_0, strings_0_doc_0);
367 try testAllParseFunctions(Strings, strings_0, strings_0_doc_1);
368
369 try testAllParseFunctions(Subnamespaces, subnamespaces_0, subnamespaces_0_doc);
370}
12371
13372test "parse" {
14373 try testing.expectEqual(false, try parseFromSliceLeaky(bool, testing.allocator, "false", .{}));