1const std = @import("std");
2const debug = std.debug;
3const ArenaAllocator = std.heap.ArenaAllocator;
4const StringArrayHashMap = std.array_hash_map.String;
5const Allocator = std.mem.Allocator;
6const json = std.json;
7
8const ParseOptions = @import("./static.zig").ParseOptions;
9const ParseError = @import("./static.zig").ParseError;
10
11const isNumberFormattedLikeAnInteger = @import("Scanner.zig").isNumberFormattedLikeAnInteger;
12
13pub const ObjectMap = StringArrayHashMap(Value);
14pub const Array = std.array_list.Managed(Value);
15
16/// Represents any JSON value, potentially containing other JSON values.
17/// A .float value may be an approximation of the original value.
18/// Arbitrary precision numbers can be represented by .number_string values.
19/// See also `std.json.ParseOptions.parse_numbers`.
20pub const Value = union(enum) {
21 null,
22 bool: bool,
23 integer: i64,
24 float: f64,
25 number_string: []const u8,
26 string: []const u8,
27 array: Array,
28 object: ObjectMap,
29
30 pub fn parseFromNumberSlice(s: []const u8) Value {
31 if (!isNumberFormattedLikeAnInteger(s)) {
32 const f = std.fmt.parseFloat(f64, s) catch unreachable;
33 if (std.math.isFinite(f)) {
34 return Value{ .float = f };
35 } else {
36 return Value{ .number_string = s };
37 }
38 }
39 if (std.fmt.parseInt(i64, s, 10)) |i| {
40 return Value{ .integer = i };
41 } else |e| {
42 switch (e) {
43 error.Overflow => return Value{ .number_string = s },
44 error.InvalidCharacter => unreachable,
45 }
46 }
47 }
48
49 pub fn dump(v: Value) void {
50 const stderr = std.debug.lockStderr(&.{});
51 defer std.debug.unlockStderr();
52 json.Stringify.value(v, .{}, &stderr.file_writer.interface) catch return;
53 }
54
55 pub fn jsonStringify(value: @This(), jws: anytype) !void {
56 switch (value) {
57 .null => try jws.write(null),
58 .bool => |inner| try jws.write(inner),
59 .integer => |inner| try jws.write(inner),
60 .float => |inner| try jws.write(inner),
61 .number_string => |inner| try jws.print("{s}", .{inner}),
62 .string => |inner| try jws.write(inner),
63 .array => |inner| try jws.write(inner.items),
64 .object => |inner| {
65 try jws.beginObject();
66 var it = inner.iterator();
67 while (it.next()) |entry| {
68 try jws.objectField(entry.key_ptr.*);
69 try jws.write(entry.value_ptr.*);
70 }
71 try jws.endObject();
72 },
73 }
74 }
75
76 pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) ParseError(@TypeOf(source.*))!@This() {
77 // The grammar of the stack is:
78 // (.array | .object .string)*
79 var stack = Array.init(allocator);
80 defer stack.deinit();
81
82 while (true) {
83 // Assert the stack grammar at the top of the stack.
84 debug.assert(stack.items.len == 0 or
85 stack.items[stack.items.len - 1] == .array or
86 (stack.items[stack.items.len - 2] == .object and stack.items[stack.items.len - 1] == .string));
87
88 switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
89 .allocated_string => |s| {
90 return try handleCompleteValue(&stack, allocator, source, Value{ .string = s }, options) orelse continue;
91 },
92 .allocated_number => |slice| {
93 if (options.parse_numbers) {
94 return try handleCompleteValue(&stack, allocator, source, Value.parseFromNumberSlice(slice), options) orelse continue;
95 } else {
96 return try handleCompleteValue(&stack, allocator, source, Value{ .number_string = slice }, options) orelse continue;
97 }
98 },
99
100 .null => return try handleCompleteValue(&stack, allocator, source, .null, options) orelse continue,
101 .true => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = true }, options) orelse continue,
102 .false => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = false }, options) orelse continue,
103
104 .object_begin => {
105 switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
106 .object_end => return try handleCompleteValue(&stack, allocator, source, Value{ .object = .empty }, options) orelse continue,
107 .allocated_string => |key| {
108 try stack.appendSlice(&[_]Value{
109 Value{ .object = .empty },
110 Value{ .string = key },
111 });
112 },
113 else => unreachable,
114 }
115 },
116 .array_begin => {
117 try stack.append(Value{ .array = Array.init(allocator) });
118 },
119 .array_end => return try handleCompleteValue(&stack, allocator, source, stack.pop().?, options) orelse continue,
120
121 else => unreachable,
122 }
123 }
124 }
125
126 pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() {
127 _ = allocator;
128 _ = options;
129 return source;
130 }
131};
132
133fn handleCompleteValue(stack: *Array, allocator: Allocator, source: anytype, value_: Value, options: ParseOptions) !?Value {
134 if (stack.items.len == 0) return value_;
135 var value = value_;
136 while (true) {
137 // Assert the stack grammar at the top of the stack.
138 debug.assert(stack.items[stack.items.len - 1] == .array or
139 (stack.items[stack.items.len - 2] == .object and stack.items[stack.items.len - 1] == .string));
140 switch (stack.items[stack.items.len - 1]) {
141 .string => |key| {
142 // stack: [..., .object, .string]
143 _ = stack.pop();
144
145 // stack: [..., .object]
146 var object = &stack.items[stack.items.len - 1].object;
147
148 const gop = try object.getOrPut(allocator, key);
149 if (gop.found_existing) {
150 switch (options.duplicate_field_behavior) {
151 .use_first => {},
152 .@"error" => return error.DuplicateField,
153 .use_last => {
154 gop.value_ptr.* = value;
155 },
156 }
157 } else {
158 gop.value_ptr.* = value;
159 }
160
161 // This is an invalid state to leave the stack in,
162 // so we have to process the next token before we return.
163 switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
164 .object_end => {
165 // This object is complete.
166 value = stack.pop().?;
167 // Effectively recurse now that we have a complete value.
168 if (stack.items.len == 0) return value;
169 continue;
170 },
171 .allocated_string => |next_key| {
172 // We've got another key.
173 try stack.append(Value{ .string = next_key });
174 // stack: [..., .object, .string]
175 return null;
176 },
177 else => unreachable,
178 }
179 },
180 .array => |*array| {
181 // stack: [..., .array]
182 try array.append(value);
183 return null;
184 },
185 else => unreachable,
186 }
187 }
188}
189
190test {
191 _ = @import("dynamic_test.zig");
192}