zig/lib/std/json/static_test.zig
Gregory Anders cab9da35bd std: enable FailingAllocator to fail on resize
Now that allocator.resize() is allowed to fail, programs may wish to
test code paths that handle resize() failure. The simplest way to do
this now is to replace the vtable of the testing allocator with one
that uses Allocator.noResize for the 'resize' function pointer.

An alternative way to support this testing capability is to augment the
FailingAllocator (which is already useful for testing allocation failure
scenarios) to intentionally fail on calls to resize(). To do this, add a
'resize_fail_index' parameter to the FailingAllocator that causes
resize() to fail after the given number of calls.
2023-09-06 19:06:32 +03:00

927 lines
30 KiB
Zig

const std = @import("std");
const testing = std.testing;
const ArenaAllocator = std.heap.ArenaAllocator;
const Allocator = std.mem.Allocator;
const parseFromSlice = @import("./static.zig").parseFromSlice;
const parseFromSliceLeaky = @import("./static.zig").parseFromSliceLeaky;
const parseFromTokenSource = @import("./static.zig").parseFromTokenSource;
const parseFromTokenSourceLeaky = @import("./static.zig").parseFromTokenSourceLeaky;
const innerParse = @import("./static.zig").innerParse;
const parseFromValue = @import("./static.zig").parseFromValue;
const parseFromValueLeaky = @import("./static.zig").parseFromValueLeaky;
const ParseOptions = @import("./static.zig").ParseOptions;
const JsonScanner = @import("./scanner.zig").Scanner;
const jsonReader = @import("./scanner.zig").reader;
const Diagnostics = @import("./scanner.zig").Diagnostics;
const Value = @import("./dynamic.zig").Value;
const Primitives = struct {
bool: bool,
// f16, f80, f128: don't work in std.fmt.parseFloat(T).
f32: f32,
f64: f64,
u0: u0,
i0: i0,
u1: u1,
i1: i1,
u8: u8,
i8: i8,
i130: i130,
};
const primitives_0 = Primitives{
.bool = false,
.f32 = 0,
.f64 = 0,
.u0 = 0,
.i0 = 0,
.u1 = 0,
.i1 = 0,
.u8 = 0,
.i8 = 0,
.i130 = 0,
};
const primitives_0_doc_0 =
\\{
\\ "bool": false,
\\ "f32": 0,
\\ "f64": 0,
\\ "u0": 0,
\\ "i0": 0,
\\ "u1": 0,
\\ "i1": 0,
\\ "u8": 0,
\\ "i8": 0,
\\ "i130": 0
\\}
;
const primitives_0_doc_1 = // looks like a float.
\\{
\\ "bool": false,
\\ "f32": 0.0,
\\ "f64": 0.0,
\\ "u0": 0.0,
\\ "i0": 0.0,
\\ "u1": 0.0,
\\ "i1": 0.0,
\\ "u8": 0.0,
\\ "i8": 0.0,
\\ "i130": 0.0
\\}
;
const primitives_1 = Primitives{
.bool = true,
.f32 = 1073741824,
.f64 = 1152921504606846976,
.u0 = 0,
.i0 = 0,
.u1 = 1,
.i1 = -1,
.u8 = 255,
.i8 = -128,
.i130 = -680564733841876926926749214863536422911,
};
const primitives_1_doc_0 =
\\{
\\ "bool": true,
\\ "f32": 1073741824,
\\ "f64": 1152921504606846976,
\\ "u0": 0,
\\ "i0": 0,
\\ "u1": 1,
\\ "i1": -1,
\\ "u8": 255,
\\ "i8": -128,
\\ "i130": -680564733841876926926749214863536422911
\\}
;
const primitives_1_doc_1 = // float rounding.
\\{
\\ "bool": true,
\\ "f32": 1073741825,
\\ "f64": 1152921504606846977,
\\ "u0": 0,
\\ "i0": 0,
\\ "u1": 1,
\\ "i1": -1,
\\ "u8": 255,
\\ "i8": -128,
\\ "i130": -680564733841876926926749214863536422911
\\}
;
const Aggregates = struct {
optional: ?i32,
array: [4]i32,
vector: @Vector(4, i32),
pointer: *i32,
pointer_const: *const i32,
slice: []i32,
slice_const: []const i32,
slice_sentinel: [:0]i32,
slice_sentinel_const: [:0]const i32,
};
var zero: i32 = 0;
const zero_const: i32 = 0;
var array_of_zeros: [4:0]i32 = [_:0]i32{ 0, 0, 0, 0 };
var one: i32 = 1;
const one_const: i32 = 1;
var array_countdown: [4:0]i32 = [_:0]i32{ 4, 3, 2, 1 };
const aggregates_0 = Aggregates{
.optional = null,
.array = [4]i32{ 0, 0, 0, 0 },
.vector = @Vector(4, i32){ 0, 0, 0, 0 },
.pointer = &zero,
.pointer_const = &zero_const,
.slice = array_of_zeros[0..0],
.slice_const = &[_]i32{},
.slice_sentinel = array_of_zeros[0..0 :0],
.slice_sentinel_const = &[_:0]i32{},
};
const aggregates_0_doc =
\\{
\\ "optional": null,
\\ "array": [0, 0, 0, 0],
\\ "vector": [0, 0, 0, 0],
\\ "pointer": 0,
\\ "pointer_const": 0,
\\ "slice": [],
\\ "slice_const": [],
\\ "slice_sentinel": [],
\\ "slice_sentinel_const": []
\\}
;
const aggregates_1 = Aggregates{
.optional = 1,
.array = [4]i32{ 1, 2, 3, 4 },
.vector = @Vector(4, i32){ 1, 2, 3, 4 },
.pointer = &one,
.pointer_const = &one_const,
.slice = array_countdown[0..],
.slice_const = array_countdown[0..],
.slice_sentinel = array_countdown[0.. :0],
.slice_sentinel_const = array_countdown[0.. :0],
};
const aggregates_1_doc =
\\{
\\ "optional": 1,
\\ "array": [1, 2, 3, 4],
\\ "vector": [1, 2, 3, 4],
\\ "pointer": 1,
\\ "pointer_const": 1,
\\ "slice": [4, 3, 2, 1],
\\ "slice_const": [4, 3, 2, 1],
\\ "slice_sentinel": [4, 3, 2, 1],
\\ "slice_sentinel_const": [4, 3, 2, 1]
\\}
;
const Strings = struct {
slice_u8: []u8,
slice_const_u8: []const u8,
array_u8: [4]u8,
slice_sentinel_u8: [:0]u8,
slice_const_sentinel_u8: [:0]const u8,
array_sentinel_u8: [4:0]u8,
};
var abcd = [4:0]u8{ 'a', 'b', 'c', 'd' };
const strings_0 = Strings{
.slice_u8 = abcd[0..],
.slice_const_u8 = "abcd",
.array_u8 = [4]u8{ 'a', 'b', 'c', 'd' },
.slice_sentinel_u8 = abcd[0..],
.slice_const_sentinel_u8 = "abcd",
.array_sentinel_u8 = [4:0]u8{ 'a', 'b', 'c', 'd' },
};
const strings_0_doc_0 =
\\{
\\ "slice_u8": "abcd",
\\ "slice_const_u8": "abcd",
\\ "array_u8": "abcd",
\\ "slice_sentinel_u8": "abcd",
\\ "slice_const_sentinel_u8": "abcd",
\\ "array_sentinel_u8": "abcd"
\\}
;
const strings_0_doc_1 =
\\{
\\ "slice_u8": [97, 98, 99, 100],
\\ "slice_const_u8": [97, 98, 99, 100],
\\ "array_u8": [97, 98, 99, 100],
\\ "slice_sentinel_u8": [97, 98, 99, 100],
\\ "slice_const_sentinel_u8": [97, 98, 99, 100],
\\ "array_sentinel_u8": [97, 98, 99, 100]
\\}
;
const Subnamespaces = struct {
packed_struct: packed struct { a: u32, b: u32 },
union_enum: union(enum) { i: i32, s: []const u8, v },
inferred_enum: enum { a, b },
explicit_enum: enum(u8) { a = 0, b = 1 },
custom_struct: struct {
pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) !@This() {
_ = allocator;
_ = options;
try source.skipValue();
return @This(){};
}
pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() {
_ = allocator;
_ = source;
_ = options;
return @This(){};
}
},
custom_union: union(enum) {
i: i32,
s: []const u8,
pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) !@This() {
_ = allocator;
_ = options;
try source.skipValue();
return @This(){ .i = 0 };
}
pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() {
_ = allocator;
_ = source;
_ = options;
return @This(){ .i = 0 };
}
},
custom_enum: enum {
a,
b,
pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) !@This() {
_ = allocator;
_ = options;
try source.skipValue();
return .a;
}
pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() {
_ = allocator;
_ = source;
_ = options;
return .a;
}
},
};
const subnamespaces_0 = Subnamespaces{
.packed_struct = .{ .a = 0, .b = 0 },
.union_enum = .{ .i = 0 },
.inferred_enum = .a,
.explicit_enum = .a,
.custom_struct = .{},
.custom_union = .{ .i = 0 },
.custom_enum = .a,
};
const subnamespaces_0_doc =
\\{
\\ "packed_struct": {"a": 0, "b": 0},
\\ "union_enum": {"i": 0},
\\ "inferred_enum": "a",
\\ "explicit_enum": "a",
\\ "custom_struct": null,
\\ "custom_union": null,
\\ "custom_enum": null
\\}
;
fn testAllParseFunctions(comptime T: type, expected: T, doc: []const u8) !void {
// First do the one with the debug info in case we get a SyntaxError or something.
{
var scanner = JsonScanner.initCompleteInput(testing.allocator, doc);
defer scanner.deinit();
var diagnostics = Diagnostics{};
scanner.enableDiagnostics(&diagnostics);
var parsed = parseFromTokenSource(T, testing.allocator, &scanner, .{}) catch |e| {
std.debug.print("at line,col: {}:{}\n", .{ diagnostics.getLine(), diagnostics.getColumn() });
return e;
};
defer parsed.deinit();
try testing.expectEqualDeep(expected, parsed.value);
}
{
const parsed = try parseFromSlice(T, testing.allocator, doc, .{});
defer parsed.deinit();
try testing.expectEqualDeep(expected, parsed.value);
}
{
var stream = std.io.fixedBufferStream(doc);
var json_reader = jsonReader(std.testing.allocator, stream.reader());
defer json_reader.deinit();
var parsed = try parseFromTokenSource(T, testing.allocator, &json_reader, .{});
defer parsed.deinit();
try testing.expectEqualDeep(expected, parsed.value);
}
var arena = ArenaAllocator.init(testing.allocator);
defer arena.deinit();
{
try testing.expectEqualDeep(expected, try parseFromSliceLeaky(T, arena.allocator(), doc, .{}));
}
{
var scanner = JsonScanner.initCompleteInput(testing.allocator, doc);
defer scanner.deinit();
try testing.expectEqualDeep(expected, try parseFromTokenSourceLeaky(T, arena.allocator(), &scanner, .{}));
}
{
var stream = std.io.fixedBufferStream(doc);
var json_reader = jsonReader(std.testing.allocator, stream.reader());
defer json_reader.deinit();
try testing.expectEqualDeep(expected, try parseFromTokenSourceLeaky(T, arena.allocator(), &json_reader, .{}));
}
const parsed_dynamic = try parseFromSlice(Value, testing.allocator, doc, .{});
defer parsed_dynamic.deinit();
{
const parsed = try parseFromValue(T, testing.allocator, parsed_dynamic.value, .{});
defer parsed.deinit();
try testing.expectEqualDeep(expected, parsed.value);
}
{
try testing.expectEqualDeep(expected, try parseFromValueLeaky(T, arena.allocator(), parsed_dynamic.value, .{}));
}
}
test "test all types" {
if (true) return error.SkipZigTest; // See https://github.com/ziglang/zig/issues/16108
try testAllParseFunctions(Primitives, primitives_0, primitives_0_doc_0);
try testAllParseFunctions(Primitives, primitives_0, primitives_0_doc_1);
try testAllParseFunctions(Primitives, primitives_1, primitives_1_doc_0);
try testAllParseFunctions(Primitives, primitives_1, primitives_1_doc_1);
try testAllParseFunctions(Aggregates, aggregates_0, aggregates_0_doc);
try testAllParseFunctions(Aggregates, aggregates_1, aggregates_1_doc);
try testAllParseFunctions(Strings, strings_0, strings_0_doc_0);
try testAllParseFunctions(Strings, strings_0, strings_0_doc_1);
try testAllParseFunctions(Subnamespaces, subnamespaces_0, subnamespaces_0_doc);
}
test "parse" {
try testing.expectEqual(false, try parseFromSliceLeaky(bool, testing.allocator, "false", .{}));
try testing.expectEqual(true, try parseFromSliceLeaky(bool, testing.allocator, "true", .{}));
try testing.expectEqual(@as(u1, 1), try parseFromSliceLeaky(u1, testing.allocator, "1", .{}));
try testing.expectError(error.Overflow, parseFromSliceLeaky(u1, testing.allocator, "50", .{}));
try testing.expectEqual(@as(u64, 42), try parseFromSliceLeaky(u64, testing.allocator, "42", .{}));
try testing.expectEqual(@as(f64, 42), try parseFromSliceLeaky(f64, testing.allocator, "42.0", .{}));
try testing.expectEqual(@as(?bool, null), try parseFromSliceLeaky(?bool, testing.allocator, "null", .{}));
try testing.expectEqual(@as(?bool, true), try parseFromSliceLeaky(?bool, testing.allocator, "true", .{}));
try testing.expectEqual(@as([3]u8, "foo".*), try parseFromSliceLeaky([3]u8, testing.allocator, "\"foo\"", .{}));
try testing.expectEqual(@as([3]u8, "foo".*), try parseFromSliceLeaky([3]u8, testing.allocator, "[102, 111, 111]", .{}));
try testing.expectEqual(@as([0]u8, undefined), try parseFromSliceLeaky([0]u8, testing.allocator, "[]", .{}));
try testing.expectEqual(@as(u64, 12345678901234567890), try parseFromSliceLeaky(u64, testing.allocator, "\"12345678901234567890\"", .{}));
try testing.expectEqual(@as(f64, 123.456), try parseFromSliceLeaky(f64, testing.allocator, "\"123.456\"", .{}));
}
test "parse into enum" {
const T = enum(u32) {
Foo = 42,
Bar,
@"with\\escape",
};
try testing.expectEqual(@as(T, .Foo), try parseFromSliceLeaky(T, testing.allocator, "\"Foo\"", .{}));
try testing.expectEqual(@as(T, .Foo), try parseFromSliceLeaky(T, testing.allocator, "42", .{}));
try testing.expectEqual(@as(T, .@"with\\escape"), try parseFromSliceLeaky(T, testing.allocator, "\"with\\\\escape\"", .{}));
try testing.expectError(error.InvalidEnumTag, parseFromSliceLeaky(T, testing.allocator, "5", .{}));
try testing.expectError(error.InvalidEnumTag, parseFromSliceLeaky(T, testing.allocator, "\"Qux\"", .{}));
}
test "parse into that allocates a slice" {
{
// string as string
const parsed = try parseFromSlice([]u8, testing.allocator, "\"foo\"", .{});
defer parsed.deinit();
try testing.expectEqualSlices(u8, "foo", parsed.value);
}
{
// string as array of u8 integers
const parsed = try parseFromSlice([]u8, testing.allocator, "[102, 111, 111]", .{});
defer parsed.deinit();
try testing.expectEqualSlices(u8, "foo", parsed.value);
}
{
const parsed = try parseFromSlice([]u8, testing.allocator, "\"with\\\\escape\"", .{});
defer parsed.deinit();
try testing.expectEqualSlices(u8, "with\\escape", parsed.value);
}
}
test "parse into sentinel slice" {
const parsed = try parseFromSlice([:0]const u8, testing.allocator, "\"\\n\"", .{});
defer parsed.deinit();
try testing.expect(std.mem.eql(u8, parsed.value, "\n"));
}
test "parse into tagged union" {
const T = union(enum) {
nothing,
int: i32,
float: f64,
string: []const u8,
};
try testing.expectEqual(T{ .float = 1.5 }, try parseFromSliceLeaky(T, testing.allocator, "{\"float\":1.5}", .{}));
try testing.expectEqual(T{ .int = 1 }, try parseFromSliceLeaky(T, testing.allocator, "{\"int\":1}", .{}));
try testing.expectEqual(T{ .nothing = {} }, try parseFromSliceLeaky(T, testing.allocator, "{\"nothing\":{}}", .{}));
const parsed = try parseFromSlice(T, testing.allocator, "{\"string\":\"foo\"}", .{});
defer parsed.deinit();
try testing.expectEqualSlices(u8, "foo", parsed.value.string);
}
test "parse into tagged union errors" {
const T = union(enum) {
nothing,
int: i32,
float: f64,
string: []const u8,
};
var arena = ArenaAllocator.init(testing.allocator);
defer arena.deinit();
try testing.expectError(error.UnexpectedToken, parseFromSliceLeaky(T, arena.allocator(), "42", .{}));
try testing.expectError(error.SyntaxError, parseFromSliceLeaky(T, arena.allocator(), "{\"int\":1} 42", .{}));
try testing.expectError(error.UnexpectedToken, parseFromSliceLeaky(T, arena.allocator(), "{}", .{}));
try testing.expectError(error.UnknownField, parseFromSliceLeaky(T, arena.allocator(), "{\"bogus\":1}", .{}));
try testing.expectError(error.UnexpectedToken, parseFromSliceLeaky(T, arena.allocator(), "{\"int\":1, \"int\":1", .{}));
try testing.expectError(error.UnexpectedToken, parseFromSliceLeaky(T, arena.allocator(), "{\"int\":1, \"float\":1.0}", .{}));
try testing.expectError(error.UnexpectedToken, parseFromSliceLeaky(T, arena.allocator(), "{\"nothing\":null}", .{}));
try testing.expectError(error.UnexpectedToken, parseFromSliceLeaky(T, arena.allocator(), "{\"nothing\":{\"no\":0}}", .{}));
// Allocator failure
try testing.expectError(error.OutOfMemory, parseFromSlice(T, testing.failing_allocator, "{\"string\"\"foo\"}", .{}));
}
test "parse into struct with no fields" {
const T = struct {};
const parsed = try parseFromSlice(T, testing.allocator, "{}", .{});
defer parsed.deinit();
try testing.expectEqual(T{}, parsed.value);
}
const test_const_value: usize = 123;
test "parse into struct with default const pointer field" {
const T = struct { a: *const usize = &test_const_value };
const parsed = try parseFromSlice(T, testing.allocator, "{}", .{});
defer parsed.deinit();
try testing.expectEqual(T{}, parsed.value);
}
const test_default_usize: usize = 123;
const test_default_usize_ptr: *align(1) const usize = &test_default_usize;
const test_default_str: []const u8 = "test str";
const test_default_str_slice: [2][]const u8 = [_][]const u8{
"test1",
"test2",
};
test "freeing parsed structs with pointers to default values" {
const T = struct {
int: *const usize = &test_default_usize,
int_ptr: *allowzero align(1) const usize = test_default_usize_ptr,
str: []const u8 = test_default_str,
str_slice: []const []const u8 = &test_default_str_slice,
};
var parsed = try parseFromSlice(T, testing.allocator, "{}", .{});
try testing.expectEqual(T{}, parsed.value);
defer parsed.deinit();
}
test "parse into struct where destination and source lengths mismatch" {
const T = struct { a: [2]u8 };
try testing.expectError(error.LengthMismatch, parseFromSlice(T, testing.allocator, "{\"a\": \"bbb\"}", .{}));
}
test "parse into struct with misc fields" {
const T = struct {
int: i64,
float: f64,
@"with\\escape": bool,
@"withąunicode😂": bool,
language: []const u8,
optional: ?bool,
default_field: i32 = 42,
static_array: [3]f64,
dynamic_array: []f64,
complex: struct {
nested: []const u8,
},
veryComplex: []struct {
foo: []const u8,
},
a_union: Union,
const Union = union(enum) {
x: u8,
float: f64,
string: []const u8,
};
};
var document_str =
\\{
\\ "int": 420,
\\ "float": 3.14,
\\ "with\\escape": true,
\\ "with\u0105unicode\ud83d\ude02": false,
\\ "language": "zig",
\\ "optional": null,
\\ "static_array": [66.6, 420.420, 69.69],
\\ "dynamic_array": [66.6, 420.420, 69.69],
\\ "complex": {
\\ "nested": "zig"
\\ },
\\ "veryComplex": [
\\ {
\\ "foo": "zig"
\\ }, {
\\ "foo": "rocks"
\\ }
\\ ],
\\ "a_union": {
\\ "float": 100000
\\ }
\\}
;
const parsed = try parseFromSlice(T, testing.allocator, document_str, .{});
defer parsed.deinit();
const r = &parsed.value;
try testing.expectEqual(@as(i64, 420), r.int);
try testing.expectEqual(@as(f64, 3.14), r.float);
try testing.expectEqual(true, r.@"with\\escape");
try testing.expectEqual(false, r.@"withąunicode😂");
try testing.expectEqualSlices(u8, "zig", r.language);
try testing.expectEqual(@as(?bool, null), r.optional);
try testing.expectEqual(@as(i32, 42), r.default_field);
try testing.expectEqual(@as(f64, 66.6), r.static_array[0]);
try testing.expectEqual(@as(f64, 420.420), r.static_array[1]);
try testing.expectEqual(@as(f64, 69.69), r.static_array[2]);
try testing.expectEqual(@as(usize, 3), r.dynamic_array.len);
try testing.expectEqual(@as(f64, 66.6), r.dynamic_array[0]);
try testing.expectEqual(@as(f64, 420.420), r.dynamic_array[1]);
try testing.expectEqual(@as(f64, 69.69), r.dynamic_array[2]);
try testing.expectEqualSlices(u8, r.complex.nested, "zig");
try testing.expectEqualSlices(u8, "zig", r.veryComplex[0].foo);
try testing.expectEqualSlices(u8, "rocks", r.veryComplex[1].foo);
try testing.expectEqual(T.Union{ .float = 100000 }, r.a_union);
}
test "parse into struct with strings and arrays with sentinels" {
const T = struct {
language: [:0]const u8,
language_without_sentinel: []const u8,
data: [:99]const i32,
simple_data: []const i32,
};
var document_str =
\\{
\\ "language": "zig",
\\ "language_without_sentinel": "zig again!",
\\ "data": [1, 2, 3],
\\ "simple_data": [4, 5, 6]
\\}
;
const parsed = try parseFromSlice(T, testing.allocator, document_str, .{});
defer parsed.deinit();
try testing.expectEqualSentinel(u8, 0, "zig", parsed.value.language);
const data = [_:99]i32{ 1, 2, 3 };
try testing.expectEqualSentinel(i32, 99, data[0..data.len], parsed.value.data);
// Make sure that arrays who aren't supposed to have a sentinel still parse without one.
try testing.expectEqual(@as(?i32, null), std.meta.sentinel(@TypeOf(parsed.value.simple_data)));
try testing.expectEqual(@as(?u8, null), std.meta.sentinel(@TypeOf(parsed.value.language_without_sentinel)));
}
test "parse into struct with duplicate field" {
const options_first = ParseOptions{ .duplicate_field_behavior = .use_first };
const options_last = ParseOptions{ .duplicate_field_behavior = .use_last };
const str = "{ \"a\": 1, \"a\": 0.25 }";
const T1 = struct { a: *u64 };
// both .use_first and .use_last should fail because second "a" value isn't a u64
try testing.expectError(error.InvalidNumber, parseFromSlice(T1, testing.allocator, str, options_first));
try testing.expectError(error.InvalidNumber, parseFromSlice(T1, testing.allocator, str, options_last));
var arena = ArenaAllocator.init(testing.allocator);
defer arena.deinit();
const T2 = struct { a: f64 };
try testing.expectEqual(T2{ .a = 1.0 }, try parseFromSliceLeaky(T2, arena.allocator(), str, options_first));
try testing.expectEqual(T2{ .a = 0.25 }, try parseFromSliceLeaky(T2, arena.allocator(), str, options_last));
}
test "parse into struct ignoring unknown fields" {
const T = struct {
int: i64,
language: []const u8,
};
var str =
\\{
\\ "int": 420,
\\ "float": 3.14,
\\ "with\\escape": true,
\\ "with\u0105unicode\ud83d\ude02": false,
\\ "optional": null,
\\ "static_array": [66.6, 420.420, 69.69],
\\ "dynamic_array": [66.6, 420.420, 69.69],
\\ "complex": {
\\ "nested": "zig"
\\ },
\\ "veryComplex": [
\\ {
\\ "foo": "zig"
\\ }, {
\\ "foo": "rocks"
\\ }
\\ ],
\\ "a_union": {
\\ "float": 100000
\\ },
\\ "language": "zig"
\\}
;
const parsed = try parseFromSlice(T, testing.allocator, str, .{ .ignore_unknown_fields = true });
defer parsed.deinit();
try testing.expectEqual(@as(i64, 420), parsed.value.int);
try testing.expectEqualSlices(u8, "zig", parsed.value.language);
}
test "parse into tuple" {
const Union = union(enum) {
char: u8,
float: f64,
string: []const u8,
};
const T = std.meta.Tuple(&.{
i64,
f64,
bool,
[]const u8,
?bool,
struct {
foo: i32,
bar: []const u8,
},
std.meta.Tuple(&.{ u8, []const u8, u8 }),
Union,
});
var str =
\\[
\\ 420,
\\ 3.14,
\\ true,
\\ "zig",
\\ null,
\\ {
\\ "foo": 1,
\\ "bar": "zero"
\\ },
\\ [4, "två", 42],
\\ {"float": 12.34}
\\]
;
const parsed = try parseFromSlice(T, testing.allocator, str, .{});
defer parsed.deinit();
const r = parsed.value;
try testing.expectEqual(@as(i64, 420), r[0]);
try testing.expectEqual(@as(f64, 3.14), r[1]);
try testing.expectEqual(true, r[2]);
try testing.expectEqualSlices(u8, "zig", r[3]);
try testing.expectEqual(@as(?bool, null), r[4]);
try testing.expectEqual(@as(i32, 1), r[5].foo);
try testing.expectEqualSlices(u8, "zero", r[5].bar);
try testing.expectEqual(@as(u8, 4), r[6][0]);
try testing.expectEqualSlices(u8, "två", r[6][1]);
try testing.expectEqual(@as(u8, 42), r[6][2]);
try testing.expectEqual(Union{ .float = 12.34 }, r[7]);
}
const ParseIntoRecursiveUnionDefinitionValue = union(enum) {
integer: i64,
array: []const ParseIntoRecursiveUnionDefinitionValue,
};
test "parse into recursive union definition" {
const T = struct {
values: ParseIntoRecursiveUnionDefinitionValue,
};
const parsed = try parseFromSlice(T, testing.allocator, "{\"values\":{\"array\":[{\"integer\":58}]}}", .{});
defer parsed.deinit();
try testing.expectEqual(@as(i64, 58), parsed.value.values.array[0].integer);
}
const ParseIntoDoubleRecursiveUnionValueFirst = union(enum) {
integer: i64,
array: []const ParseIntoDoubleRecursiveUnionValueSecond,
};
const ParseIntoDoubleRecursiveUnionValueSecond = union(enum) {
boolean: bool,
array: []const ParseIntoDoubleRecursiveUnionValueFirst,
};
test "parse into double recursive union definition" {
const T = struct {
values: ParseIntoDoubleRecursiveUnionValueFirst,
};
const parsed = try parseFromSlice(T, testing.allocator, "{\"values\":{\"array\":[{\"array\":[{\"integer\":58}]}]}}", .{});
defer parsed.deinit();
try testing.expectEqual(@as(i64, 58), parsed.value.values.array[0].array[0].integer);
}
test "parse exponential into int" {
const T = struct { int: i64 };
const r = try parseFromSliceLeaky(T, testing.allocator, "{ \"int\": 4.2e2 }", .{});
try testing.expectEqual(@as(i64, 420), r.int);
try testing.expectError(error.InvalidNumber, parseFromSliceLeaky(T, testing.allocator, "{ \"int\": 0.042e2 }", .{}));
try testing.expectError(error.Overflow, parseFromSliceLeaky(T, testing.allocator, "{ \"int\": 18446744073709551616.0 }", .{}));
}
test "parseFromTokenSource" {
{
var scanner = JsonScanner.initCompleteInput(testing.allocator, "123");
defer scanner.deinit();
var parsed = try parseFromTokenSource(u32, testing.allocator, &scanner, .{});
defer parsed.deinit();
try testing.expectEqual(@as(u32, 123), parsed.value);
}
{
var stream = std.io.fixedBufferStream("123");
var json_reader = jsonReader(std.testing.allocator, stream.reader());
defer json_reader.deinit();
var parsed = try parseFromTokenSource(u32, testing.allocator, &json_reader, .{});
defer parsed.deinit();
try testing.expectEqual(@as(u32, 123), parsed.value);
}
}
test "max_value_len" {
try testing.expectError(error.ValueTooLong, parseFromSlice([]u8, testing.allocator, "\"0123456789\"", .{ .max_value_len = 5 }));
}
test "parse into vector" {
const T = struct {
vec_i32: @Vector(4, i32),
vec_f32: @Vector(2, f32),
};
var s =
\\{
\\ "vec_f32": [1.5, 2.5],
\\ "vec_i32": [4, 5, 6, 7]
\\}
;
const parsed = try parseFromSlice(T, testing.allocator, s, .{});
defer parsed.deinit();
try testing.expectApproxEqAbs(@as(f32, 1.5), parsed.value.vec_f32[0], 0.0000001);
try testing.expectApproxEqAbs(@as(f32, 2.5), parsed.value.vec_f32[1], 0.0000001);
try testing.expectEqual(@Vector(4, i32){ 4, 5, 6, 7 }, parsed.value.vec_i32);
}
fn assertKey(
allocator: Allocator,
test_string: []const u8,
scanner: anytype,
) !void {
const token_outer = try scanner.nextAlloc(allocator, .alloc_always);
switch (token_outer) {
.allocated_string => |string| {
try testing.expectEqualSlices(u8, string, test_string);
allocator.free(string);
},
else => return error.UnexpectedToken,
}
}
test "json parse partial" {
const Inner = struct {
num: u32,
yes: bool,
};
var str =
\\{
\\ "outer": {
\\ "key1": {
\\ "num": 75,
\\ "yes": true
\\ },
\\ "key2": {
\\ "num": 95,
\\ "yes": false
\\ }
\\ }
\\}
;
var allocator = testing.allocator;
var scanner = JsonScanner.initCompleteInput(allocator, str);
defer scanner.deinit();
var arena = ArenaAllocator.init(allocator);
defer arena.deinit();
// Peel off the outer object
try testing.expectEqual(try scanner.next(), .object_begin);
try assertKey(allocator, "outer", &scanner);
try testing.expectEqual(try scanner.next(), .object_begin);
try assertKey(allocator, "key1", &scanner);
// Parse the inner object to an Inner struct
const inner_token = try innerParse(
Inner,
arena.allocator(),
&scanner,
.{ .max_value_len = scanner.input.len },
);
try testing.expectEqual(inner_token.num, 75);
try testing.expectEqual(inner_token.yes, true);
// Get they next key
try assertKey(allocator, "key2", &scanner);
const inner_token_2 = try innerParse(
Inner,
arena.allocator(),
&scanner,
.{ .max_value_len = scanner.input.len },
);
try testing.expectEqual(inner_token_2.num, 95);
try testing.expectEqual(inner_token_2.yes, false);
try testing.expectEqual(try scanner.next(), .object_end);
}
test "json parse allocate when streaming" {
const T = struct {
not_const: []u8,
is_const: []const u8,
};
var str =
\\{
\\ "not_const": "non const string",
\\ "is_const": "const string"
\\}
;
var allocator = testing.allocator;
var arena = ArenaAllocator.init(allocator);
defer arena.deinit();
var stream = std.io.fixedBufferStream(str);
var json_reader = jsonReader(std.testing.allocator, stream.reader());
const parsed = parseFromTokenSourceLeaky(T, arena.allocator(), &json_reader, .{}) catch |err| {
json_reader.deinit();
return err;
};
// Deinit our reader to invalidate its buffer
json_reader.deinit();
// If either of these was invalidated, it would be full of '0xAA'
try testing.expectEqualSlices(u8, parsed.not_const, "non const string");
try testing.expectEqualSlices(u8, parsed.is_const, "const string");
}
test "parse at comptime" {
const doc =
\\{
\\ "vals": {
\\ "testing": 1,
\\ "production": 42
\\ },
\\ "uptime": 9999
\\}
;
const Config = struct {
vals: struct { testing: u8, production: u8 },
uptime: u64,
};
const config = comptime x: {
var buf: [32]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf);
const res = parseFromSliceLeaky(Config, fba.allocator(), doc, .{});
// Assert no error can occur since we are
// parsing this JSON at comptime!
break :x res catch unreachable;
};
comptime testing.expectEqual(@as(u64, 9999), config.uptime) catch unreachable;
}