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}