mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 14:23:09 +00:00
This is consistent with `JSON.parse("-0")` in JavaScript, RFC 8259
doesn't specifically mention what to do in this case.
If a negative zero is encoded the intention is likely to preserve the
sign.
356 lines
12 KiB
Zig
356 lines
12 KiB
Zig
const std = @import("std");
|
|
const mem = std.mem;
|
|
const testing = std.testing;
|
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
const ObjectMap = @import("dynamic.zig").ObjectMap;
|
|
const Array = @import("dynamic.zig").Array;
|
|
const Value = @import("dynamic.zig").Value;
|
|
|
|
const parseFromSlice = @import("static.zig").parseFromSlice;
|
|
const parseFromSliceLeaky = @import("static.zig").parseFromSliceLeaky;
|
|
const parseFromTokenSource = @import("static.zig").parseFromTokenSource;
|
|
const parseFromValueLeaky = @import("static.zig").parseFromValueLeaky;
|
|
const ParseOptions = @import("static.zig").ParseOptions;
|
|
|
|
const jsonReader = @import("scanner.zig").reader;
|
|
const JsonReader = @import("scanner.zig").Reader;
|
|
|
|
test "json.parser.dynamic" {
|
|
const s =
|
|
\\{
|
|
\\ "Image": {
|
|
\\ "Width": 800,
|
|
\\ "Height": 600,
|
|
\\ "Title": "View from 15th Floor",
|
|
\\ "Thumbnail": {
|
|
\\ "Url": "http://www.example.com/image/481989943",
|
|
\\ "Height": 125,
|
|
\\ "Width": 100
|
|
\\ },
|
|
\\ "Animated" : false,
|
|
\\ "IDs": [116, 943, 234, 38793],
|
|
\\ "ArrayOfObject": [{"n": "m"}],
|
|
\\ "double": 1.3412,
|
|
\\ "LargeInt": 18446744073709551615
|
|
\\ }
|
|
\\}
|
|
;
|
|
|
|
var parsed = try parseFromSlice(Value, testing.allocator, s, .{});
|
|
defer parsed.deinit();
|
|
|
|
var root = parsed.value;
|
|
|
|
var image = root.object.get("Image").?;
|
|
|
|
const width = image.object.get("Width").?;
|
|
try testing.expect(width.integer == 800);
|
|
|
|
const height = image.object.get("Height").?;
|
|
try testing.expect(height.integer == 600);
|
|
|
|
const title = image.object.get("Title").?;
|
|
try testing.expect(mem.eql(u8, title.string, "View from 15th Floor"));
|
|
|
|
const animated = image.object.get("Animated").?;
|
|
try testing.expect(animated.bool == false);
|
|
|
|
const array_of_object = image.object.get("ArrayOfObject").?;
|
|
try testing.expect(array_of_object.array.items.len == 1);
|
|
|
|
const obj0 = array_of_object.array.items[0].object.get("n").?;
|
|
try testing.expect(mem.eql(u8, obj0.string, "m"));
|
|
|
|
const double = image.object.get("double").?;
|
|
try testing.expect(double.float == 1.3412);
|
|
|
|
const large_int = image.object.get("LargeInt").?;
|
|
try testing.expect(mem.eql(u8, large_int.number_string, "18446744073709551615"));
|
|
}
|
|
|
|
const writeStream = @import("./stringify.zig").writeStream;
|
|
test "write json then parse it" {
|
|
var out_buffer: [1000]u8 = undefined;
|
|
|
|
var fixed_buffer_stream = std.io.fixedBufferStream(&out_buffer);
|
|
const out_stream = fixed_buffer_stream.writer();
|
|
var jw = writeStream(out_stream, .{});
|
|
defer jw.deinit();
|
|
|
|
try jw.beginObject();
|
|
|
|
try jw.objectField("f");
|
|
try jw.write(false);
|
|
|
|
try jw.objectField("t");
|
|
try jw.write(true);
|
|
|
|
try jw.objectField("int");
|
|
try jw.write(1234);
|
|
|
|
try jw.objectField("array");
|
|
try jw.beginArray();
|
|
try jw.write(null);
|
|
try jw.write(12.34);
|
|
try jw.endArray();
|
|
|
|
try jw.objectField("str");
|
|
try jw.write("hello");
|
|
|
|
try jw.endObject();
|
|
|
|
fixed_buffer_stream = std.io.fixedBufferStream(fixed_buffer_stream.getWritten());
|
|
var json_reader = jsonReader(testing.allocator, fixed_buffer_stream.reader());
|
|
defer json_reader.deinit();
|
|
var parsed = try parseFromTokenSource(Value, testing.allocator, &json_reader, .{});
|
|
defer parsed.deinit();
|
|
|
|
try testing.expect(parsed.value.object.get("f").?.bool == false);
|
|
try testing.expect(parsed.value.object.get("t").?.bool == true);
|
|
try testing.expect(parsed.value.object.get("int").?.integer == 1234);
|
|
try testing.expect(parsed.value.object.get("array").?.array.items[0].null == {});
|
|
try testing.expect(parsed.value.object.get("array").?.array.items[1].float == 12.34);
|
|
try testing.expect(mem.eql(u8, parsed.value.object.get("str").?.string, "hello"));
|
|
}
|
|
|
|
fn testParse(allocator: std.mem.Allocator, json_str: []const u8) !Value {
|
|
return parseFromSliceLeaky(Value, allocator, json_str, .{});
|
|
}
|
|
|
|
test "parsing empty string gives appropriate error" {
|
|
var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena_allocator.deinit();
|
|
try testing.expectError(error.UnexpectedEndOfInput, testParse(arena_allocator.allocator(), ""));
|
|
}
|
|
|
|
test "Value.array allocator should still be usable after parsing" {
|
|
var parsed = try parseFromSlice(Value, std.testing.allocator, "[]", .{});
|
|
defer parsed.deinit();
|
|
|
|
// Allocation should succeed
|
|
var i: usize = 0;
|
|
while (i < 100) : (i += 1) {
|
|
try parsed.value.array.append(Value{ .integer = 100 });
|
|
}
|
|
try testing.expectEqual(parsed.value.array.items.len, 100);
|
|
}
|
|
|
|
test "integer after float has proper type" {
|
|
var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena_allocator.deinit();
|
|
const parsed = try testParse(arena_allocator.allocator(),
|
|
\\{
|
|
\\ "float": 3.14,
|
|
\\ "ints": [1, 2, 3]
|
|
\\}
|
|
);
|
|
try std.testing.expect(parsed.object.get("ints").?.array.items[0] == .integer);
|
|
}
|
|
|
|
test "escaped characters" {
|
|
var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena_allocator.deinit();
|
|
const input =
|
|
\\{
|
|
\\ "backslash": "\\",
|
|
\\ "forwardslash": "\/",
|
|
\\ "newline": "\n",
|
|
\\ "carriagereturn": "\r",
|
|
\\ "tab": "\t",
|
|
\\ "formfeed": "\f",
|
|
\\ "backspace": "\b",
|
|
\\ "doublequote": "\"",
|
|
\\ "unicode": "\u0105",
|
|
\\ "surrogatepair": "\ud83d\ude02"
|
|
\\}
|
|
;
|
|
|
|
const obj = (try testParse(arena_allocator.allocator(), input)).object;
|
|
|
|
try testing.expectEqualSlices(u8, obj.get("backslash").?.string, "\\");
|
|
try testing.expectEqualSlices(u8, obj.get("forwardslash").?.string, "/");
|
|
try testing.expectEqualSlices(u8, obj.get("newline").?.string, "\n");
|
|
try testing.expectEqualSlices(u8, obj.get("carriagereturn").?.string, "\r");
|
|
try testing.expectEqualSlices(u8, obj.get("tab").?.string, "\t");
|
|
try testing.expectEqualSlices(u8, obj.get("formfeed").?.string, "\x0C");
|
|
try testing.expectEqualSlices(u8, obj.get("backspace").?.string, "\x08");
|
|
try testing.expectEqualSlices(u8, obj.get("doublequote").?.string, "\"");
|
|
try testing.expectEqualSlices(u8, obj.get("unicode").?.string, "Ä…");
|
|
try testing.expectEqualSlices(u8, obj.get("surrogatepair").?.string, "😂");
|
|
}
|
|
|
|
test "Value.jsonStringify" {
|
|
var vals = [_]Value{
|
|
.{ .integer = 1 },
|
|
.{ .integer = 2 },
|
|
.{ .number_string = "3" },
|
|
};
|
|
var obj = ObjectMap.init(testing.allocator);
|
|
defer obj.deinit();
|
|
try obj.putNoClobber("a", .{ .string = "b" });
|
|
var array = [_]Value{
|
|
Value.null,
|
|
Value{ .bool = true },
|
|
Value{ .integer = 42 },
|
|
Value{ .number_string = "43" },
|
|
Value{ .float = 42 },
|
|
Value{ .string = "weeee" },
|
|
Value{ .array = Array.fromOwnedSlice(undefined, &vals) },
|
|
Value{ .object = obj },
|
|
};
|
|
var buffer: [0x1000]u8 = undefined;
|
|
var fbs = std.io.fixedBufferStream(&buffer);
|
|
|
|
var jw = writeStream(fbs.writer(), .{ .whitespace = .indent_1 });
|
|
defer jw.deinit();
|
|
try jw.write(array);
|
|
|
|
const expected =
|
|
\\[
|
|
\\ null,
|
|
\\ true,
|
|
\\ 42,
|
|
\\ 43,
|
|
\\ 4.2e+01,
|
|
\\ "weeee",
|
|
\\ [
|
|
\\ 1,
|
|
\\ 2,
|
|
\\ 3
|
|
\\ ],
|
|
\\ {
|
|
\\ "a": "b"
|
|
\\ }
|
|
\\]
|
|
;
|
|
try testing.expectEqualSlices(u8, expected, fbs.getWritten());
|
|
}
|
|
|
|
test "parseFromValue(std.json.Value,...)" {
|
|
const str =
|
|
\\{
|
|
\\ "int": 32,
|
|
\\ "float": 3.2,
|
|
\\ "str": "str",
|
|
\\ "array": [3, 2],
|
|
\\ "object": {}
|
|
\\}
|
|
;
|
|
|
|
const parsed_tree = try parseFromSlice(Value, testing.allocator, str, .{});
|
|
defer parsed_tree.deinit();
|
|
const tree = try parseFromValueLeaky(Value, parsed_tree.arena.allocator(), parsed_tree.value, .{});
|
|
try testing.expect(std.meta.eql(parsed_tree.value, tree));
|
|
}
|
|
|
|
test "polymorphic parsing" {
|
|
if (true) return error.SkipZigTest; // See https://github.com/ziglang/zig/issues/16108
|
|
const doc =
|
|
\\{ "type": "div",
|
|
\\ "color": "blue",
|
|
\\ "children": [
|
|
\\ { "type": "button",
|
|
\\ "caption": "OK" },
|
|
\\ { "type": "button",
|
|
\\ "caption": "Cancel" } ] }
|
|
;
|
|
const Node = union(enum) {
|
|
div: Div,
|
|
button: Button,
|
|
const Self = @This();
|
|
const Div = struct {
|
|
color: enum { red, blue },
|
|
children: []Self,
|
|
};
|
|
const Button = struct {
|
|
caption: []const u8,
|
|
};
|
|
|
|
pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() {
|
|
if (source != .object) return error.UnexpectedToken;
|
|
const type_value = source.object.get("type") orelse return error.UnexpectedToken; // Missing "type" field.
|
|
if (type_value != .string) return error.UnexpectedToken; // "type" expected to be string.
|
|
const type_str = type_value.string;
|
|
var child_options = options;
|
|
child_options.ignore_unknown_fields = true;
|
|
if (std.mem.eql(u8, type_str, "div")) return .{ .div = try parseFromValueLeaky(Div, allocator, source, child_options) };
|
|
if (std.mem.eql(u8, type_str, "button")) return .{ .button = try parseFromValueLeaky(Button, allocator, source, child_options) };
|
|
return error.UnexpectedToken; // unknown type.
|
|
}
|
|
};
|
|
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
const dynamic_tree = try parseFromSliceLeaky(Value, arena.allocator(), doc, .{});
|
|
const tree = try parseFromValueLeaky(Node, arena.allocator(), dynamic_tree, .{});
|
|
|
|
try testing.expect(tree.div.color == .blue);
|
|
try testing.expectEqualStrings("Cancel", tree.div.children[1].button.caption);
|
|
}
|
|
|
|
test "long object value" {
|
|
const value = "01234567890123456789";
|
|
const doc = "{\"key\":\"" ++ value ++ "\"}";
|
|
var fbs = std.io.fixedBufferStream(doc);
|
|
var reader = smallBufferJsonReader(testing.allocator, fbs.reader());
|
|
defer reader.deinit();
|
|
var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{});
|
|
defer parsed.deinit();
|
|
|
|
try testing.expectEqualStrings(value, parsed.value.object.get("key").?.string);
|
|
}
|
|
|
|
test "ParseOptions.max_value_len" {
|
|
var arena = ArenaAllocator.init(testing.allocator);
|
|
defer arena.deinit();
|
|
|
|
const str = "\"0800fc577294c34e0b28ad2839435945\"";
|
|
|
|
const value = try std.json.parseFromSliceLeaky(std.json.Value, arena.allocator(), str, .{ .max_value_len = 32 });
|
|
|
|
try testing.expect(value == .string);
|
|
try testing.expect(value.string.len == 32);
|
|
|
|
try testing.expectError(error.ValueTooLong, std.json.parseFromSliceLeaky(std.json.Value, arena.allocator(), str, .{ .max_value_len = 31 }));
|
|
}
|
|
|
|
test "many object keys" {
|
|
const doc =
|
|
\\{
|
|
\\ "k1": "v1",
|
|
\\ "k2": "v2",
|
|
\\ "k3": "v3",
|
|
\\ "k4": "v4",
|
|
\\ "k5": "v5"
|
|
\\}
|
|
;
|
|
var fbs = std.io.fixedBufferStream(doc);
|
|
var reader = smallBufferJsonReader(testing.allocator, fbs.reader());
|
|
defer reader.deinit();
|
|
var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{});
|
|
defer parsed.deinit();
|
|
|
|
try testing.expectEqualStrings("v1", parsed.value.object.get("k1").?.string);
|
|
try testing.expectEqualStrings("v2", parsed.value.object.get("k2").?.string);
|
|
try testing.expectEqualStrings("v3", parsed.value.object.get("k3").?.string);
|
|
try testing.expectEqualStrings("v4", parsed.value.object.get("k4").?.string);
|
|
try testing.expectEqualStrings("v5", parsed.value.object.get("k5").?.string);
|
|
}
|
|
|
|
test "negative zero" {
|
|
const doc = "-0";
|
|
var fbs = std.io.fixedBufferStream(doc);
|
|
var reader = smallBufferJsonReader(testing.allocator, fbs.reader());
|
|
defer reader.deinit();
|
|
var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{});
|
|
defer parsed.deinit();
|
|
|
|
try testing.expect(parsed.value.float == 0 and std.math.signbit(parsed.value.float));
|
|
}
|
|
|
|
fn smallBufferJsonReader(allocator: Allocator, io_reader: anytype) JsonReader(16, @TypeOf(io_reader)) {
|
|
return JsonReader(16, @TypeOf(io_reader)).init(allocator, io_reader);
|
|
}
|