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}