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}