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