master
1const std = @import("std");
2const json = std.json;
3const mem = std.mem;
4const testing = std.testing;
5const ArenaAllocator = std.heap.ArenaAllocator;
6const Allocator = std.mem.Allocator;
7const Writer = std.Io.Writer;
8
9const ObjectMap = @import("dynamic.zig").ObjectMap;
10const Array = @import("dynamic.zig").Array;
11const Value = @import("dynamic.zig").Value;
12
13const parseFromSlice = @import("static.zig").parseFromSlice;
14const parseFromSliceLeaky = @import("static.zig").parseFromSliceLeaky;
15const parseFromTokenSource = @import("static.zig").parseFromTokenSource;
16const parseFromValueLeaky = @import("static.zig").parseFromValueLeaky;
17const ParseOptions = @import("static.zig").ParseOptions;
18
19const Scanner = @import("Scanner.zig");
20
21test "json.parser.dynamic" {
22 const s =
23 \\{
24 \\ "Image": {
25 \\ "Width": 800,
26 \\ "Height": 600,
27 \\ "Title": "View from 15th Floor",
28 \\ "Thumbnail": {
29 \\ "Url": "http://www.example.com/image/481989943",
30 \\ "Height": 125,
31 \\ "Width": 100
32 \\ },
33 \\ "Animated" : false,
34 \\ "IDs": [116, 943, 234, 38793],
35 \\ "ArrayOfObject": [{"n": "m"}],
36 \\ "double": 1.3412,
37 \\ "LargeInt": 18446744073709551615
38 \\ }
39 \\}
40 ;
41
42 var parsed = try parseFromSlice(Value, testing.allocator, s, .{});
43 defer parsed.deinit();
44
45 var root = parsed.value;
46
47 var image = root.object.get("Image").?;
48
49 const width = image.object.get("Width").?;
50 try testing.expect(width.integer == 800);
51
52 const height = image.object.get("Height").?;
53 try testing.expect(height.integer == 600);
54
55 const title = image.object.get("Title").?;
56 try testing.expect(mem.eql(u8, title.string, "View from 15th Floor"));
57
58 const animated = image.object.get("Animated").?;
59 try testing.expect(animated.bool == false);
60
61 const array_of_object = image.object.get("ArrayOfObject").?;
62 try testing.expect(array_of_object.array.items.len == 1);
63
64 const obj0 = array_of_object.array.items[0].object.get("n").?;
65 try testing.expect(mem.eql(u8, obj0.string, "m"));
66
67 const double = image.object.get("double").?;
68 try testing.expect(double.float == 1.3412);
69
70 const large_int = image.object.get("LargeInt").?;
71 try testing.expect(mem.eql(u8, large_int.number_string, "18446744073709551615"));
72}
73
74test "write json then parse it" {
75 var out_buffer: [1000]u8 = undefined;
76 var fixed_writer: Writer = .fixed(&out_buffer);
77 var jw: json.Stringify = .{ .writer = &fixed_writer, .options = .{} };
78
79 try jw.beginObject();
80
81 try jw.objectField("f");
82 try jw.write(false);
83
84 try jw.objectField("t");
85 try jw.write(true);
86
87 try jw.objectField("int");
88 try jw.write(1234);
89
90 try jw.objectField("array");
91 try jw.beginArray();
92 try jw.write(null);
93 try jw.write(12.34);
94 try jw.endArray();
95
96 try jw.objectField("str");
97 try jw.write("hello");
98
99 try jw.endObject();
100
101 var fbs: std.Io.Reader = .fixed(fixed_writer.buffered());
102 var json_reader: Scanner.Reader = .init(testing.allocator, &fbs);
103 defer json_reader.deinit();
104 var parsed = try parseFromTokenSource(Value, testing.allocator, &json_reader, .{});
105 defer parsed.deinit();
106
107 try testing.expect(parsed.value.object.get("f").?.bool == false);
108 try testing.expect(parsed.value.object.get("t").?.bool == true);
109 try testing.expect(parsed.value.object.get("int").?.integer == 1234);
110 try testing.expect(parsed.value.object.get("array").?.array.items[0].null == {});
111 try testing.expect(parsed.value.object.get("array").?.array.items[1].float == 12.34);
112 try testing.expect(mem.eql(u8, parsed.value.object.get("str").?.string, "hello"));
113}
114
115fn testParse(allocator: std.mem.Allocator, json_str: []const u8) !Value {
116 return parseFromSliceLeaky(Value, allocator, json_str, .{});
117}
118
119test "parsing empty string gives appropriate error" {
120 var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
121 defer arena_allocator.deinit();
122 try testing.expectError(error.UnexpectedEndOfInput, testParse(arena_allocator.allocator(), ""));
123}
124
125test "Value.array allocator should still be usable after parsing" {
126 var parsed = try parseFromSlice(Value, std.testing.allocator, "[]", .{});
127 defer parsed.deinit();
128
129 // Allocation should succeed
130 var i: usize = 0;
131 while (i < 100) : (i += 1) {
132 try parsed.value.array.append(Value{ .integer = 100 });
133 }
134 try testing.expectEqual(parsed.value.array.items.len, 100);
135}
136
137test "integer after float has proper type" {
138 var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
139 defer arena_allocator.deinit();
140 const parsed = try testParse(arena_allocator.allocator(),
141 \\{
142 \\ "float": 3.14,
143 \\ "ints": [1, 2, 3]
144 \\}
145 );
146 try std.testing.expect(parsed.object.get("ints").?.array.items[0] == .integer);
147}
148
149test "ParseOptions.parse_numbers prevents parsing when false" {
150 var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
151 defer arena_allocator.deinit();
152 const parsed = try parseFromSliceLeaky(Value, arena_allocator.allocator(),
153 \\{
154 \\ "float": 3.14,
155 \\ "int": 3
156 \\}
157 , .{ .parse_numbers = false });
158 try std.testing.expect(parsed.object.get("float").? == .number_string);
159 try std.testing.expect(parsed.object.get("int").? == .number_string);
160}
161
162test "escaped characters" {
163 var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
164 defer arena_allocator.deinit();
165 const input =
166 \\{
167 \\ "backslash": "\\",
168 \\ "forwardslash": "\/",
169 \\ "newline": "\n",
170 \\ "carriagereturn": "\r",
171 \\ "tab": "\t",
172 \\ "formfeed": "\f",
173 \\ "backspace": "\b",
174 \\ "doublequote": "\"",
175 \\ "unicode": "\u0105",
176 \\ "surrogatepair": "\ud83d\ude02"
177 \\}
178 ;
179
180 const obj = (try testParse(arena_allocator.allocator(), input)).object;
181
182 try testing.expectEqualSlices(u8, obj.get("backslash").?.string, "\\");
183 try testing.expectEqualSlices(u8, obj.get("forwardslash").?.string, "/");
184 try testing.expectEqualSlices(u8, obj.get("newline").?.string, "\n");
185 try testing.expectEqualSlices(u8, obj.get("carriagereturn").?.string, "\r");
186 try testing.expectEqualSlices(u8, obj.get("tab").?.string, "\t");
187 try testing.expectEqualSlices(u8, obj.get("formfeed").?.string, "\x0C");
188 try testing.expectEqualSlices(u8, obj.get("backspace").?.string, "\x08");
189 try testing.expectEqualSlices(u8, obj.get("doublequote").?.string, "\"");
190 try testing.expectEqualSlices(u8, obj.get("unicode").?.string, "Ä…");
191 try testing.expectEqualSlices(u8, obj.get("surrogatepair").?.string, "😂");
192}
193
194test "Value with duplicate fields" {
195 var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
196 defer arena_allocator.deinit();
197
198 const doc =
199 \\{
200 \\ "abc": 0,
201 \\ "abc": 1
202 \\}
203 ;
204
205 try testing.expectError(error.DuplicateField, parseFromSliceLeaky(std.json.Value, arena_allocator.allocator(), doc, .{
206 .duplicate_field_behavior = .@"error",
207 }));
208
209 const first = try parseFromSliceLeaky(std.json.Value, arena_allocator.allocator(), doc, .{
210 .duplicate_field_behavior = .use_first,
211 });
212 try testing.expectEqual(@as(usize, 1), first.object.count());
213 try testing.expectEqual(@as(i64, 0), first.object.get("abc").?.integer);
214
215 const last = try parseFromSliceLeaky(std.json.Value, arena_allocator.allocator(), doc, .{
216 .duplicate_field_behavior = .use_last,
217 });
218 try testing.expectEqual(@as(usize, 1), last.object.count());
219 try testing.expectEqual(@as(i64, 1), last.object.get("abc").?.integer);
220}
221
222test "Value.jsonStringify" {
223 var vals = [_]Value{
224 .{ .integer = 1 },
225 .{ .integer = 2 },
226 .{ .number_string = "3" },
227 };
228 var obj = ObjectMap.init(testing.allocator);
229 defer obj.deinit();
230 try obj.putNoClobber("a", .{ .string = "b" });
231 const array = [_]Value{
232 .null,
233 .{ .bool = true },
234 .{ .integer = 42 },
235 .{ .number_string = "43" },
236 .{ .float = 42 },
237 .{ .string = "weeee" },
238 .{ .array = Array.fromOwnedSlice(undefined, &vals) },
239 .{ .object = obj },
240 };
241 var buffer: [0x1000]u8 = undefined;
242 var fixed_writer: Writer = .fixed(&buffer);
243
244 var jw: json.Stringify = .{ .writer = &fixed_writer, .options = .{ .whitespace = .indent_1 } };
245 try jw.write(array);
246
247 const expected =
248 \\[
249 \\ null,
250 \\ true,
251 \\ 42,
252 \\ 43,
253 \\ 42,
254 \\ "weeee",
255 \\ [
256 \\ 1,
257 \\ 2,
258 \\ 3
259 \\ ],
260 \\ {
261 \\ "a": "b"
262 \\ }
263 \\]
264 ;
265 try testing.expectEqualStrings(expected, fixed_writer.buffered());
266}
267
268test "parseFromValue(std.json.Value,...)" {
269 const str =
270 \\{
271 \\ "int": 32,
272 \\ "float": 3.2,
273 \\ "str": "str",
274 \\ "array": [3, 2],
275 \\ "object": {}
276 \\}
277 ;
278
279 const parsed_tree = try parseFromSlice(Value, testing.allocator, str, .{});
280 defer parsed_tree.deinit();
281 const tree = try parseFromValueLeaky(Value, parsed_tree.arena.allocator(), parsed_tree.value, .{});
282 try testing.expect(std.meta.eql(parsed_tree.value, tree));
283}
284
285test "polymorphic parsing" {
286 if (true) return error.SkipZigTest; // See https://github.com/ziglang/zig/issues/16108
287 const doc =
288 \\{ "type": "div",
289 \\ "color": "blue",
290 \\ "children": [
291 \\ { "type": "button",
292 \\ "caption": "OK" },
293 \\ { "type": "button",
294 \\ "caption": "Cancel" } ] }
295 ;
296 const Node = union(enum) {
297 div: Div,
298 button: Button,
299 const Self = @This();
300 const Div = struct {
301 color: enum { red, blue },
302 children: []Self,
303 };
304 const Button = struct {
305 caption: []const u8,
306 };
307
308 pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() {
309 if (source != .object) return error.UnexpectedToken;
310 const type_value = source.object.get("type") orelse return error.UnexpectedToken; // Missing "type" field.
311 if (type_value != .string) return error.UnexpectedToken; // "type" expected to be string.
312 const type_str = type_value.string;
313 var child_options = options;
314 child_options.ignore_unknown_fields = true;
315 if (std.mem.eql(u8, type_str, "div")) return .{ .div = try parseFromValueLeaky(Div, allocator, source, child_options) };
316 if (std.mem.eql(u8, type_str, "button")) return .{ .button = try parseFromValueLeaky(Button, allocator, source, child_options) };
317 return error.UnexpectedToken; // unknown type.
318 }
319 };
320
321 var arena = ArenaAllocator.init(testing.allocator);
322 defer arena.deinit();
323 const dynamic_tree = try parseFromSliceLeaky(Value, arena.allocator(), doc, .{});
324 const tree = try parseFromValueLeaky(Node, arena.allocator(), dynamic_tree, .{});
325
326 try testing.expect(tree.div.color == .blue);
327 try testing.expectEqualStrings("Cancel", tree.div.children[1].button.caption);
328}
329
330test "long object value" {
331 const value = "01234567890123456789";
332 const doc = "{\"key\":\"" ++ value ++ "\"}";
333 var fbs: std.Io.Reader = .fixed(doc);
334 var reader = smallBufferJsonReader(testing.allocator, &fbs);
335 defer reader.deinit();
336 var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{});
337 defer parsed.deinit();
338
339 try testing.expectEqualStrings(value, parsed.value.object.get("key").?.string);
340}
341
342test "ParseOptions.max_value_len" {
343 var arena = ArenaAllocator.init(testing.allocator);
344 defer arena.deinit();
345
346 const str = "\"0800fc577294c34e0b28ad2839435945\"";
347
348 const value = try std.json.parseFromSliceLeaky(std.json.Value, arena.allocator(), str, .{ .max_value_len = 32 });
349
350 try testing.expect(value == .string);
351 try testing.expect(value.string.len == 32);
352
353 try testing.expectError(error.ValueTooLong, std.json.parseFromSliceLeaky(std.json.Value, arena.allocator(), str, .{ .max_value_len = 31 }));
354}
355
356test "many object keys" {
357 const doc =
358 \\{
359 \\ "k1": "v1",
360 \\ "k2": "v2",
361 \\ "k3": "v3",
362 \\ "k4": "v4",
363 \\ "k5": "v5"
364 \\}
365 ;
366 var fbs: std.Io.Reader = .fixed(doc);
367 var reader = smallBufferJsonReader(testing.allocator, &fbs);
368 defer reader.deinit();
369 var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{});
370 defer parsed.deinit();
371
372 try testing.expectEqualStrings("v1", parsed.value.object.get("k1").?.string);
373 try testing.expectEqualStrings("v2", parsed.value.object.get("k2").?.string);
374 try testing.expectEqualStrings("v3", parsed.value.object.get("k3").?.string);
375 try testing.expectEqualStrings("v4", parsed.value.object.get("k4").?.string);
376 try testing.expectEqualStrings("v5", parsed.value.object.get("k5").?.string);
377}
378
379test "negative zero" {
380 const doc = "-0";
381 var fbs: std.Io.Reader = .fixed(doc);
382 var reader = smallBufferJsonReader(testing.allocator, &fbs);
383 defer reader.deinit();
384 var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{});
385 defer parsed.deinit();
386
387 try testing.expect(std.math.isNegativeZero(parsed.value.float));
388}
389
390fn smallBufferJsonReader(allocator: Allocator, io_reader: anytype) Scanner.Reader {
391 return .init(allocator, io_reader);
392}