master
1const std = @import("std");
2const debug = std.debug;
3const ArenaAllocator = std.heap.ArenaAllocator;
4const StringArrayHashMap = std.StringArrayHashMap;
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 w, _ = std.debug.lockStderrWriter(&.{});
51 defer std.debug.unlockStderrWriter();
52
53 json.Stringify.value(v, .{}, w) catch return;
54 }
55
56 pub fn jsonStringify(value: @This(), jws: anytype) !void {
57 switch (value) {
58 .null => try jws.write(null),
59 .bool => |inner| try jws.write(inner),
60 .integer => |inner| try jws.write(inner),
61 .float => |inner| try jws.write(inner),
62 .number_string => |inner| try jws.print("{s}", .{inner}),
63 .string => |inner| try jws.write(inner),
64 .array => |inner| try jws.write(inner.items),
65 .object => |inner| {
66 try jws.beginObject();
67 var it = inner.iterator();
68 while (it.next()) |entry| {
69 try jws.objectField(entry.key_ptr.*);
70 try jws.write(entry.value_ptr.*);
71 }
72 try jws.endObject();
73 },
74 }
75 }
76
77 pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) ParseError(@TypeOf(source.*))!@This() {
78 // The grammar of the stack is:
79 // (.array | .object .string)*
80 var stack = Array.init(allocator);
81 defer stack.deinit();
82
83 while (true) {
84 // Assert the stack grammar at the top of the stack.
85 debug.assert(stack.items.len == 0 or
86 stack.items[stack.items.len - 1] == .array or
87 (stack.items[stack.items.len - 2] == .object and stack.items[stack.items.len - 1] == .string));
88
89 switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
90 .allocated_string => |s| {
91 return try handleCompleteValue(&stack, allocator, source, Value{ .string = s }, options) orelse continue;
92 },
93 .allocated_number => |slice| {
94 if (options.parse_numbers) {
95 return try handleCompleteValue(&stack, allocator, source, Value.parseFromNumberSlice(slice), options) orelse continue;
96 } else {
97 return try handleCompleteValue(&stack, allocator, source, Value{ .number_string = slice }, options) orelse continue;
98 }
99 },
100
101 .null => return try handleCompleteValue(&stack, allocator, source, .null, options) orelse continue,
102 .true => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = true }, options) orelse continue,
103 .false => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = false }, options) orelse continue,
104
105 .object_begin => {
106 switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
107 .object_end => return try handleCompleteValue(&stack, allocator, source, Value{ .object = ObjectMap.init(allocator) }, options) orelse continue,
108 .allocated_string => |key| {
109 try stack.appendSlice(&[_]Value{
110 Value{ .object = ObjectMap.init(allocator) },
111 Value{ .string = key },
112 });
113 },
114 else => unreachable,
115 }
116 },
117 .array_begin => {
118 try stack.append(Value{ .array = Array.init(allocator) });
119 },
120 .array_end => return try handleCompleteValue(&stack, allocator, source, stack.pop().?, options) orelse continue,
121
122 else => unreachable,
123 }
124 }
125 }
126
127 pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() {
128 _ = allocator;
129 _ = options;
130 return source;
131 }
132};
133
134fn handleCompleteValue(stack: *Array, allocator: Allocator, source: anytype, value_: Value, options: ParseOptions) !?Value {
135 if (stack.items.len == 0) return value_;
136 var value = value_;
137 while (true) {
138 // Assert the stack grammar at the top of the stack.
139 debug.assert(stack.items[stack.items.len - 1] == .array or
140 (stack.items[stack.items.len - 2] == .object and stack.items[stack.items.len - 1] == .string));
141 switch (stack.items[stack.items.len - 1]) {
142 .string => |key| {
143 // stack: [..., .object, .string]
144 _ = stack.pop();
145
146 // stack: [..., .object]
147 var object = &stack.items[stack.items.len - 1].object;
148
149 const gop = try object.getOrPut(key);
150 if (gop.found_existing) {
151 switch (options.duplicate_field_behavior) {
152 .use_first => {},
153 .@"error" => return error.DuplicateField,
154 .use_last => {
155 gop.value_ptr.* = value;
156 },
157 }
158 } else {
159 gop.value_ptr.* = value;
160 }
161
162 // This is an invalid state to leave the stack in,
163 // so we have to process the next token before we return.
164 switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
165 .object_end => {
166 // This object is complete.
167 value = stack.pop().?;
168 // Effectively recurse now that we have a complete value.
169 if (stack.items.len == 0) return value;
170 continue;
171 },
172 .allocated_string => |next_key| {
173 // We've got another key.
174 try stack.append(Value{ .string = next_key });
175 // stack: [..., .object, .string]
176 return null;
177 },
178 else => unreachable,
179 }
180 },
181 .array => |*array| {
182 // stack: [..., .array]
183 try array.append(value);
184 return null;
185 },
186 else => unreachable,
187 }
188 }
189}
190
191test {
192 _ = @import("dynamic_test.zig");
193}