master
 1const std = @import("std");
 2const Allocator = std.mem.Allocator;
 3
 4const ParseOptions = @import("static.zig").ParseOptions;
 5const innerParse = @import("static.zig").innerParse;
 6const innerParseFromValue = @import("static.zig").innerParseFromValue;
 7const Value = @import("dynamic.zig").Value;
 8
 9/// A thin wrapper around `std.StringArrayHashMapUnmanaged` that implements
10/// `jsonParse`, `jsonParseFromValue`, and `jsonStringify`.
11/// This is useful when your JSON schema has an object with arbitrary data keys
12/// instead of comptime-known struct field names.
13pub fn ArrayHashMap(comptime T: type) type {
14    return struct {
15        map: std.StringArrayHashMapUnmanaged(T) = .empty,
16
17        pub fn deinit(self: *@This(), allocator: Allocator) void {
18            self.map.deinit(allocator);
19        }
20
21        pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) !@This() {
22            var map: std.StringArrayHashMapUnmanaged(T) = .empty;
23            errdefer map.deinit(allocator);
24
25            if (.object_begin != try source.next()) return error.UnexpectedToken;
26            while (true) {
27                const token = try source.nextAlloc(allocator, options.allocate.?);
28                switch (token) {
29                    inline .string, .allocated_string => |k| {
30                        const gop = try map.getOrPut(allocator, k);
31                        if (gop.found_existing) {
32                            switch (options.duplicate_field_behavior) {
33                                .use_first => {
34                                    // Parse and ignore the redundant value.
35                                    // We don't want to skip the value, because we want type checking.
36                                    _ = try innerParse(T, allocator, source, options);
37                                    continue;
38                                },
39                                .@"error" => return error.DuplicateField,
40                                .use_last => {},
41                            }
42                        }
43                        gop.value_ptr.* = try innerParse(T, allocator, source, options);
44                    },
45                    .object_end => break,
46                    else => unreachable,
47                }
48            }
49            return .{ .map = map };
50        }
51
52        pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() {
53            if (source != .object) return error.UnexpectedToken;
54
55            var map: std.StringArrayHashMapUnmanaged(T) = .empty;
56            errdefer map.deinit(allocator);
57
58            var it = source.object.iterator();
59            while (it.next()) |kv| {
60                try map.put(allocator, kv.key_ptr.*, try innerParseFromValue(T, allocator, kv.value_ptr.*, options));
61            }
62            return .{ .map = map };
63        }
64
65        pub fn jsonStringify(self: @This(), jws: anytype) !void {
66            try jws.beginObject();
67            var it = self.map.iterator();
68            while (it.next()) |kv| {
69                try jws.objectField(kv.key_ptr.*);
70                try jws.write(kv.value_ptr.*);
71            }
72            try jws.endObject();
73        }
74    };
75}
76
77test {
78    _ = @import("hashmap_test.zig");
79}