1const std = @import("std");
2const assert = std.debug.assert;
3const Allocator = std.mem.Allocator;
4const ArenaAllocator = std.heap.ArenaAllocator;
5const ArrayList = std.array_list.Managed;
6
7const Scanner = @import("Scanner.zig");
8const Token = Scanner.Token;
9const AllocWhen = Scanner.AllocWhen;
10const default_max_value_len = Scanner.default_max_value_len;
11const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger;
12
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.
19pub const ParseOptions = struct {
20 /// Behaviour when a duplicate field is encountered.
21 /// The default is to return `error.DuplicateField`.
22 duplicate_field_behavior: enum {
23 use_first,
24 @"error",
25 use_last,
26 } = .@"error",
27
28 /// If false, finding an unknown field returns `error.UnknownField`.
29 ignore_unknown_fields: bool = false,
30
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 values that don't need allocation or are not copied (see `allocate`).
36 /// Ignored for `parseFromValue` and `parseFromValueLeaky`.
37 max_value_len: ?usize = null,
38
39 /// This determines whether strings should always be copied,
40 /// or if a reference to the given buffer should be preferred if possible.
41 /// The default for `parseFromSlice` or `parseFromTokenSource` with a `*std.json.Scanner` input
42 /// is `.alloc_if_needed`.
43 /// The default with a `*std.json.Reader` input is `.alloc_always`.
44 /// Ignored for `parseFromValue` and `parseFromValueLeaky`.
45 allocate: ?AllocWhen = null,
46
47 /// When parsing to a `std.json.Value`, set this option to false to always emit
48 /// JSON numbers as unparsed `std.json.Value.number_string`.
49 /// Otherwise, JSON numbers are parsed as either `std.json.Value.integer`,
50 /// `std.json.Value.float` or left as unparsed `std.json.Value.number_string`
51 /// depending on the format and value of the JSON number.
52 /// When this option is true, JSON numbers encoded as floats (see `std.json.isNumberFormattedLikeAnInteger`)
53 /// may lose precision when being parsed into `std.json.Value.float`.
54 parse_numbers: bool = true,
55};
56
57pub fn Parsed(comptime T: type) type {
58 return struct {
59 arena: *ArenaAllocator,
60 value: T,
61
62 pub fn deinit(self: @This()) void {
63 const allocator = self.arena.child_allocator;
64 self.arena.deinit();
65 allocator.destroy(self.arena);
66 }
67 };
68}
69
70/// Parses the json document from `s` and returns the result packaged in a `std.json.Parsed`.
71/// You must call `deinit()` of the returned object to clean up allocated resources.
72/// If you are using a `std.heap.ArenaAllocator` or similar, consider calling `parseFromSliceLeaky` instead.
73/// Note that `error.BufferUnderrun` is not actually possible to return from this function.
74pub fn parseFromSlice(
75 comptime T: type,
76 allocator: Allocator,
77 s: []const u8,
78 options: ParseOptions,
79) ParseError(Scanner)!Parsed(T) {
80 var scanner = Scanner.initCompleteInput(allocator, s);
81 defer scanner.deinit();
82
83 return parseFromTokenSource(T, allocator, &scanner, options);
84}
85
86/// Parses the json document from `s` and returns the result.
87/// Allocations made during this operation are not carefully tracked and may not be possible to individually clean up.
88/// It is recommended to use a `std.heap.ArenaAllocator` or similar.
89pub fn parseFromSliceLeaky(
90 comptime T: type,
91 allocator: Allocator,
92 s: []const u8,
93 options: ParseOptions,
94) ParseError(Scanner)!T {
95 var scanner = Scanner.initCompleteInput(allocator, s);
96 defer scanner.deinit();
97
98 return parseFromTokenSourceLeaky(T, allocator, &scanner, options);
99}
100
101/// `scanner_or_reader` must be either a `*std.json.Scanner` with complete input or a `*std.json.Reader`.
102/// Note that `error.BufferUnderrun` is not actually possible to return from this function.
103pub fn parseFromTokenSource(
104 comptime T: type,
105 allocator: Allocator,
106 scanner_or_reader: anytype,
107 options: ParseOptions,
108) ParseError(@TypeOf(scanner_or_reader.*))!Parsed(T) {
109 var parsed = Parsed(T){
110 .arena = try allocator.create(ArenaAllocator),
111 .value = undefined,
112 };
113 errdefer allocator.destroy(parsed.arena);
114 parsed.arena.* = ArenaAllocator.init(allocator);
115 errdefer parsed.arena.deinit();
116
117 parsed.value = try parseFromTokenSourceLeaky(T, parsed.arena.allocator(), scanner_or_reader, options);
118
119 return parsed;
120}
121
122/// `scanner_or_reader` must be either a `*std.json.Scanner` with complete input or a `*std.json.Reader`.
123/// Allocations made during this operation are not carefully tracked and may not be possible to individually clean up.
124/// It is recommended to use a `std.heap.ArenaAllocator` or similar.
125pub fn parseFromTokenSourceLeaky(
126 comptime T: type,
127 allocator: Allocator,
128 scanner_or_reader: anytype,
129 options: ParseOptions,
130) ParseError(@TypeOf(scanner_or_reader.*))!T {
131 if (@TypeOf(scanner_or_reader.*) == Scanner) {
132 assert(scanner_or_reader.is_end_of_input);
133 }
134 var resolved_options = options;
135 if (resolved_options.max_value_len == null) {
136 if (@TypeOf(scanner_or_reader.*) == Scanner) {
137 resolved_options.max_value_len = scanner_or_reader.input.len;
138 } else {
139 resolved_options.max_value_len = default_max_value_len;
140 }
141 }
142 if (resolved_options.allocate == null) {
143 if (@TypeOf(scanner_or_reader.*) == Scanner) {
144 resolved_options.allocate = .alloc_if_needed;
145 } else {
146 resolved_options.allocate = .alloc_always;
147 }
148 }
149
150 const value = try innerParse(T, allocator, scanner_or_reader, resolved_options);
151
152 assert(.end_of_document == try scanner_or_reader.next());
153
154 return value;
155}
156
157/// Like `parseFromSlice`, but the input is an already-parsed `std.json.Value` object.
158/// Only `options.ignore_unknown_fields` is used from `options`.
159pub fn parseFromValue(
160 comptime T: type,
161 allocator: Allocator,
162 source: Value,
163 options: ParseOptions,
164) ParseFromValueError!Parsed(T) {
165 var parsed = Parsed(T){
166 .arena = try allocator.create(ArenaAllocator),
167 .value = undefined,
168 };
169 errdefer allocator.destroy(parsed.arena);
170 parsed.arena.* = ArenaAllocator.init(allocator);
171 errdefer parsed.arena.deinit();
172
173 parsed.value = try parseFromValueLeaky(T, parsed.arena.allocator(), source, options);
174
175 return parsed;
176}
177
178pub fn parseFromValueLeaky(
179 comptime T: type,
180 allocator: Allocator,
181 source: Value,
182 options: ParseOptions,
183) ParseFromValueError!T {
184 // I guess this function doesn't need to exist,
185 // but the flow of the sourcecode is easy to follow and grouped nicely with
186 // this pub redirect function near the top and the implementation near the bottom.
187 return innerParseFromValue(T, allocator, source, options);
188}
189
190/// The error set that will be returned when parsing from `*Source`.
191/// Note that this may contain `error.BufferUnderrun`, but that error will never actually be returned.
192pub fn ParseError(comptime Source: type) type {
193 // A few of these will either always be present or present enough of the time that
194 // omitting them is more confusing than always including them.
195 return ParseFromValueError || Source.NextError || Source.PeekError || Source.AllocError;
196}
197
198pub const ParseFromValueError = std.fmt.ParseIntError || std.fmt.ParseFloatError || Allocator.Error || error{
199 UnexpectedToken,
200 InvalidNumber,
201 Overflow,
202 InvalidEnumTag,
203 DuplicateField,
204 UnknownField,
205 MissingField,
206 LengthMismatch,
207};
208
209/// This is an internal function called recursively
210/// during the implementation of `parseFromTokenSourceLeaky` and similar.
211/// It is exposed primarily to enable custom `jsonParse()` methods to call back into the `parseFrom*` system,
212/// such as if you're implementing a custom container of type `T`;
213/// you can call `innerParse(T, ...)` for each of the container's items.
214/// Note that `null` fields are not allowed on the `options` when calling this function.
215/// (The `options` you get in your `jsonParse` method has no `null` fields.)
216pub fn innerParse(
217 comptime T: type,
218 allocator: Allocator,
219 source: anytype,
220 options: ParseOptions,
221) ParseError(@TypeOf(source.*))!T {
222 switch (@typeInfo(T)) {
223 .bool => {
224 return switch (try source.next()) {
225 .true => true,
226 .false => false,
227 else => error.UnexpectedToken,
228 };
229 },
230 .float, .comptime_float => {
231 const token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?);
232 defer freeAllocated(allocator, token);
233 const slice = switch (token) {
234 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,
235 else => return error.UnexpectedToken,
236 };
237 return try std.fmt.parseFloat(T, slice);
238 },
239 .int, .comptime_int => {
240 const token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?);
241 defer freeAllocated(allocator, token);
242 const slice = switch (token) {
243 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,
244 else => return error.UnexpectedToken,
245 };
246 return sliceToInt(T, slice);
247 },
248 .optional => |optionalInfo| {
249 switch (try source.peekNextTokenType()) {
250 .null => {
251 _ = try source.next();
252 return null;
253 },
254 else => {
255 return try innerParse(optionalInfo.child, allocator, source, options);
256 },
257 }
258 },
259 .@"enum" => {
260 if (std.meta.hasFn(T, "jsonParse")) {
261 return T.jsonParse(allocator, source, options);
262 }
263
264 const token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?);
265 defer freeAllocated(allocator, token);
266 const slice = switch (token) {
267 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,
268 else => return error.UnexpectedToken,
269 };
270 return sliceToEnum(T, slice);
271 },
272 .@"union" => |unionInfo| {
273 if (std.meta.hasFn(T, "jsonParse")) {
274 return T.jsonParse(allocator, source, options);
275 }
276
277 if (unionInfo.tag_type == null) @compileError("Unable to parse into untagged union '" ++ @typeName(T) ++ "'");
278
279 if (.object_begin != try source.next()) return error.UnexpectedToken;
280
281 var result: ?T = null;
282 var name_token: ?Token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?);
283 const field_name = switch (name_token.?) {
284 inline .string, .allocated_string => |slice| slice,
285 else => {
286 return error.UnexpectedToken;
287 },
288 };
289
290 inline for (unionInfo.field_names, unionInfo.field_types) |u_field_name, u_field_type| {
291 if (std.mem.eql(u8, u_field_name, field_name)) {
292 // Free the name token now in case we're using an allocator that optimizes freeing the last allocated object.
293 // (Recursing into innerParse() might trigger more allocations.)
294 freeAllocated(allocator, name_token.?);
295 name_token = null;
296 if (u_field_type == void) {
297 // void isn't really a json type, but we can support void payload union tags with {} as a value.
298 if (.object_begin != try source.next()) return error.UnexpectedToken;
299 if (.object_end != try source.next()) return error.UnexpectedToken;
300 result = @unionInit(T, u_field_name, {});
301 } else {
302 // Recurse.
303 result = @unionInit(T, u_field_name, try innerParse(u_field_type, allocator, source, options));
304 }
305 break;
306 }
307 } else {
308 // Didn't match anything.
309 return error.UnknownField;
310 }
311
312 if (.object_end != try source.next()) return error.UnexpectedToken;
313
314 return result.?;
315 },
316
317 .@"struct" => |structInfo| {
318 if (structInfo.is_tuple) {
319 if (.array_begin != try source.next()) return error.UnexpectedToken;
320
321 var r: T = undefined;
322 inline for (structInfo.field_types, 0..) |field_type, i| {
323 r[i] = try innerParse(field_type, allocator, source, options);
324 }
325
326 if (.array_end != try source.next()) return error.UnexpectedToken;
327
328 return r;
329 }
330
331 if (std.meta.hasFn(T, "jsonParse")) {
332 return T.jsonParse(allocator, source, options);
333 }
334
335 if (.object_begin != try source.next()) return error.UnexpectedToken;
336
337 var r: T = undefined;
338 var fields_seen: [structInfo.field_names.len]bool = @splat(false);
339
340 while (true) {
341 var name_token: ?Token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?);
342 const field_name = switch (name_token.?) {
343 inline .string, .allocated_string => |slice| slice,
344 .object_end => { // No more fields.
345 break;
346 },
347 else => {
348 return error.UnexpectedToken;
349 },
350 };
351
352 inline for (
353 structInfo.field_names,
354 structInfo.field_types,
355 structInfo.field_attrs,
356 0..,
357 ) |f_name, f_type, f_attrs, i| {
358 if (f_attrs.@"comptime") @compileError("comptime fields are not supported: " ++ @typeName(T) ++ "." ++ f_name);
359 if (std.mem.eql(u8, f_name, field_name)) {
360 // Free the name token now in case we're using an allocator that optimizes freeing the last allocated object.
361 // (Recursing into innerParse() might trigger more allocations.)
362 freeAllocated(allocator, name_token.?);
363 name_token = null;
364 if (fields_seen[i]) {
365 switch (options.duplicate_field_behavior) {
366 .use_first => {
367 // Parse and ignore the redundant value.
368 // We don't want to skip the value, because we want type checking.
369 _ = try innerParse(f_type, allocator, source, options);
370 break;
371 },
372 .@"error" => return error.DuplicateField,
373 .use_last => {},
374 }
375 }
376 @field(r, f_name) = try innerParse(f_type, allocator, source, options);
377 fields_seen[i] = true;
378 break;
379 }
380 } else {
381 // Didn't match anything.
382 freeAllocated(allocator, name_token.?);
383 if (options.ignore_unknown_fields) {
384 try source.skipValue();
385 } else {
386 return error.UnknownField;
387 }
388 }
389 }
390 try fillDefaultStructValues(T, &r, &fields_seen);
391 return r;
392 },
393
394 .array => |arrayInfo| {
395 switch (try source.peekNextTokenType()) {
396 .array_begin => {
397 // Typical array.
398 return internalParseArray(T, arrayInfo.child, allocator, source, options);
399 },
400 .string => {
401 if (arrayInfo.child != u8) return error.UnexpectedToken;
402 // Fixed-length string.
403
404 var r: T = undefined;
405 var i: usize = 0;
406 while (true) {
407 switch (try source.next()) {
408 .string => |slice| {
409 if (i + slice.len != r.len) return error.LengthMismatch;
410 @memcpy(r[i..][0..slice.len], slice);
411 break;
412 },
413 .partial_string => |slice| {
414 if (i + slice.len > r.len) return error.LengthMismatch;
415 @memcpy(r[i..][0..slice.len], slice);
416 i += slice.len;
417 },
418 .partial_string_escaped_1 => |arr| {
419 if (i + arr.len > r.len) return error.LengthMismatch;
420 @memcpy(r[i..][0..arr.len], arr[0..]);
421 i += arr.len;
422 },
423 .partial_string_escaped_2 => |arr| {
424 if (i + arr.len > r.len) return error.LengthMismatch;
425 @memcpy(r[i..][0..arr.len], arr[0..]);
426 i += arr.len;
427 },
428 .partial_string_escaped_3 => |arr| {
429 if (i + arr.len > r.len) return error.LengthMismatch;
430 @memcpy(r[i..][0..arr.len], arr[0..]);
431 i += arr.len;
432 },
433 .partial_string_escaped_4 => |arr| {
434 if (i + arr.len > r.len) return error.LengthMismatch;
435 @memcpy(r[i..][0..arr.len], arr[0..]);
436 i += arr.len;
437 },
438 else => unreachable,
439 }
440 }
441
442 return r;
443 },
444
445 else => return error.UnexpectedToken,
446 }
447 },
448
449 .vector => |vector_info| {
450 switch (try source.peekNextTokenType()) {
451 .array_begin => {
452 const A = [vector_info.len]vector_info.child;
453 return try internalParseArray(A, vector_info.child, allocator, source, options);
454 },
455 else => return error.UnexpectedToken,
456 }
457 },
458
459 .pointer => |ptrInfo| {
460 switch (ptrInfo.size) {
461 .one => {
462 const r: *ptrInfo.child = try allocator.create(ptrInfo.child);
463 r.* = try innerParse(ptrInfo.child, allocator, source, options);
464 return r;
465 },
466 .slice => {
467 switch (try source.peekNextTokenType()) {
468 .array_begin => {
469 _ = try source.next();
470
471 // Typical array.
472 var arraylist = ArrayList(ptrInfo.child).init(allocator);
473 while (true) {
474 switch (try source.peekNextTokenType()) {
475 .array_end => {
476 _ = try source.next();
477 break;
478 },
479 else => {},
480 }
481
482 try arraylist.ensureUnusedCapacity(1);
483 arraylist.appendAssumeCapacity(try innerParse(ptrInfo.child, allocator, source, options));
484 }
485
486 if (ptrInfo.sentinel()) |s| {
487 return try arraylist.toOwnedSliceSentinel(s);
488 }
489
490 return try arraylist.toOwnedSlice();
491 },
492 .string => {
493 if (ptrInfo.child != u8) return error.UnexpectedToken;
494
495 // Dynamic length string.
496 if (ptrInfo.sentinel()) |s| {
497 // Use our own array list so we can append the sentinel.
498 var value_list = ArrayList(u8).init(allocator);
499 _ = try source.allocNextIntoArrayListMax(&value_list, .alloc_always, options.max_value_len.?);
500 return try value_list.toOwnedSliceSentinel(s);
501 }
502 if (ptrInfo.attrs.@"const") {
503 switch (try source.nextAllocMax(allocator, options.allocate.?, options.max_value_len.?)) {
504 inline .string, .allocated_string => |slice| return slice,
505 else => unreachable,
506 }
507 } else {
508 // Have to allocate to get a mutable copy.
509 switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
510 .allocated_string => |slice| return slice,
511 else => unreachable,
512 }
513 }
514 },
515 else => return error.UnexpectedToken,
516 }
517 },
518 else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
519 }
520 },
521 else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
522 }
523 unreachable;
524}
525
526fn internalParseArray(
527 comptime T: type,
528 comptime Child: type,
529 allocator: Allocator,
530 source: anytype,
531 options: ParseOptions,
532) !T {
533 assert(.array_begin == try source.next());
534
535 var r: T = undefined;
536 for (&r) |*elem| {
537 elem.* = try innerParse(Child, allocator, source, options);
538 }
539
540 if (.array_end != try source.next()) return error.UnexpectedToken;
541
542 return r;
543}
544
545/// This is an internal function called recursively
546/// during the implementation of `parseFromValueLeaky`.
547/// It is exposed primarily to enable custom `jsonParseFromValue()` methods to call back into the `parseFromValue*` system,
548/// such as if you're implementing a custom container of type `T`;
549/// you can call `innerParseFromValue(T, ...)` for each of the container's items.
550pub fn innerParseFromValue(
551 comptime T: type,
552 allocator: Allocator,
553 source: Value,
554 options: ParseOptions,
555) ParseFromValueError!T {
556 switch (@typeInfo(T)) {
557 .bool => {
558 switch (source) {
559 .bool => |b| return b,
560 else => return error.UnexpectedToken,
561 }
562 },
563 .float, .comptime_float => {
564 switch (source) {
565 .float => |f| return @as(T, @floatCast(f)),
566 .integer => |i| return @as(T, @floatFromInt(i)),
567 .number_string, .string => |s| return std.fmt.parseFloat(T, s),
568 else => return error.UnexpectedToken,
569 }
570 },
571 .int, .comptime_int => {
572 switch (source) {
573 .float => |f| {
574 if (@round(f) != f) return error.InvalidNumber;
575 if (f > @as(@TypeOf(f), @floatFromInt(std.math.maxInt(T)))) return error.Overflow;
576 if (f < @as(@TypeOf(f), @floatFromInt(std.math.minInt(T)))) return error.Overflow;
577 return @intFromFloat(f);
578 },
579 .integer => |i| {
580 if (i > std.math.maxInt(T)) return error.Overflow;
581 if (i < std.math.minInt(T)) return error.Overflow;
582 return @intCast(i);
583 },
584 .number_string, .string => |s| {
585 return sliceToInt(T, s);
586 },
587 else => return error.UnexpectedToken,
588 }
589 },
590 .optional => |optionalInfo| {
591 switch (source) {
592 .null => return null,
593 else => return try innerParseFromValue(optionalInfo.child, allocator, source, options),
594 }
595 },
596 .@"enum" => {
597 if (std.meta.hasFn(T, "jsonParseFromValue")) {
598 return T.jsonParseFromValue(allocator, source, options);
599 }
600
601 switch (source) {
602 .float => return error.InvalidEnumTag,
603 .integer => |i| return std.enums.fromInt(T, i) orelse return error.InvalidEnumTag,
604 .number_string, .string => |s| return sliceToEnum(T, s),
605 else => return error.UnexpectedToken,
606 }
607 },
608 .@"union" => |unionInfo| {
609 if (std.meta.hasFn(T, "jsonParseFromValue")) {
610 return T.jsonParseFromValue(allocator, source, options);
611 }
612
613 if (unionInfo.tag_type == null) @compileError("Unable to parse into untagged union '" ++ @typeName(T) ++ "'");
614
615 if (source != .object) return error.UnexpectedToken;
616 if (source.object.count() != 1) return error.UnexpectedToken;
617
618 var it = source.object.iterator();
619 const kv = it.next().?;
620 const field_name = kv.key_ptr.*;
621
622 inline for (unionInfo.field_names, unionInfo.field_types) |u_field_name, u_field_type| {
623 if (std.mem.eql(u8, u_field_name, field_name)) {
624 if (u_field_type == void) {
625 // void isn't really a json type, but we can support void payload union tags with {} as a value.
626 if (kv.value_ptr.* != .object) return error.UnexpectedToken;
627 if (kv.value_ptr.*.object.count() != 0) return error.UnexpectedToken;
628 return @unionInit(T, u_field_name, {});
629 }
630 // Recurse.
631 return @unionInit(T, u_field_name, try innerParseFromValue(u_field_type, allocator, kv.value_ptr.*, options));
632 }
633 }
634 // Didn't match anything.
635 return error.UnknownField;
636 },
637
638 .@"struct" => |structInfo| {
639 if (structInfo.is_tuple) {
640 if (source != .array) return error.UnexpectedToken;
641 if (source.array.items.len != structInfo.field_names.len) return error.UnexpectedToken;
642
643 var r: T = undefined;
644 inline for (0..structInfo.field_names.len, source.array.items) |i, item| {
645 r[i] = try innerParseFromValue(structInfo.field_types[i], allocator, item, options);
646 }
647
648 return r;
649 }
650
651 if (std.meta.hasFn(T, "jsonParseFromValue")) {
652 return T.jsonParseFromValue(allocator, source, options);
653 }
654
655 if (source != .object) return error.UnexpectedToken;
656
657 var r: T = undefined;
658 var fields_seen: [structInfo.field_names.len]bool = @splat(false);
659
660 var it = source.object.iterator();
661 while (it.next()) |kv| {
662 const field_name = kv.key_ptr.*;
663
664 inline for (
665 structInfo.field_names,
666 structInfo.field_types,
667 structInfo.field_attrs,
668 0..,
669 ) |f_name, f_type, f_attrs, i| {
670 if (f_attrs.@"comptime") @compileError("comptime fields are not supported: " ++ @typeName(T) ++ "." ++ f_name);
671 if (std.mem.eql(u8, f_name, field_name)) {
672 assert(!fields_seen[i]); // Can't have duplicate keys in a Value.object.
673 @field(r, f_name) = try innerParseFromValue(f_type, allocator, kv.value_ptr.*, options);
674 fields_seen[i] = true;
675 break;
676 }
677 } else {
678 // Didn't match anything.
679 if (!options.ignore_unknown_fields) return error.UnknownField;
680 }
681 }
682 try fillDefaultStructValues(T, &r, &fields_seen);
683 return r;
684 },
685
686 .array => |arrayInfo| {
687 switch (source) {
688 .array => |array| {
689 // Typical array.
690 return innerParseArrayFromArrayValue(T, arrayInfo.child, arrayInfo.len, allocator, array, options);
691 },
692 .string => |s| {
693 if (arrayInfo.child != u8) return error.UnexpectedToken;
694 // Fixed-length string.
695
696 if (s.len != arrayInfo.len) return error.LengthMismatch;
697
698 var r: T = undefined;
699 @memcpy(r[0..], s);
700 return r;
701 },
702
703 else => return error.UnexpectedToken,
704 }
705 },
706
707 .vector => |vecInfo| {
708 switch (source) {
709 .array => |array| {
710 return innerParseArrayFromArrayValue(T, vecInfo.child, vecInfo.len, allocator, array, options);
711 },
712 else => return error.UnexpectedToken,
713 }
714 },
715
716 .pointer => |ptrInfo| {
717 switch (ptrInfo.size) {
718 .one => {
719 const r: *ptrInfo.child = try allocator.create(ptrInfo.child);
720 r.* = try innerParseFromValue(ptrInfo.child, allocator, source, options);
721 return r;
722 },
723 .slice => {
724 switch (source) {
725 .array => |array| {
726 const r = if (ptrInfo.sentinel()) |sentinel|
727 try allocator.allocSentinel(ptrInfo.child, array.items.len, sentinel)
728 else
729 try allocator.alloc(ptrInfo.child, array.items.len);
730
731 for (array.items, r) |item, *dest| {
732 dest.* = try innerParseFromValue(ptrInfo.child, allocator, item, options);
733 }
734
735 return r;
736 },
737 .string => |s| {
738 if (ptrInfo.child != u8) return error.UnexpectedToken;
739 // Dynamic length string.
740
741 const r = if (ptrInfo.sentinel()) |sentinel|
742 try allocator.allocSentinel(ptrInfo.child, s.len, sentinel)
743 else
744 try allocator.alloc(ptrInfo.child, s.len);
745 @memcpy(r[0..], s);
746
747 return r;
748 },
749 else => return error.UnexpectedToken,
750 }
751 },
752 else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
753 }
754 },
755 else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
756 }
757}
758
759fn innerParseArrayFromArrayValue(
760 comptime T: type,
761 comptime Child: type,
762 comptime len: comptime_int,
763 allocator: Allocator,
764 array: Array,
765 options: ParseOptions,
766) !T {
767 if (array.items.len != len) return error.LengthMismatch;
768
769 var r: T = undefined;
770 for (array.items, 0..) |item, i| {
771 r[i] = try innerParseFromValue(Child, allocator, item, options);
772 }
773
774 return r;
775}
776
777fn sliceToInt(comptime T: type, slice: []const u8) !T {
778 if (isNumberFormattedLikeAnInteger(slice))
779 return std.fmt.parseInt(T, slice, 10);
780 // Try to coerce a float to an integer.
781 const float = try std.fmt.parseFloat(f128, slice);
782 if (@round(float) != float) return error.InvalidNumber;
783 if (float > @as(f128, @floatFromInt(std.math.maxInt(T))) or float < @as(f128, @floatFromInt(std.math.minInt(T)))) return error.Overflow;
784 return @as(T, @intCast(@as(i128, @intFromFloat(float))));
785}
786
787fn sliceToEnum(comptime T: type, slice: []const u8) !T {
788 // Check for a named value.
789 if (std.meta.stringToEnum(T, slice)) |value| return value;
790 // Check for a numeric value.
791 if (!isNumberFormattedLikeAnInteger(slice)) return error.InvalidEnumTag;
792 const n = std.fmt.parseInt(@typeInfo(T).@"enum".tag_type, slice, 10) catch return error.InvalidEnumTag;
793 return std.enums.fromInt(T, n) orelse return error.InvalidEnumTag;
794}
795
796fn fillDefaultStructValues(comptime T: type, r: *T, fields_seen: *[@typeInfo(T).@"struct".field_names.len]bool) !void {
797 const info = @typeInfo(T).@"struct";
798 inline for (
799 info.field_names,
800 info.field_types,
801 info.field_attrs,
802 0..,
803 ) |field_name, field_type, field_attrs, i| {
804 if (!fields_seen[i]) {
805 if (field_attrs.defaultValue(field_type)) |default| {
806 @field(r, field_name) = default;
807 } else {
808 return error.MissingField;
809 }
810 }
811 }
812}
813
814fn freeAllocated(allocator: Allocator, token: Token) void {
815 switch (token) {
816 .allocated_number, .allocated_string => |slice| {
817 allocator.free(slice);
818 },
819 else => {},
820 }
821}
822
823test {
824 _ = @import("./static_test.zig");
825}