authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-07-06 23:22:19-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-07-07 00:05:21-07:00
logc9006d9479c619d9ed555164831e11a04d88d382
treeea3972c57ca31245d0f0466d9011ced419cede35
parent0101a5f75e825c4eb099107f9fcec4941c077bea

std.json: move tests to json/test.zig file

This accomplishes two things: * Works around #8442 by putting stage1-specific logic in to disable all the std.json tests. * Slightly reduces installation size of zig since std lib files ending in "test.zig" are excluded from being installed.

2 files changed, 981 insertions(+), 960 deletions(-)

lib/std/json.zig+158-960
...@@ -2,6 +2,7 @@...@@ -2,6 +2,7 @@
2//2//
3// https://tools.ietf.org/html/rfc82593// https://tools.ietf.org/html/rfc8259
44
5const builtin = @import("builtin");
5const std = @import("std.zig");6const std = @import("std.zig");
6const debug = std.debug;7const debug = std.debug;
7const assert = debug.assert;8const assert = debug.assert;
...@@ -72,22 +73,6 @@ fn encodesTo(decoded: []const u8, encoded: []const u8) bool {...@@ -72,22 +73,6 @@ fn encodesTo(decoded: []const u8, encoded: []const u8) bool {
72 return true;73 return true;
73}74}
7475
75test "encodesTo" {
76 // same
77 try testing.expectEqual(true, encodesTo("false", "false"));
78 // totally different
79 try testing.expectEqual(false, encodesTo("false", "true"));
80 // different lengths
81 try testing.expectEqual(false, encodesTo("false", "other"));
82 // with escape
83 try testing.expectEqual(true, encodesTo("\\", "\\\\"));
84 try testing.expectEqual(true, encodesTo("with\nescape", "with\\nescape"));
85 // with unicode
86 try testing.expectEqual(true, encodesTo("ą", "\\u0105"));
87 try testing.expectEqual(true, encodesTo("πŸ˜‚", "\\ud83d\\ude02"));
88 try testing.expectEqual(true, encodesTo("withąunicodeπŸ˜‚", "with\\u0105unicode\\ud83d\\ude02"));
89}
90
91/// A single token slice into the parent string.76/// A single token slice into the parent string.
92///77///
93/// Use `token.slice()` on the input at the current position to get the current slice.78/// Use `token.slice()` on the input at the current position to get the current slice.
...@@ -1100,15 +1085,6 @@ pub const StreamingParser = struct {...@@ -1100,15 +1085,6 @@ pub const StreamingParser = struct {
1100 }1085 }
1101};1086};
11021087
1103test "json.serialize issue #5959" {
1104 var parser: StreamingParser = undefined;
1105 // StreamingParser has multiple internal fields set to undefined. This causes issues when using
1106 // expectEqual so these are zeroed. We are testing for equality here only because this is a
1107 // known small test reproduction which hits the relevant LLVM issue.
1108 std.mem.set(u8, @ptrCast([*]u8, &parser)[0..@sizeOf(StreamingParser)], 0);
1109 try std.testing.expectEqual(parser, parser);
1110}
1111
1112/// A small wrapper over a StreamingParser for full slices. Returns a stream of json Tokens.1088/// A small wrapper over a StreamingParser for full slices. Returns a stream of json Tokens.
1113pub const TokenStream = struct {1089pub const TokenStream = struct {
1114 i: usize,1090 i: usize,
...@@ -1164,80 +1140,6 @@ pub const TokenStream = struct {...@@ -1164,80 +1140,6 @@ pub const TokenStream = struct {
1164 }1140 }
1165};1141};
11661142
1167fn checkNext(p: *TokenStream, id: std.meta.Tag(Token)) !void {
1168 const token = (p.next() catch unreachable).?;
1169 try testing.expect(std.meta.activeTag(token) == id);
1170}
1171
1172test "json.token" {
1173 const s =
1174 \\{
1175 \\ "Image": {
1176 \\ "Width": 800,
1177 \\ "Height": 600,
1178 \\ "Title": "View from 15th Floor",
1179 \\ "Thumbnail": {
1180 \\ "Url": "http://www.example.com/image/481989943",
1181 \\ "Height": 125,
1182 \\ "Width": 100
1183 \\ },
1184 \\ "Animated" : false,
1185 \\ "IDs": [116, 943, 234, 38793]
1186 \\ }
1187 \\}
1188 ;
1189
1190 var p = TokenStream.init(s);
1191
1192 try checkNext(&p, .ObjectBegin);
1193 try checkNext(&p, .String); // Image
1194 try checkNext(&p, .ObjectBegin);
1195 try checkNext(&p, .String); // Width
1196 try checkNext(&p, .Number);
1197 try checkNext(&p, .String); // Height
1198 try checkNext(&p, .Number);
1199 try checkNext(&p, .String); // Title
1200 try checkNext(&p, .String);
1201 try checkNext(&p, .String); // Thumbnail
1202 try checkNext(&p, .ObjectBegin);
1203 try checkNext(&p, .String); // Url
1204 try checkNext(&p, .String);
1205 try checkNext(&p, .String); // Height
1206 try checkNext(&p, .Number);
1207 try checkNext(&p, .String); // Width
1208 try checkNext(&p, .Number);
1209 try checkNext(&p, .ObjectEnd);
1210 try checkNext(&p, .String); // Animated
1211 try checkNext(&p, .False);
1212 try checkNext(&p, .String); // IDs
1213 try checkNext(&p, .ArrayBegin);
1214 try checkNext(&p, .Number);
1215 try checkNext(&p, .Number);
1216 try checkNext(&p, .Number);
1217 try checkNext(&p, .Number);
1218 try checkNext(&p, .ArrayEnd);
1219 try checkNext(&p, .ObjectEnd);
1220 try checkNext(&p, .ObjectEnd);
1221
1222 try testing.expect((try p.next()) == null);
1223}
1224
1225test "json.token mismatched close" {
1226 var p = TokenStream.init("[102, 111, 111 }");
1227 try checkNext(&p, .ArrayBegin);
1228 try checkNext(&p, .Number);
1229 try checkNext(&p, .Number);
1230 try checkNext(&p, .Number);
1231 try testing.expectError(error.UnexpectedClosingBrace, p.next());
1232}
1233
1234test "json.token premature object close" {
1235 var p = TokenStream.init("{ \"key\": }");
1236 try checkNext(&p, .ObjectBegin);
1237 try checkNext(&p, .String);
1238 try testing.expectError(error.InvalidValueBegin, p.next());
1239}
1240
1241/// Validate a JSON string. This does not limit number precision so a decoder may not necessarily1143/// Validate a JSON string. This does not limit number precision so a decoder may not necessarily
1242/// be able to decode the string even if this returns true.1144/// be able to decode the string even if this returns true.
1243pub fn validate(s: []const u8) bool {1145pub fn validate(s: []const u8) bool {
...@@ -1255,15 +1157,6 @@ pub fn validate(s: []const u8) bool {...@@ -1255,15 +1157,6 @@ pub fn validate(s: []const u8) bool {
1255 return p.complete;1157 return p.complete;
1256}1158}
12571159
1258test "json.validate" {
1259 try testing.expectEqual(true, validate("{}"));
1260 try testing.expectEqual(true, validate("[]"));
1261 try testing.expectEqual(true, validate("[{[[[[{}]]]]}]"));
1262 try testing.expectEqual(false, validate("{]"));
1263 try testing.expectEqual(false, validate("[}"));
1264 try testing.expectEqual(false, validate("{{{{[]}}}]"));
1265}
1266
1267const Allocator = std.mem.Allocator;1160const Allocator = std.mem.Allocator;
1268const ArenaAllocator = std.heap.ArenaAllocator;1161const ArenaAllocator = std.heap.ArenaAllocator;
1269const ArrayList = std.ArrayList;1162const ArrayList = std.ArrayList;
...@@ -1352,67 +1245,6 @@ pub const Value = union(enum) {...@@ -1352,67 +1245,6 @@ pub const Value = union(enum) {
1352 }1245 }
1353};1246};
13541247
1355test "Value.jsonStringify" {
1356 {
1357 var buffer: [10]u8 = undefined;
1358 var fbs = std.io.fixedBufferStream(&buffer);
1359 try @as(Value, .Null).jsonStringify(.{}, fbs.writer());
1360 try testing.expectEqualSlices(u8, fbs.getWritten(), "null");
1361 }
1362 {
1363 var buffer: [10]u8 = undefined;
1364 var fbs = std.io.fixedBufferStream(&buffer);
1365 try (Value{ .Bool = true }).jsonStringify(.{}, fbs.writer());
1366 try testing.expectEqualSlices(u8, fbs.getWritten(), "true");
1367 }
1368 {
1369 var buffer: [10]u8 = undefined;
1370 var fbs = std.io.fixedBufferStream(&buffer);
1371 try (Value{ .Integer = 42 }).jsonStringify(.{}, fbs.writer());
1372 try testing.expectEqualSlices(u8, fbs.getWritten(), "42");
1373 }
1374 {
1375 var buffer: [10]u8 = undefined;
1376 var fbs = std.io.fixedBufferStream(&buffer);
1377 try (Value{ .NumberString = "43" }).jsonStringify(.{}, fbs.writer());
1378 try testing.expectEqualSlices(u8, fbs.getWritten(), "43");
1379 }
1380 {
1381 var buffer: [10]u8 = undefined;
1382 var fbs = std.io.fixedBufferStream(&buffer);
1383 try (Value{ .Float = 42 }).jsonStringify(.{}, fbs.writer());
1384 try testing.expectEqualSlices(u8, fbs.getWritten(), "4.2e+01");
1385 }
1386 {
1387 var buffer: [10]u8 = undefined;
1388 var fbs = std.io.fixedBufferStream(&buffer);
1389 try (Value{ .String = "weeee" }).jsonStringify(.{}, fbs.writer());
1390 try testing.expectEqualSlices(u8, fbs.getWritten(), "\"weeee\"");
1391 }
1392 {
1393 var buffer: [10]u8 = undefined;
1394 var fbs = std.io.fixedBufferStream(&buffer);
1395 var vals = [_]Value{
1396 .{ .Integer = 1 },
1397 .{ .Integer = 2 },
1398 .{ .NumberString = "3" },
1399 };
1400 try (Value{
1401 .Array = Array.fromOwnedSlice(undefined, &vals),
1402 }).jsonStringify(.{}, fbs.writer());
1403 try testing.expectEqualSlices(u8, fbs.getWritten(), "[1,2,3]");
1404 }
1405 {
1406 var buffer: [10]u8 = undefined;
1407 var fbs = std.io.fixedBufferStream(&buffer);
1408 var obj = ObjectMap.init(testing.allocator);
1409 defer obj.deinit();
1410 try obj.putNoClobber("a", .{ .String = "b" });
1411 try (Value{ .Object = obj }).jsonStringify(.{}, fbs.writer());
1412 try testing.expectEqualSlices(u8, fbs.getWritten(), "{\"a\":\"b\"}");
1413 }
1414}
1415
1416/// parse tokens from a stream, returning `false` if they do not decode to `value`1248/// parse tokens from a stream, returning `false` if they do not decode to `value`
1417fn parsesTo(comptime T: type, value: T, tokens: *TokenStream, options: ParseOptions) !bool {1249fn parsesTo(comptime T: type, value: T, tokens: *TokenStream, options: ParseOptions) !bool {
1418 // TODO: should be able to write this function to not require an allocator1250 // TODO: should be able to write this function to not require an allocator
...@@ -1503,59 +1335,6 @@ fn skipValue(tokens: *TokenStream) SkipValueError!void {...@@ -1503,59 +1335,6 @@ fn skipValue(tokens: *TokenStream) SkipValueError!void {
1503 }1335 }
1504}1336}
15051337
1506test "skipValue" {
1507 var ts = TokenStream.init("false");
1508 try skipValue(&ts);
1509 ts = TokenStream.init("true");
1510 try skipValue(&ts);
1511 ts = TokenStream.init("null");
1512 try skipValue(&ts);
1513 ts = TokenStream.init("42");
1514 try skipValue(&ts);
1515 ts = TokenStream.init("42.0");
1516 try skipValue(&ts);
1517 ts = TokenStream.init("\"foo\"");
1518 try skipValue(&ts);
1519 ts = TokenStream.init("[101, 111, 121]");
1520 try skipValue(&ts);
1521 ts = TokenStream.init("{}");
1522 try skipValue(&ts);
1523 ts = TokenStream.init("{\"foo\": \"bar\"}");
1524 try skipValue(&ts);
1525
1526 { // An absurd number of nestings
1527 const nestings = StreamingParser.default_max_nestings + 1;
1528
1529 ts = TokenStream.init("[" ** nestings ++ "]" ** nestings);
1530 try testing.expectError(error.TooManyNestedItems, skipValue(&ts));
1531 }
1532
1533 { // Would a number token cause problems in a deeply-nested array?
1534 const nestings = StreamingParser.default_max_nestings;
1535 const deeply_nested_array = "[" ** nestings ++ "0.118, 999, 881.99, 911.9, 725, 3" ++ "]" ** nestings;
1536
1537 ts = TokenStream.init(deeply_nested_array);
1538 try skipValue(&ts);
1539
1540 ts = TokenStream.init("[" ++ deeply_nested_array ++ "]");
1541 try testing.expectError(error.TooManyNestedItems, skipValue(&ts));
1542 }
1543
1544 // Mismatched brace/square bracket
1545 ts = TokenStream.init("[102, 111, 111}");
1546 try testing.expectError(error.UnexpectedClosingBrace, skipValue(&ts));
1547
1548 { // should fail if no value found (e.g. immediate close of object)
1549 var empty_object = TokenStream.init("{}");
1550 assert(.ObjectBegin == (try empty_object.next()).?);
1551 try testing.expectError(error.UnexpectedJsonDepth, skipValue(&empty_object));
1552
1553 var empty_array = TokenStream.init("[]");
1554 assert(.ArrayBegin == (try empty_array.next()).?);
1555 try testing.expectError(error.UnexpectedJsonDepth, skipValue(&empty_array));
1556 }
1557}
1558
1559fn ParseInternalError(comptime T: type) type {1338fn ParseInternalError(comptime T: type) type {
1560 // `inferred_types` is used to avoid infinite recursion for recursive type definitions.1339 // `inferred_types` is used to avoid infinite recursion for recursive type definitions.
1561 const inferred_types = [_]type{};1340 const inferred_types = [_]type{};
...@@ -1981,440 +1760,6 @@ pub fn parseFree(comptime T: type, value: T, options: ParseOptions) void {...@@ -1981,440 +1760,6 @@ pub fn parseFree(comptime T: type, value: T, options: ParseOptions) void {
1981 }1760 }
1982}1761}
19831762
1984test "parse" {
1985 var ts = TokenStream.init("false");
1986 try testing.expectEqual(false, try parse(bool, &ts, ParseOptions{}));
1987 ts = TokenStream.init("true");
1988 try testing.expectEqual(true, try parse(bool, &ts, ParseOptions{}));
1989 ts = TokenStream.init("1");
1990 try testing.expectEqual(@as(u1, 1), try parse(u1, &ts, ParseOptions{}));
1991 ts = TokenStream.init("50");
1992 try testing.expectError(error.Overflow, parse(u1, &ts, ParseOptions{}));
1993 ts = TokenStream.init("42");
1994 try testing.expectEqual(@as(u64, 42), try parse(u64, &ts, ParseOptions{}));
1995 ts = TokenStream.init("42.0");
1996 try testing.expectEqual(@as(f64, 42), try parse(f64, &ts, ParseOptions{}));
1997 ts = TokenStream.init("null");
1998 try testing.expectEqual(@as(?bool, null), try parse(?bool, &ts, ParseOptions{}));
1999 ts = TokenStream.init("true");
2000 try testing.expectEqual(@as(?bool, true), try parse(?bool, &ts, ParseOptions{}));
2001
2002 ts = TokenStream.init("\"foo\"");
2003 try testing.expectEqual(@as([3]u8, "foo".*), try parse([3]u8, &ts, ParseOptions{}));
2004 ts = TokenStream.init("[102, 111, 111]");
2005 try testing.expectEqual(@as([3]u8, "foo".*), try parse([3]u8, &ts, ParseOptions{}));
2006 ts = TokenStream.init("[]");
2007 try testing.expectEqual(@as([0]u8, undefined), try parse([0]u8, &ts, ParseOptions{}));
2008}
2009
2010test "parse into enum" {
2011 const T = enum(u32) {
2012 Foo = 42,
2013 Bar,
2014 @"with\\escape",
2015 };
2016 var ts = TokenStream.init("\"Foo\"");
2017 try testing.expectEqual(@as(T, .Foo), try parse(T, &ts, ParseOptions{}));
2018 ts = TokenStream.init("42");
2019 try testing.expectEqual(@as(T, .Foo), try parse(T, &ts, ParseOptions{}));
2020 ts = TokenStream.init("\"with\\\\escape\"");
2021 try testing.expectEqual(@as(T, .@"with\\escape"), try parse(T, &ts, ParseOptions{}));
2022 ts = TokenStream.init("5");
2023 try testing.expectError(error.InvalidEnumTag, parse(T, &ts, ParseOptions{}));
2024 ts = TokenStream.init("\"Qux\"");
2025 try testing.expectError(error.InvalidEnumTag, parse(T, &ts, ParseOptions{}));
2026}
2027
2028test "parse with trailing data" {
2029 var ts = TokenStream.init("falsed");
2030 try testing.expectEqual(false, try parse(bool, &ts, ParseOptions{ .allow_trailing_data = true }));
2031 ts = TokenStream.init("falsed");
2032 try testing.expectError(error.InvalidTopLevelTrailing, parse(bool, &ts, ParseOptions{ .allow_trailing_data = false }));
2033 // trailing whitespace is okay
2034 ts = TokenStream.init("false \n");
2035 try testing.expectEqual(false, try parse(bool, &ts, ParseOptions{ .allow_trailing_data = false }));
2036}
2037
2038test "parse into that allocates a slice" {
2039 var ts = TokenStream.init("\"foo\"");
2040 try testing.expectError(error.AllocatorRequired, parse([]u8, &ts, ParseOptions{}));
2041
2042 const options = ParseOptions{ .allocator = testing.allocator };
2043 {
2044 ts = TokenStream.init("\"foo\"");
2045 const r = try parse([]u8, &ts, options);
2046 defer parseFree([]u8, r, options);
2047 try testing.expectEqualSlices(u8, "foo", r);
2048 }
2049 {
2050 ts = TokenStream.init("[102, 111, 111]");
2051 const r = try parse([]u8, &ts, options);
2052 defer parseFree([]u8, r, options);
2053 try testing.expectEqualSlices(u8, "foo", r);
2054 }
2055 {
2056 ts = TokenStream.init("\"with\\\\escape\"");
2057 const r = try parse([]u8, &ts, options);
2058 defer parseFree([]u8, r, options);
2059 try testing.expectEqualSlices(u8, "with\\escape", r);
2060 }
2061}
2062
2063test "parse into tagged union" {
2064 {
2065 const T = union(enum) {
2066 int: i32,
2067 float: f64,
2068 string: []const u8,
2069 };
2070 var ts = TokenStream.init("1.5");
2071 try testing.expectEqual(T{ .float = 1.5 }, try parse(T, &ts, ParseOptions{}));
2072 }
2073
2074 { // failing allocations should be bubbled up instantly without trying next member
2075 var fail_alloc = testing.FailingAllocator.init(testing.allocator, 0);
2076 const options = ParseOptions{ .allocator = fail_alloc.allocator() };
2077 const T = union(enum) {
2078 // both fields here match the input
2079 string: []const u8,
2080 array: [3]u8,
2081 };
2082 var ts = TokenStream.init("[1,2,3]");
2083 try testing.expectError(error.OutOfMemory, parse(T, &ts, options));
2084 }
2085
2086 {
2087 // if multiple matches possible, takes first option
2088 const T = union(enum) {
2089 x: u8,
2090 y: u8,
2091 };
2092 var ts = TokenStream.init("42");
2093 try testing.expectEqual(T{ .x = 42 }, try parse(T, &ts, ParseOptions{}));
2094 }
2095
2096 { // needs to back out when first union member doesn't match
2097 const T = union(enum) {
2098 A: struct { x: u32 },
2099 B: struct { y: u32 },
2100 };
2101 var ts = TokenStream.init("{\"y\":42}");
2102 try testing.expectEqual(T{ .B = .{ .y = 42 } }, try parse(T, &ts, ParseOptions{}));
2103 }
2104}
2105
2106test "parse union bubbles up AllocatorRequired" {
2107 { // string member first in union (and not matching)
2108 const T = union(enum) {
2109 string: []const u8,
2110 int: i32,
2111 };
2112 var ts = TokenStream.init("42");
2113 try testing.expectError(error.AllocatorRequired, parse(T, &ts, ParseOptions{}));
2114 }
2115
2116 { // string member not first in union (and matching)
2117 const T = union(enum) {
2118 int: i32,
2119 float: f64,
2120 string: []const u8,
2121 };
2122 var ts = TokenStream.init("\"foo\"");
2123 try testing.expectError(error.AllocatorRequired, parse(T, &ts, ParseOptions{}));
2124 }
2125}
2126
2127test "parseFree descends into tagged union" {
2128 var fail_alloc = testing.FailingAllocator.init(testing.allocator, 1);
2129 const options = ParseOptions{ .allocator = fail_alloc.allocator() };
2130 const T = union(enum) {
2131 int: i32,
2132 float: f64,
2133 string: []const u8,
2134 };
2135 // use a string with unicode escape so we know result can't be a reference to global constant
2136 var ts = TokenStream.init("\"with\\u0105unicode\"");
2137 const r = try parse(T, &ts, options);
2138 try testing.expectEqual(std.meta.Tag(T).string, @as(std.meta.Tag(T), r));
2139 try testing.expectEqualSlices(u8, "withąunicode", r.string);
2140 try testing.expectEqual(@as(usize, 0), fail_alloc.deallocations);
2141 parseFree(T, r, options);
2142 try testing.expectEqual(@as(usize, 1), fail_alloc.deallocations);
2143}
2144
2145test "parse with comptime field" {
2146 {
2147 const T = struct {
2148 comptime a: i32 = 0,
2149 b: bool,
2150 };
2151 var ts = TokenStream.init(
2152 \\{
2153 \\ "a": 0,
2154 \\ "b": true
2155 \\}
2156 );
2157 try testing.expectEqual(T{ .a = 0, .b = true }, try parse(T, &ts, ParseOptions{}));
2158 }
2159
2160 { // string comptime values currently require an allocator
2161 const T = union(enum) {
2162 foo: struct {
2163 comptime kind: []const u8 = "boolean",
2164 b: bool,
2165 },
2166 bar: struct {
2167 comptime kind: []const u8 = "float",
2168 b: f64,
2169 },
2170 };
2171
2172 const options = ParseOptions{
2173 .allocator = std.testing.allocator,
2174 };
2175
2176 var ts = TokenStream.init(
2177 \\{
2178 \\ "kind": "float",
2179 \\ "b": 1.0
2180 \\}
2181 );
2182 const r = try parse(T, &ts, options);
2183
2184 // check that parseFree doesn't try to free comptime fields
2185 parseFree(T, r, options);
2186 }
2187}
2188
2189test "parse into struct with no fields" {
2190 const T = struct {};
2191 var ts = TokenStream.init("{}");
2192 try testing.expectEqual(T{}, try parse(T, &ts, ParseOptions{}));
2193}
2194
2195test "parse into struct with misc fields" {
2196 @setEvalBranchQuota(10000);
2197 const options = ParseOptions{ .allocator = testing.allocator };
2198 const T = struct {
2199 int: i64,
2200 float: f64,
2201 @"with\\escape": bool,
2202 @"withąunicodeπŸ˜‚": bool,
2203 language: []const u8,
2204 optional: ?bool,
2205 default_field: i32 = 42,
2206 static_array: [3]f64,
2207 dynamic_array: []f64,
2208
2209 complex: struct {
2210 nested: []const u8,
2211 },
2212
2213 veryComplex: []struct {
2214 foo: []const u8,
2215 },
2216
2217 a_union: Union,
2218 const Union = union(enum) {
2219 x: u8,
2220 float: f64,
2221 string: []const u8,
2222 };
2223 };
2224 var ts = TokenStream.init(
2225 \\{
2226 \\ "int": 420,
2227 \\ "float": 3.14,
2228 \\ "with\\escape": true,
2229 \\ "with\u0105unicode\ud83d\ude02": false,
2230 \\ "language": "zig",
2231 \\ "optional": null,
2232 \\ "static_array": [66.6, 420.420, 69.69],
2233 \\ "dynamic_array": [66.6, 420.420, 69.69],
2234 \\ "complex": {
2235 \\ "nested": "zig"
2236 \\ },
2237 \\ "veryComplex": [
2238 \\ {
2239 \\ "foo": "zig"
2240 \\ }, {
2241 \\ "foo": "rocks"
2242 \\ }
2243 \\ ],
2244 \\ "a_union": 100000
2245 \\}
2246 );
2247 const r = try parse(T, &ts, options);
2248 defer parseFree(T, r, options);
2249 try testing.expectEqual(@as(i64, 420), r.int);
2250 try testing.expectEqual(@as(f64, 3.14), r.float);
2251 try testing.expectEqual(true, r.@"with\\escape");
2252 try testing.expectEqual(false, r.@"withąunicodeπŸ˜‚");
2253 try testing.expectEqualSlices(u8, "zig", r.language);
2254 try testing.expectEqual(@as(?bool, null), r.optional);
2255 try testing.expectEqual(@as(i32, 42), r.default_field);
2256 try testing.expectEqual(@as(f64, 66.6), r.static_array[0]);
2257 try testing.expectEqual(@as(f64, 420.420), r.static_array[1]);
2258 try testing.expectEqual(@as(f64, 69.69), r.static_array[2]);
2259 try testing.expectEqual(@as(usize, 3), r.dynamic_array.len);
2260 try testing.expectEqual(@as(f64, 66.6), r.dynamic_array[0]);
2261 try testing.expectEqual(@as(f64, 420.420), r.dynamic_array[1]);
2262 try testing.expectEqual(@as(f64, 69.69), r.dynamic_array[2]);
2263 try testing.expectEqualSlices(u8, r.complex.nested, "zig");
2264 try testing.expectEqualSlices(u8, "zig", r.veryComplex[0].foo);
2265 try testing.expectEqualSlices(u8, "rocks", r.veryComplex[1].foo);
2266 try testing.expectEqual(T.Union{ .float = 100000 }, r.a_union);
2267}
2268
2269test "parse into struct with strings and arrays with sentinels" {
2270 @setEvalBranchQuota(10000);
2271 const options = ParseOptions{ .allocator = testing.allocator };
2272 const T = struct {
2273 language: [:0]const u8,
2274 language_without_sentinel: []const u8,
2275 data: [:99]const i32,
2276 simple_data: []const i32,
2277 };
2278 var ts = TokenStream.init(
2279 \\{
2280 \\ "language": "zig",
2281 \\ "language_without_sentinel": "zig again!",
2282 \\ "data": [1, 2, 3],
2283 \\ "simple_data": [4, 5, 6]
2284 \\}
2285 );
2286 const r = try parse(T, &ts, options);
2287 defer parseFree(T, r, options);
2288
2289 try testing.expectEqualSentinel(u8, 0, "zig", r.language);
2290
2291 const data = [_:99]i32{ 1, 2, 3 };
2292 try testing.expectEqualSentinel(i32, 99, data[0..data.len], r.data);
2293
2294 // Make sure that arrays who aren't supposed to have a sentinel still parse without one.
2295 try testing.expectEqual(@as(?i32, null), std.meta.sentinel(@TypeOf(r.simple_data)));
2296 try testing.expectEqual(@as(?u8, null), std.meta.sentinel(@TypeOf(r.language_without_sentinel)));
2297}
2298
2299test "parse into struct with duplicate field" {
2300 // allow allocator to detect double frees by keeping bucket in use
2301 const ballast = try testing.allocator.alloc(u64, 1);
2302 defer testing.allocator.free(ballast);
2303
2304 const options_first = ParseOptions{ .allocator = testing.allocator, .duplicate_field_behavior = .UseFirst };
2305
2306 const options_last = ParseOptions{
2307 .allocator = testing.allocator,
2308 .duplicate_field_behavior = .UseLast,
2309 };
2310
2311 const str = "{ \"a\": 1, \"a\": 0.25 }";
2312
2313 const T1 = struct { a: *u64 };
2314 // both .UseFirst and .UseLast should fail because second "a" value isn't a u64
2315 var ts = TokenStream.init(str);
2316 try testing.expectError(error.InvalidNumber, parse(T1, &ts, options_first));
2317 ts = TokenStream.init(str);
2318 try testing.expectError(error.InvalidNumber, parse(T1, &ts, options_last));
2319
2320 const T2 = struct { a: f64 };
2321 ts = TokenStream.init(str);
2322 try testing.expectEqual(T2{ .a = 1.0 }, try parse(T2, &ts, options_first));
2323 ts = TokenStream.init(str);
2324 try testing.expectEqual(T2{ .a = 0.25 }, try parse(T2, &ts, options_last));
2325
2326 const T3 = struct { comptime a: f64 = 1.0 };
2327 // .UseFirst should succeed because second "a" value is unconditionally ignored (even though != 1.0)
2328 const t3 = T3{ .a = 1.0 };
2329 ts = TokenStream.init(str);
2330 try testing.expectEqual(t3, try parse(T3, &ts, options_first));
2331 // .UseLast should fail because second "a" value is 0.25 which is not equal to default value of 1.0
2332 ts = TokenStream.init(str);
2333 try testing.expectError(error.UnexpectedValue, parse(T3, &ts, options_last));
2334}
2335
2336test "parse into struct ignoring unknown fields" {
2337 const T = struct {
2338 int: i64,
2339 language: []const u8,
2340 };
2341
2342 const ops = ParseOptions{
2343 .allocator = testing.allocator,
2344 .ignore_unknown_fields = true,
2345 };
2346
2347 var ts = TokenStream.init(
2348 \\{
2349 \\ "int": 420,
2350 \\ "float": 3.14,
2351 \\ "with\\escape": true,
2352 \\ "with\u0105unicode\ud83d\ude02": false,
2353 \\ "optional": null,
2354 \\ "static_array": [66.6, 420.420, 69.69],
2355 \\ "dynamic_array": [66.6, 420.420, 69.69],
2356 \\ "complex": {
2357 \\ "nested": "zig"
2358 \\ },
2359 \\ "veryComplex": [
2360 \\ {
2361 \\ "foo": "zig"
2362 \\ }, {
2363 \\ "foo": "rocks"
2364 \\ }
2365 \\ ],
2366 \\ "a_union": 100000,
2367 \\ "language": "zig"
2368 \\}
2369 );
2370 const r = try parse(T, &ts, ops);
2371 defer parseFree(T, r, ops);
2372
2373 try testing.expectEqual(@as(i64, 420), r.int);
2374 try testing.expectEqualSlices(u8, "zig", r.language);
2375}
2376
2377const ParseIntoRecursiveUnionDefinitionValue = union(enum) {
2378 integer: i64,
2379 array: []const ParseIntoRecursiveUnionDefinitionValue,
2380};
2381
2382test "parse into recursive union definition" {
2383 const T = struct {
2384 values: ParseIntoRecursiveUnionDefinitionValue,
2385 };
2386 const ops = ParseOptions{ .allocator = testing.allocator };
2387
2388 var ts = TokenStream.init("{\"values\":[58]}");
2389 const r = try parse(T, &ts, ops);
2390 defer parseFree(T, r, ops);
2391
2392 try testing.expectEqual(@as(i64, 58), r.values.array[0].integer);
2393}
2394
2395const ParseIntoDoubleRecursiveUnionValueFirst = union(enum) {
2396 integer: i64,
2397 array: []const ParseIntoDoubleRecursiveUnionValueSecond,
2398};
2399
2400const ParseIntoDoubleRecursiveUnionValueSecond = union(enum) {
2401 boolean: bool,
2402 array: []const ParseIntoDoubleRecursiveUnionValueFirst,
2403};
2404
2405test "parse into double recursive union definition" {
2406 const T = struct {
2407 values: ParseIntoDoubleRecursiveUnionValueFirst,
2408 };
2409 const ops = ParseOptions{ .allocator = testing.allocator };
2410
2411 var ts = TokenStream.init("{\"values\":[[58]]}");
2412 const r = try parse(T, &ts, ops);
2413 defer parseFree(T, r, ops);
2414
2415 try testing.expectEqual(@as(i64, 58), r.values.array[0].array[0].integer);
2416}
2417
2418/// A non-stream JSON parser which constructs a tree of Value's.1763/// A non-stream JSON parser which constructs a tree of Value's.
2419pub const Parser = struct {1764pub const Parser = struct {
2420 allocator: Allocator,1765 allocator: Allocator,
...@@ -2719,219 +2064,6 @@ pub fn unescapeValidString(output: []u8, input: []const u8) UnescapeValidStringE...@@ -2719,219 +2064,6 @@ pub fn unescapeValidString(output: []u8, input: []const u8) UnescapeValidStringE
2719 assert(outIndex == output.len);2064 assert(outIndex == output.len);
2720}2065}
27212066
2722test "json.parser.dynamic" {
2723 var p = Parser.init(testing.allocator, false);
2724 defer p.deinit();
2725
2726 const s =
2727 \\{
2728 \\ "Image": {
2729 \\ "Width": 800,
2730 \\ "Height": 600,
2731 \\ "Title": "View from 15th Floor",
2732 \\ "Thumbnail": {
2733 \\ "Url": "http://www.example.com/image/481989943",
2734 \\ "Height": 125,
2735 \\ "Width": 100
2736 \\ },
2737 \\ "Animated" : false,
2738 \\ "IDs": [116, 943, 234, 38793],
2739 \\ "ArrayOfObject": [{"n": "m"}],
2740 \\ "double": 1.3412,
2741 \\ "LargeInt": 18446744073709551615
2742 \\ }
2743 \\}
2744 ;
2745
2746 var tree = try p.parse(s);
2747 defer tree.deinit();
2748
2749 var root = tree.root;
2750
2751 var image = root.Object.get("Image").?;
2752
2753 const width = image.Object.get("Width").?;
2754 try testing.expect(width.Integer == 800);
2755
2756 const height = image.Object.get("Height").?;
2757 try testing.expect(height.Integer == 600);
2758
2759 const title = image.Object.get("Title").?;
2760 try testing.expect(mem.eql(u8, title.String, "View from 15th Floor"));
2761
2762 const animated = image.Object.get("Animated").?;
2763 try testing.expect(animated.Bool == false);
2764
2765 const array_of_object = image.Object.get("ArrayOfObject").?;
2766 try testing.expect(array_of_object.Array.items.len == 1);
2767
2768 const obj0 = array_of_object.Array.items[0].Object.get("n").?;
2769 try testing.expect(mem.eql(u8, obj0.String, "m"));
2770
2771 const double = image.Object.get("double").?;
2772 try testing.expect(double.Float == 1.3412);
2773
2774 const large_int = image.Object.get("LargeInt").?;
2775 try testing.expect(mem.eql(u8, large_int.NumberString, "18446744073709551615"));
2776}
2777
2778test {
2779 _ = @import("json/test.zig");
2780 _ = @import("json/write_stream.zig");
2781}
2782
2783test "write json then parse it" {
2784 var out_buffer: [1000]u8 = undefined;
2785
2786 var fixed_buffer_stream = std.io.fixedBufferStream(&out_buffer);
2787 const out_stream = fixed_buffer_stream.writer();
2788 var jw = writeStream(out_stream, 4);
2789
2790 try jw.beginObject();
2791
2792 try jw.objectField("f");
2793 try jw.emitBool(false);
2794
2795 try jw.objectField("t");
2796 try jw.emitBool(true);
2797
2798 try jw.objectField("int");
2799 try jw.emitNumber(1234);
2800
2801 try jw.objectField("array");
2802 try jw.beginArray();
2803
2804 try jw.arrayElem();
2805 try jw.emitNull();
2806
2807 try jw.arrayElem();
2808 try jw.emitNumber(12.34);
2809
2810 try jw.endArray();
2811
2812 try jw.objectField("str");
2813 try jw.emitString("hello");
2814
2815 try jw.endObject();
2816
2817 var parser = Parser.init(testing.allocator, false);
2818 defer parser.deinit();
2819 var tree = try parser.parse(fixed_buffer_stream.getWritten());
2820 defer tree.deinit();
2821
2822 try testing.expect(tree.root.Object.get("f").?.Bool == false);
2823 try testing.expect(tree.root.Object.get("t").?.Bool == true);
2824 try testing.expect(tree.root.Object.get("int").?.Integer == 1234);
2825 try testing.expect(tree.root.Object.get("array").?.Array.items[0].Null == {});
2826 try testing.expect(tree.root.Object.get("array").?.Array.items[1].Float == 12.34);
2827 try testing.expect(mem.eql(u8, tree.root.Object.get("str").?.String, "hello"));
2828}
2829
2830fn testParse(arena_allocator: std.mem.Allocator, json_str: []const u8) !Value {
2831 var p = Parser.init(arena_allocator, false);
2832 return (try p.parse(json_str)).root;
2833}
2834
2835test "parsing empty string gives appropriate error" {
2836 var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
2837 defer arena_allocator.deinit();
2838 try testing.expectError(error.UnexpectedEndOfJson, testParse(arena_allocator.allocator(), ""));
2839}
2840
2841test "integer after float has proper type" {
2842 var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
2843 defer arena_allocator.deinit();
2844 const json = try testParse(arena_allocator.allocator(),
2845 \\{
2846 \\ "float": 3.14,
2847 \\ "ints": [1, 2, 3]
2848 \\}
2849 );
2850 try std.testing.expect(json.Object.get("ints").?.Array.items[0] == .Integer);
2851}
2852
2853test "parse exponential into int" {
2854 const T = struct { int: i64 };
2855 var ts = TokenStream.init("{ \"int\": 4.2e2 }");
2856 const r = try parse(T, &ts, ParseOptions{});
2857 try testing.expectEqual(@as(i64, 420), r.int);
2858 ts = TokenStream.init("{ \"int\": 0.042e2 }");
2859 try testing.expectError(error.InvalidNumber, parse(T, &ts, ParseOptions{}));
2860 ts = TokenStream.init("{ \"int\": 18446744073709551616.0 }");
2861 try testing.expectError(error.Overflow, parse(T, &ts, ParseOptions{}));
2862}
2863
2864test "escaped characters" {
2865 var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
2866 defer arena_allocator.deinit();
2867 const input =
2868 \\{
2869 \\ "backslash": "\\",
2870 \\ "forwardslash": "\/",
2871 \\ "newline": "\n",
2872 \\ "carriagereturn": "\r",
2873 \\ "tab": "\t",
2874 \\ "formfeed": "\f",
2875 \\ "backspace": "\b",
2876 \\ "doublequote": "\"",
2877 \\ "unicode": "\u0105",
2878 \\ "surrogatepair": "\ud83d\ude02"
2879 \\}
2880 ;
2881
2882 const obj = (try testParse(arena_allocator.allocator(), input)).Object;
2883
2884 try testing.expectEqualSlices(u8, obj.get("backslash").?.String, "\\");
2885 try testing.expectEqualSlices(u8, obj.get("forwardslash").?.String, "/");
2886 try testing.expectEqualSlices(u8, obj.get("newline").?.String, "\n");
2887 try testing.expectEqualSlices(u8, obj.get("carriagereturn").?.String, "\r");
2888 try testing.expectEqualSlices(u8, obj.get("tab").?.String, "\t");
2889 try testing.expectEqualSlices(u8, obj.get("formfeed").?.String, "\x0C");
2890 try testing.expectEqualSlices(u8, obj.get("backspace").?.String, "\x08");
2891 try testing.expectEqualSlices(u8, obj.get("doublequote").?.String, "\"");
2892 try testing.expectEqualSlices(u8, obj.get("unicode").?.String, "ą");
2893 try testing.expectEqualSlices(u8, obj.get("surrogatepair").?.String, "πŸ˜‚");
2894}
2895
2896test "string copy option" {
2897 const input =
2898 \\{
2899 \\ "noescape": "aąπŸ˜‚",
2900 \\ "simple": "\\\/\n\r\t\f\b\"",
2901 \\ "unicode": "\u0105",
2902 \\ "surrogatepair": "\ud83d\ude02"
2903 \\}
2904 ;
2905
2906 var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
2907 defer arena_allocator.deinit();
2908 const allocator = arena_allocator.allocator();
2909
2910 var parser = Parser.init(allocator, false);
2911 const tree_nocopy = try parser.parse(input);
2912 const obj_nocopy = tree_nocopy.root.Object;
2913
2914 parser = Parser.init(allocator, true);
2915 const tree_copy = try parser.parse(input);
2916 const obj_copy = tree_copy.root.Object;
2917
2918 for ([_][]const u8{ "noescape", "simple", "unicode", "surrogatepair" }) |field_name| {
2919 try testing.expectEqualSlices(u8, obj_nocopy.get(field_name).?.String, obj_copy.get(field_name).?.String);
2920 }
2921
2922 const nocopy_addr = &obj_nocopy.get("noescape").?.String[0];
2923 const copy_addr = &obj_copy.get("noescape").?.String[0];
2924
2925 var found_nocopy = false;
2926 for (input) |_, index| {
2927 try testing.expect(copy_addr != &input[index]);
2928 if (nocopy_addr == &input[index]) {
2929 found_nocopy = true;
2930 }
2931 }
2932 try testing.expect(found_nocopy);
2933}
2934
2935pub const StringifyOptions = struct {2067pub const StringifyOptions = struct {
2936 pub const Whitespace = struct {2068 pub const Whitespace = struct {
2937 /// How many indentation levels deep are we?2069 /// How many indentation levels deep are we?
...@@ -3213,60 +2345,102 @@ pub fn stringify(...@@ -3213,60 +2345,102 @@ pub fn stringify(
3213 unreachable;2345 unreachable;
3214}2346}
32152347
3216fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions) !void {2348// Same as `stringify` but accepts an Allocator and stores result in dynamically allocated memory instead of using a Writer.
3217 const ValidationWriter = struct {2349// Caller owns returned memory.
3218 const Self = @This();2350pub fn stringifyAlloc(allocator: std.mem.Allocator, value: anytype, options: StringifyOptions) ![]const u8 {
3219 pub const Writer = std.io.Writer(*Self, Error, write);2351 var list = std.ArrayList(u8).init(allocator);
3220 pub const Error = error{2352 errdefer list.deinit();
3221 TooMuchData,2353 try stringify(value, options, list.writer());
3222 DifferentData,2354 return list.toOwnedSlice();
3223 };2355}
32242356
3225 expected_remaining: []const u8,2357test {
2358 if (builtin.zig_backend != .stage1) {
2359 // https://github.com/ziglang/zig/issues/8442
2360 _ = @import("json/test.zig");
2361 }
2362 _ = @import("json/write_stream.zig");
2363}
32262364
3227 fn init(exp: []const u8) Self {2365test "stringify null optional fields" {
3228 return .{ .expected_remaining = exp };2366 const MyStruct = struct {
3229 }2367 optional: ?[]const u8 = null,
2368 required: []const u8 = "something",
2369 another_optional: ?[]const u8 = null,
2370 another_required: []const u8 = "something else",
2371 };
2372 try teststringify(
2373 \\{"optional":null,"required":"something","another_optional":null,"another_required":"something else"}
2374 ,
2375 MyStruct{},
2376 StringifyOptions{},
2377 );
2378 try teststringify(
2379 \\{"required":"something","another_required":"something else"}
2380 ,
2381 MyStruct{},
2382 StringifyOptions{ .emit_null_optional_fields = false },
2383 );
32302384
3231 pub fn writer(self: *Self) Writer {2385 var ts = TokenStream.init(
3232 return .{ .context = self };2386 \\{"required":"something","another_required":"something else"}
3233 }2387 );
2388 try std.testing.expect(try parsesTo(MyStruct, MyStruct{}, &ts, .{
2389 .allocator = std.testing.allocator,
2390 }));
2391}
32342392
3235 fn write(self: *Self, bytes: []const u8) Error!usize {2393test "skipValue" {
3236 if (self.expected_remaining.len < bytes.len) {2394 var ts = TokenStream.init("false");
3237 std.debug.print(2395 try skipValue(&ts);
3238 \\====== expected this output: =========2396 ts = TokenStream.init("true");
3239 \\{s}2397 try skipValue(&ts);
3240 \\======== instead found this: =========2398 ts = TokenStream.init("null");
3241 \\{s}2399 try skipValue(&ts);
3242 \\======================================2400 ts = TokenStream.init("42");
3243 , .{2401 try skipValue(&ts);
3244 self.expected_remaining,2402 ts = TokenStream.init("42.0");
3245 bytes,2403 try skipValue(&ts);
3246 });2404 ts = TokenStream.init("\"foo\"");
3247 return error.TooMuchData;2405 try skipValue(&ts);
3248 }2406 ts = TokenStream.init("[101, 111, 121]");
3249 if (!mem.eql(u8, self.expected_remaining[0..bytes.len], bytes)) {2407 try skipValue(&ts);
3250 std.debug.print(2408 ts = TokenStream.init("{}");
3251 \\====== expected this output: =========2409 try skipValue(&ts);
3252 \\{s}2410 ts = TokenStream.init("{\"foo\": \"bar\"}");
3253 \\======== instead found this: =========2411 try skipValue(&ts);
3254 \\{s}
3255 \\======================================
3256 , .{
3257 self.expected_remaining[0..bytes.len],
3258 bytes,
3259 });
3260 return error.DifferentData;
3261 }
3262 self.expected_remaining = self.expected_remaining[bytes.len..];
3263 return bytes.len;
3264 }
3265 };
32662412
3267 var vos = ValidationWriter.init(expected);2413 { // An absurd number of nestings
3268 try stringify(value, options, vos.writer());2414 const nestings = StreamingParser.default_max_nestings + 1;
3269 if (vos.expected_remaining.len > 0) return error.NotEnoughData;2415
2416 ts = TokenStream.init("[" ** nestings ++ "]" ** nestings);
2417 try testing.expectError(error.TooManyNestedItems, skipValue(&ts));
2418 }
2419
2420 { // Would a number token cause problems in a deeply-nested array?
2421 const nestings = StreamingParser.default_max_nestings;
2422 const deeply_nested_array = "[" ** nestings ++ "0.118, 999, 881.99, 911.9, 725, 3" ++ "]" ** nestings;
2423
2424 ts = TokenStream.init(deeply_nested_array);
2425 try skipValue(&ts);
2426
2427 ts = TokenStream.init("[" ++ deeply_nested_array ++ "]");
2428 try testing.expectError(error.TooManyNestedItems, skipValue(&ts));
2429 }
2430
2431 // Mismatched brace/square bracket
2432 ts = TokenStream.init("[102, 111, 111}");
2433 try testing.expectError(error.UnexpectedClosingBrace, skipValue(&ts));
2434
2435 { // should fail if no value found (e.g. immediate close of object)
2436 var empty_object = TokenStream.init("{}");
2437 assert(.ObjectBegin == (try empty_object.next()).?);
2438 try testing.expectError(error.UnexpectedJsonDepth, skipValue(&empty_object));
2439
2440 var empty_array = TokenStream.init("[]");
2441 assert(.ArrayBegin == (try empty_array.next()).?);
2442 try testing.expectError(error.UnexpectedJsonDepth, skipValue(&empty_array));
2443 }
3270}2444}
32712445
3272test "stringify basic types" {2446test "stringify basic types" {
...@@ -3423,50 +2597,74 @@ test "stringify vector" {...@@ -3423,50 +2597,74 @@ test "stringify vector" {
3423 try teststringify("[1,1]", @splat(2, @as(u32, 1)), StringifyOptions{});2597 try teststringify("[1,1]", @splat(2, @as(u32, 1)), StringifyOptions{});
3424}2598}
34252599
3426test "stringify null optional fields" {2600fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions) !void {
3427 const MyStruct = struct {2601 const ValidationWriter = struct {
3428 optional: ?[]const u8 = null,2602 const Self = @This();
3429 required: []const u8 = "something",2603 pub const Writer = std.io.Writer(*Self, Error, write);
3430 another_optional: ?[]const u8 = null,2604 pub const Error = error{
3431 another_required: []const u8 = "something else",2605 TooMuchData,
3432 };2606 DifferentData,
3433 try teststringify(2607 };
3434 \\{"optional":null,"required":"something","another_optional":null,"another_required":"something else"}
3435 ,
3436 MyStruct{},
3437 StringifyOptions{},
3438 );
3439 try teststringify(
3440 \\{"required":"something","another_required":"something else"}
3441 ,
3442 MyStruct{},
3443 StringifyOptions{ .emit_null_optional_fields = false },
3444 );
34452608
3446 var ts = TokenStream.init(2609 expected_remaining: []const u8,
3447 \\{"required":"something","another_required":"something else"}
3448 );
3449 try std.testing.expect(try parsesTo(MyStruct, MyStruct{}, &ts, .{
3450 .allocator = std.testing.allocator,
3451 }));
3452}
34532610
3454// Same as `stringify` but accepts an Allocator and stores result in dynamically allocated memory instead of using a Writer.2611 fn init(exp: []const u8) Self {
3455// Caller owns returned memory.2612 return .{ .expected_remaining = exp };
3456pub fn stringifyAlloc(allocator: std.mem.Allocator, value: anytype, options: StringifyOptions) ![]const u8 {2613 }
3457 var list = std.ArrayList(u8).init(allocator);
3458 errdefer list.deinit();
3459 try stringify(value, options, list.writer());
3460 return list.toOwnedSlice();
3461}
34622614
3463test "stringify alloc" {2615 pub fn writer(self: *Self) Writer {
3464 const allocator = std.testing.allocator;2616 return .{ .context = self };
3465 const expected =2617 }
3466 \\{"foo":"bar","answer":42,"my_friend":"sammy"}
3467 ;
3468 const actual = try stringifyAlloc(allocator, .{ .foo = "bar", .answer = 42, .my_friend = "sammy" }, .{});
3469 defer allocator.free(actual);
34702618
3471 try std.testing.expectEqualStrings(expected, actual);2619 fn write(self: *Self, bytes: []const u8) Error!usize {
2620 if (self.expected_remaining.len < bytes.len) {
2621 std.debug.print(
2622 \\====== expected this output: =========
2623 \\{s}
2624 \\======== instead found this: =========
2625 \\{s}
2626 \\======================================
2627 , .{
2628 self.expected_remaining,
2629 bytes,
2630 });
2631 return error.TooMuchData;
2632 }
2633 if (!mem.eql(u8, self.expected_remaining[0..bytes.len], bytes)) {
2634 std.debug.print(
2635 \\====== expected this output: =========
2636 \\{s}
2637 \\======== instead found this: =========
2638 \\{s}
2639 \\======================================
2640 , .{
2641 self.expected_remaining[0..bytes.len],
2642 bytes,
2643 });
2644 return error.DifferentData;
2645 }
2646 self.expected_remaining = self.expected_remaining[bytes.len..];
2647 return bytes.len;
2648 }
2649 };
2650
2651 var vos = ValidationWriter.init(expected);
2652 try stringify(value, options, vos.writer());
2653 if (vos.expected_remaining.len > 0) return error.NotEnoughData;
2654}
2655
2656test "encodesTo" {
2657 // same
2658 try testing.expectEqual(true, encodesTo("false", "false"));
2659 // totally different
2660 try testing.expectEqual(false, encodesTo("false", "true"));
2661 // different lengths
2662 try testing.expectEqual(false, encodesTo("false", "other"));
2663 // with escape
2664 try testing.expectEqual(true, encodesTo("\\", "\\\\"));
2665 try testing.expectEqual(true, encodesTo("with\nescape", "with\\nescape"));
2666 // with unicode
2667 try testing.expectEqual(true, encodesTo("ą", "\\u0105"));
2668 try testing.expectEqual(true, encodesTo("πŸ˜‚", "\\ud83d\\ude02"));
2669 try testing.expectEqual(true, encodesTo("withąunicodeπŸ˜‚", "with\\u0105unicode\\ud83d\\ude02"));
3472}2670}
lib/std/json/test.zig+823
...@@ -6,6 +6,23 @@...@@ -6,6 +6,23 @@
6const std = @import("../std.zig");6const std = @import("../std.zig");
7const json = std.json;7const json = std.json;
8const testing = std.testing;8const testing = std.testing;
9const TokenStream = std.json.TokenStream;
10const parse = std.json.parse;
11const ParseOptions = std.json.ParseOptions;
12const parseFree = std.json.parseFree;
13const Parser = std.json.Parser;
14const mem = std.mem;
15const writeStream = std.json.writeStream;
16const Value = std.json.Value;
17const StringifyOptions = std.json.StringifyOptions;
18const stringify = std.json.stringify;
19const stringifyAlloc = std.json.stringifyAlloc;
20const StreamingParser = std.json.StreamingParser;
21const Token = std.json.Token;
22const validate = std.json.validate;
23const Array = std.json.Array;
24const ObjectMap = std.json.ObjectMap;
25const assert = std.debug.assert;
926
10fn testNonStreaming(s: []const u8) !void {27fn testNonStreaming(s: []const u8) !void {
11 var p = json.Parser.init(testing.allocator, false);28 var p = json.Parser.init(testing.allocator, false);
...@@ -2004,3 +2021,809 @@ test "out of UTF-16 range" {...@@ -2004,3 +2021,809 @@ test "out of UTF-16 range" {
2004 try utf8Error("\"\xfe\x80\x80\x80\"");2021 try utf8Error("\"\xfe\x80\x80\x80\"");
2005 try utf8Error("\"\xff\x80\x80\x80\"");2022 try utf8Error("\"\xff\x80\x80\x80\"");
2006}2023}
2024
2025test "parse" {
2026 var ts = TokenStream.init("false");
2027 try testing.expectEqual(false, try parse(bool, &ts, ParseOptions{}));
2028 ts = TokenStream.init("true");
2029 try testing.expectEqual(true, try parse(bool, &ts, ParseOptions{}));
2030 ts = TokenStream.init("1");
2031 try testing.expectEqual(@as(u1, 1), try parse(u1, &ts, ParseOptions{}));
2032 ts = TokenStream.init("50");
2033 try testing.expectError(error.Overflow, parse(u1, &ts, ParseOptions{}));
2034 ts = TokenStream.init("42");
2035 try testing.expectEqual(@as(u64, 42), try parse(u64, &ts, ParseOptions{}));
2036 ts = TokenStream.init("42.0");
2037 try testing.expectEqual(@as(f64, 42), try parse(f64, &ts, ParseOptions{}));
2038 ts = TokenStream.init("null");
2039 try testing.expectEqual(@as(?bool, null), try parse(?bool, &ts, ParseOptions{}));
2040 ts = TokenStream.init("true");
2041 try testing.expectEqual(@as(?bool, true), try parse(?bool, &ts, ParseOptions{}));
2042
2043 ts = TokenStream.init("\"foo\"");
2044 try testing.expectEqual(@as([3]u8, "foo".*), try parse([3]u8, &ts, ParseOptions{}));
2045 ts = TokenStream.init("[102, 111, 111]");
2046 try testing.expectEqual(@as([3]u8, "foo".*), try parse([3]u8, &ts, ParseOptions{}));
2047 ts = TokenStream.init("[]");
2048 try testing.expectEqual(@as([0]u8, undefined), try parse([0]u8, &ts, ParseOptions{}));
2049}
2050
2051test "parse into enum" {
2052 const T = enum(u32) {
2053 Foo = 42,
2054 Bar,
2055 @"with\\escape",
2056 };
2057 var ts = TokenStream.init("\"Foo\"");
2058 try testing.expectEqual(@as(T, .Foo), try parse(T, &ts, ParseOptions{}));
2059 ts = TokenStream.init("42");
2060 try testing.expectEqual(@as(T, .Foo), try parse(T, &ts, ParseOptions{}));
2061 ts = TokenStream.init("\"with\\\\escape\"");
2062 try testing.expectEqual(@as(T, .@"with\\escape"), try parse(T, &ts, ParseOptions{}));
2063 ts = TokenStream.init("5");
2064 try testing.expectError(error.InvalidEnumTag, parse(T, &ts, ParseOptions{}));
2065 ts = TokenStream.init("\"Qux\"");
2066 try testing.expectError(error.InvalidEnumTag, parse(T, &ts, ParseOptions{}));
2067}
2068
2069test "parse with trailing data" {
2070 var ts = TokenStream.init("falsed");
2071 try testing.expectEqual(false, try parse(bool, &ts, ParseOptions{ .allow_trailing_data = true }));
2072 ts = TokenStream.init("falsed");
2073 try testing.expectError(error.InvalidTopLevelTrailing, parse(bool, &ts, ParseOptions{ .allow_trailing_data = false }));
2074 // trailing whitespace is okay
2075 ts = TokenStream.init("false \n");
2076 try testing.expectEqual(false, try parse(bool, &ts, ParseOptions{ .allow_trailing_data = false }));
2077}
2078
2079test "parse into that allocates a slice" {
2080 var ts = TokenStream.init("\"foo\"");
2081 try testing.expectError(error.AllocatorRequired, parse([]u8, &ts, ParseOptions{}));
2082
2083 const options = ParseOptions{ .allocator = testing.allocator };
2084 {
2085 ts = TokenStream.init("\"foo\"");
2086 const r = try parse([]u8, &ts, options);
2087 defer parseFree([]u8, r, options);
2088 try testing.expectEqualSlices(u8, "foo", r);
2089 }
2090 {
2091 ts = TokenStream.init("[102, 111, 111]");
2092 const r = try parse([]u8, &ts, options);
2093 defer parseFree([]u8, r, options);
2094 try testing.expectEqualSlices(u8, "foo", r);
2095 }
2096 {
2097 ts = TokenStream.init("\"with\\\\escape\"");
2098 const r = try parse([]u8, &ts, options);
2099 defer parseFree([]u8, r, options);
2100 try testing.expectEqualSlices(u8, "with\\escape", r);
2101 }
2102}
2103
2104test "parse into tagged union" {
2105 {
2106 const T = union(enum) {
2107 int: i32,
2108 float: f64,
2109 string: []const u8,
2110 };
2111 var ts = TokenStream.init("1.5");
2112 try testing.expectEqual(T{ .float = 1.5 }, try parse(T, &ts, ParseOptions{}));
2113 }
2114
2115 { // failing allocations should be bubbled up instantly without trying next member
2116 var fail_alloc = testing.FailingAllocator.init(testing.allocator, 0);
2117 const options = ParseOptions{ .allocator = fail_alloc.allocator() };
2118 const T = union(enum) {
2119 // both fields here match the input
2120 string: []const u8,
2121 array: [3]u8,
2122 };
2123 var ts = TokenStream.init("[1,2,3]");
2124 try testing.expectError(error.OutOfMemory, parse(T, &ts, options));
2125 }
2126
2127 {
2128 // if multiple matches possible, takes first option
2129 const T = union(enum) {
2130 x: u8,
2131 y: u8,
2132 };
2133 var ts = TokenStream.init("42");
2134 try testing.expectEqual(T{ .x = 42 }, try parse(T, &ts, ParseOptions{}));
2135 }
2136
2137 { // needs to back out when first union member doesn't match
2138 const T = union(enum) {
2139 A: struct { x: u32 },
2140 B: struct { y: u32 },
2141 };
2142 var ts = TokenStream.init("{\"y\":42}");
2143 try testing.expectEqual(T{ .B = .{ .y = 42 } }, try parse(T, &ts, ParseOptions{}));
2144 }
2145}
2146
2147test "parse union bubbles up AllocatorRequired" {
2148 { // string member first in union (and not matching)
2149 const T = union(enum) {
2150 string: []const u8,
2151 int: i32,
2152 };
2153 var ts = TokenStream.init("42");
2154 try testing.expectError(error.AllocatorRequired, parse(T, &ts, ParseOptions{}));
2155 }
2156
2157 { // string member not first in union (and matching)
2158 const T = union(enum) {
2159 int: i32,
2160 float: f64,
2161 string: []const u8,
2162 };
2163 var ts = TokenStream.init("\"foo\"");
2164 try testing.expectError(error.AllocatorRequired, parse(T, &ts, ParseOptions{}));
2165 }
2166}
2167
2168test "parseFree descends into tagged union" {
2169 var fail_alloc = testing.FailingAllocator.init(testing.allocator, 1);
2170 const options = ParseOptions{ .allocator = fail_alloc.allocator() };
2171 const T = union(enum) {
2172 int: i32,
2173 float: f64,
2174 string: []const u8,
2175 };
2176 // use a string with unicode escape so we know result can't be a reference to global constant
2177 var ts = TokenStream.init("\"with\\u0105unicode\"");
2178 const r = try parse(T, &ts, options);
2179 try testing.expectEqual(std.meta.Tag(T).string, @as(std.meta.Tag(T), r));
2180 try testing.expectEqualSlices(u8, "withąunicode", r.string);
2181 try testing.expectEqual(@as(usize, 0), fail_alloc.deallocations);
2182 parseFree(T, r, options);
2183 try testing.expectEqual(@as(usize, 1), fail_alloc.deallocations);
2184}
2185
2186test "parse with comptime field" {
2187 {
2188 const T = struct {
2189 comptime a: i32 = 0,
2190 b: bool,
2191 };
2192 var ts = TokenStream.init(
2193 \\{
2194 \\ "a": 0,
2195 \\ "b": true
2196 \\}
2197 );
2198 try testing.expectEqual(T{ .a = 0, .b = true }, try parse(T, &ts, ParseOptions{}));
2199 }
2200
2201 { // string comptime values currently require an allocator
2202 const T = union(enum) {
2203 foo: struct {
2204 comptime kind: []const u8 = "boolean",
2205 b: bool,
2206 },
2207 bar: struct {
2208 comptime kind: []const u8 = "float",
2209 b: f64,
2210 },
2211 };
2212
2213 const options = ParseOptions{
2214 .allocator = std.testing.allocator,
2215 };
2216
2217 var ts = TokenStream.init(
2218 \\{
2219 \\ "kind": "float",
2220 \\ "b": 1.0
2221 \\}
2222 );
2223 const r = try parse(T, &ts, options);
2224
2225 // check that parseFree doesn't try to free comptime fields
2226 parseFree(T, r, options);
2227 }
2228}
2229
2230test "parse into struct with no fields" {
2231 const T = struct {};
2232 var ts = TokenStream.init("{}");
2233 try testing.expectEqual(T{}, try parse(T, &ts, ParseOptions{}));
2234}
2235
2236test "parse into struct with misc fields" {
2237 @setEvalBranchQuota(10000);
2238 const options = ParseOptions{ .allocator = testing.allocator };
2239 const T = struct {
2240 int: i64,
2241 float: f64,
2242 @"with\\escape": bool,
2243 @"withąunicodeπŸ˜‚": bool,
2244 language: []const u8,
2245 optional: ?bool,
2246 default_field: i32 = 42,
2247 static_array: [3]f64,
2248 dynamic_array: []f64,
2249
2250 complex: struct {
2251 nested: []const u8,
2252 },
2253
2254 veryComplex: []struct {
2255 foo: []const u8,
2256 },
2257
2258 a_union: Union,
2259 const Union = union(enum) {
2260 x: u8,
2261 float: f64,
2262 string: []const u8,
2263 };
2264 };
2265 var ts = TokenStream.init(
2266 \\{
2267 \\ "int": 420,
2268 \\ "float": 3.14,
2269 \\ "with\\escape": true,
2270 \\ "with\u0105unicode\ud83d\ude02": false,
2271 \\ "language": "zig",
2272 \\ "optional": null,
2273 \\ "static_array": [66.6, 420.420, 69.69],
2274 \\ "dynamic_array": [66.6, 420.420, 69.69],
2275 \\ "complex": {
2276 \\ "nested": "zig"
2277 \\ },
2278 \\ "veryComplex": [
2279 \\ {
2280 \\ "foo": "zig"
2281 \\ }, {
2282 \\ "foo": "rocks"
2283 \\ }
2284 \\ ],
2285 \\ "a_union": 100000
2286 \\}
2287 );
2288 const r = try parse(T, &ts, options);
2289 defer parseFree(T, r, options);
2290 try testing.expectEqual(@as(i64, 420), r.int);
2291 try testing.expectEqual(@as(f64, 3.14), r.float);
2292 try testing.expectEqual(true, r.@"with\\escape");
2293 try testing.expectEqual(false, r.@"withąunicodeπŸ˜‚");
2294 try testing.expectEqualSlices(u8, "zig", r.language);
2295 try testing.expectEqual(@as(?bool, null), r.optional);
2296 try testing.expectEqual(@as(i32, 42), r.default_field);
2297 try testing.expectEqual(@as(f64, 66.6), r.static_array[0]);
2298 try testing.expectEqual(@as(f64, 420.420), r.static_array[1]);
2299 try testing.expectEqual(@as(f64, 69.69), r.static_array[2]);
2300 try testing.expectEqual(@as(usize, 3), r.dynamic_array.len);
2301 try testing.expectEqual(@as(f64, 66.6), r.dynamic_array[0]);
2302 try testing.expectEqual(@as(f64, 420.420), r.dynamic_array[1]);
2303 try testing.expectEqual(@as(f64, 69.69), r.dynamic_array[2]);
2304 try testing.expectEqualSlices(u8, r.complex.nested, "zig");
2305 try testing.expectEqualSlices(u8, "zig", r.veryComplex[0].foo);
2306 try testing.expectEqualSlices(u8, "rocks", r.veryComplex[1].foo);
2307 try testing.expectEqual(T.Union{ .float = 100000 }, r.a_union);
2308}
2309
2310test "parse into struct with strings and arrays with sentinels" {
2311 @setEvalBranchQuota(10000);
2312 const options = ParseOptions{ .allocator = testing.allocator };
2313 const T = struct {
2314 language: [:0]const u8,
2315 language_without_sentinel: []const u8,
2316 data: [:99]const i32,
2317 simple_data: []const i32,
2318 };
2319 var ts = TokenStream.init(
2320 \\{
2321 \\ "language": "zig",
2322 \\ "language_without_sentinel": "zig again!",
2323 \\ "data": [1, 2, 3],
2324 \\ "simple_data": [4, 5, 6]
2325 \\}
2326 );
2327 const r = try parse(T, &ts, options);
2328 defer parseFree(T, r, options);
2329
2330 try testing.expectEqualSentinel(u8, 0, "zig", r.language);
2331
2332 const data = [_:99]i32{ 1, 2, 3 };
2333 try testing.expectEqualSentinel(i32, 99, data[0..data.len], r.data);
2334
2335 // Make sure that arrays who aren't supposed to have a sentinel still parse without one.
2336 try testing.expectEqual(@as(?i32, null), std.meta.sentinel(@TypeOf(r.simple_data)));
2337 try testing.expectEqual(@as(?u8, null), std.meta.sentinel(@TypeOf(r.language_without_sentinel)));
2338}
2339
2340test "parse into struct with duplicate field" {
2341 // allow allocator to detect double frees by keeping bucket in use
2342 const ballast = try testing.allocator.alloc(u64, 1);
2343 defer testing.allocator.free(ballast);
2344
2345 const options_first = ParseOptions{ .allocator = testing.allocator, .duplicate_field_behavior = .UseFirst };
2346
2347 const options_last = ParseOptions{
2348 .allocator = testing.allocator,
2349 .duplicate_field_behavior = .UseLast,
2350 };
2351
2352 const str = "{ \"a\": 1, \"a\": 0.25 }";
2353
2354 const T1 = struct { a: *u64 };
2355 // both .UseFirst and .UseLast should fail because second "a" value isn't a u64
2356 var ts = TokenStream.init(str);
2357 try testing.expectError(error.InvalidNumber, parse(T1, &ts, options_first));
2358 ts = TokenStream.init(str);
2359 try testing.expectError(error.InvalidNumber, parse(T1, &ts, options_last));
2360
2361 const T2 = struct { a: f64 };
2362 ts = TokenStream.init(str);
2363 try testing.expectEqual(T2{ .a = 1.0 }, try parse(T2, &ts, options_first));
2364 ts = TokenStream.init(str);
2365 try testing.expectEqual(T2{ .a = 0.25 }, try parse(T2, &ts, options_last));
2366
2367 const T3 = struct { comptime a: f64 = 1.0 };
2368 // .UseFirst should succeed because second "a" value is unconditionally ignored (even though != 1.0)
2369 const t3 = T3{ .a = 1.0 };
2370 ts = TokenStream.init(str);
2371 try testing.expectEqual(t3, try parse(T3, &ts, options_first));
2372 // .UseLast should fail because second "a" value is 0.25 which is not equal to default value of 1.0
2373 ts = TokenStream.init(str);
2374 try testing.expectError(error.UnexpectedValue, parse(T3, &ts, options_last));
2375}
2376
2377test "parse into struct ignoring unknown fields" {
2378 const T = struct {
2379 int: i64,
2380 language: []const u8,
2381 };
2382
2383 const ops = ParseOptions{
2384 .allocator = testing.allocator,
2385 .ignore_unknown_fields = true,
2386 };
2387
2388 var ts = TokenStream.init(
2389 \\{
2390 \\ "int": 420,
2391 \\ "float": 3.14,
2392 \\ "with\\escape": true,
2393 \\ "with\u0105unicode\ud83d\ude02": false,
2394 \\ "optional": null,
2395 \\ "static_array": [66.6, 420.420, 69.69],
2396 \\ "dynamic_array": [66.6, 420.420, 69.69],
2397 \\ "complex": {
2398 \\ "nested": "zig"
2399 \\ },
2400 \\ "veryComplex": [
2401 \\ {
2402 \\ "foo": "zig"
2403 \\ }, {
2404 \\ "foo": "rocks"
2405 \\ }
2406 \\ ],
2407 \\ "a_union": 100000,
2408 \\ "language": "zig"
2409 \\}
2410 );
2411 const r = try parse(T, &ts, ops);
2412 defer parseFree(T, r, ops);
2413
2414 try testing.expectEqual(@as(i64, 420), r.int);
2415 try testing.expectEqualSlices(u8, "zig", r.language);
2416}
2417
2418const ParseIntoRecursiveUnionDefinitionValue = union(enum) {
2419 integer: i64,
2420 array: []const ParseIntoRecursiveUnionDefinitionValue,
2421};
2422
2423test "parse into recursive union definition" {
2424 const T = struct {
2425 values: ParseIntoRecursiveUnionDefinitionValue,
2426 };
2427 const ops = ParseOptions{ .allocator = testing.allocator };
2428
2429 var ts = TokenStream.init("{\"values\":[58]}");
2430 const r = try parse(T, &ts, ops);
2431 defer parseFree(T, r, ops);
2432
2433 try testing.expectEqual(@as(i64, 58), r.values.array[0].integer);
2434}
2435
2436const ParseIntoDoubleRecursiveUnionValueFirst = union(enum) {
2437 integer: i64,
2438 array: []const ParseIntoDoubleRecursiveUnionValueSecond,
2439};
2440
2441const ParseIntoDoubleRecursiveUnionValueSecond = union(enum) {
2442 boolean: bool,
2443 array: []const ParseIntoDoubleRecursiveUnionValueFirst,
2444};
2445
2446test "parse into double recursive union definition" {
2447 const T = struct {
2448 values: ParseIntoDoubleRecursiveUnionValueFirst,
2449 };
2450 const ops = ParseOptions{ .allocator = testing.allocator };
2451
2452 var ts = TokenStream.init("{\"values\":[[58]]}");
2453 const r = try parse(T, &ts, ops);
2454 defer parseFree(T, r, ops);
2455
2456 try testing.expectEqual(@as(i64, 58), r.values.array[0].array[0].integer);
2457}
2458
2459test "json.parser.dynamic" {
2460 var p = Parser.init(testing.allocator, false);
2461 defer p.deinit();
2462
2463 const s =
2464 \\{
2465 \\ "Image": {
2466 \\ "Width": 800,
2467 \\ "Height": 600,
2468 \\ "Title": "View from 15th Floor",
2469 \\ "Thumbnail": {
2470 \\ "Url": "http://www.example.com/image/481989943",
2471 \\ "Height": 125,
2472 \\ "Width": 100
2473 \\ },
2474 \\ "Animated" : false,
2475 \\ "IDs": [116, 943, 234, 38793],
2476 \\ "ArrayOfObject": [{"n": "m"}],
2477 \\ "double": 1.3412,
2478 \\ "LargeInt": 18446744073709551615
2479 \\ }
2480 \\}
2481 ;
2482
2483 var tree = try p.parse(s);
2484 defer tree.deinit();
2485
2486 var root = tree.root;
2487
2488 var image = root.Object.get("Image").?;
2489
2490 const width = image.Object.get("Width").?;
2491 try testing.expect(width.Integer == 800);
2492
2493 const height = image.Object.get("Height").?;
2494 try testing.expect(height.Integer == 600);
2495
2496 const title = image.Object.get("Title").?;
2497 try testing.expect(mem.eql(u8, title.String, "View from 15th Floor"));
2498
2499 const animated = image.Object.get("Animated").?;
2500 try testing.expect(animated.Bool == false);
2501
2502 const array_of_object = image.Object.get("ArrayOfObject").?;
2503 try testing.expect(array_of_object.Array.items.len == 1);
2504
2505 const obj0 = array_of_object.Array.items[0].Object.get("n").?;
2506 try testing.expect(mem.eql(u8, obj0.String, "m"));
2507
2508 const double = image.Object.get("double").?;
2509 try testing.expect(double.Float == 1.3412);
2510
2511 const large_int = image.Object.get("LargeInt").?;
2512 try testing.expect(mem.eql(u8, large_int.NumberString, "18446744073709551615"));
2513}
2514
2515test "write json then parse it" {
2516 var out_buffer: [1000]u8 = undefined;
2517
2518 var fixed_buffer_stream = std.io.fixedBufferStream(&out_buffer);
2519 const out_stream = fixed_buffer_stream.writer();
2520 var jw = writeStream(out_stream, 4);
2521
2522 try jw.beginObject();
2523
2524 try jw.objectField("f");
2525 try jw.emitBool(false);
2526
2527 try jw.objectField("t");
2528 try jw.emitBool(true);
2529
2530 try jw.objectField("int");
2531 try jw.emitNumber(1234);
2532
2533 try jw.objectField("array");
2534 try jw.beginArray();
2535
2536 try jw.arrayElem();
2537 try jw.emitNull();
2538
2539 try jw.arrayElem();
2540 try jw.emitNumber(12.34);
2541
2542 try jw.endArray();
2543
2544 try jw.objectField("str");
2545 try jw.emitString("hello");
2546
2547 try jw.endObject();
2548
2549 var parser = Parser.init(testing.allocator, false);
2550 defer parser.deinit();
2551 var tree = try parser.parse(fixed_buffer_stream.getWritten());
2552 defer tree.deinit();
2553
2554 try testing.expect(tree.root.Object.get("f").?.Bool == false);
2555 try testing.expect(tree.root.Object.get("t").?.Bool == true);
2556 try testing.expect(tree.root.Object.get("int").?.Integer == 1234);
2557 try testing.expect(tree.root.Object.get("array").?.Array.items[0].Null == {});
2558 try testing.expect(tree.root.Object.get("array").?.Array.items[1].Float == 12.34);
2559 try testing.expect(mem.eql(u8, tree.root.Object.get("str").?.String, "hello"));
2560}
2561
2562fn testParse(arena_allocator: std.mem.Allocator, json_str: []const u8) !Value {
2563 var p = Parser.init(arena_allocator, false);
2564 return (try p.parse(json_str)).root;
2565}
2566
2567test "parsing empty string gives appropriate error" {
2568 var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
2569 defer arena_allocator.deinit();
2570 try testing.expectError(error.UnexpectedEndOfJson, testParse(arena_allocator.allocator(), ""));
2571}
2572
2573test "integer after float has proper type" {
2574 var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
2575 defer arena_allocator.deinit();
2576 const parsed = try testParse(arena_allocator.allocator(),
2577 \\{
2578 \\ "float": 3.14,
2579 \\ "ints": [1, 2, 3]
2580 \\}
2581 );
2582 try std.testing.expect(parsed.Object.get("ints").?.Array.items[0] == .Integer);
2583}
2584
2585test "parse exponential into int" {
2586 const T = struct { int: i64 };
2587 var ts = TokenStream.init("{ \"int\": 4.2e2 }");
2588 const r = try parse(T, &ts, ParseOptions{});
2589 try testing.expectEqual(@as(i64, 420), r.int);
2590 ts = TokenStream.init("{ \"int\": 0.042e2 }");
2591 try testing.expectError(error.InvalidNumber, parse(T, &ts, ParseOptions{}));
2592 ts = TokenStream.init("{ \"int\": 18446744073709551616.0 }");
2593 try testing.expectError(error.Overflow, parse(T, &ts, ParseOptions{}));
2594}
2595
2596test "escaped characters" {
2597 var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
2598 defer arena_allocator.deinit();
2599 const input =
2600 \\{
2601 \\ "backslash": "\\",
2602 \\ "forwardslash": "\/",
2603 \\ "newline": "\n",
2604 \\ "carriagereturn": "\r",
2605 \\ "tab": "\t",
2606 \\ "formfeed": "\f",
2607 \\ "backspace": "\b",
2608 \\ "doublequote": "\"",
2609 \\ "unicode": "\u0105",
2610 \\ "surrogatepair": "\ud83d\ude02"
2611 \\}
2612 ;
2613
2614 const obj = (try testParse(arena_allocator.allocator(), input)).Object;
2615
2616 try testing.expectEqualSlices(u8, obj.get("backslash").?.String, "\\");
2617 try testing.expectEqualSlices(u8, obj.get("forwardslash").?.String, "/");
2618 try testing.expectEqualSlices(u8, obj.get("newline").?.String, "\n");
2619 try testing.expectEqualSlices(u8, obj.get("carriagereturn").?.String, "\r");
2620 try testing.expectEqualSlices(u8, obj.get("tab").?.String, "\t");
2621 try testing.expectEqualSlices(u8, obj.get("formfeed").?.String, "\x0C");
2622 try testing.expectEqualSlices(u8, obj.get("backspace").?.String, "\x08");
2623 try testing.expectEqualSlices(u8, obj.get("doublequote").?.String, "\"");
2624 try testing.expectEqualSlices(u8, obj.get("unicode").?.String, "ą");
2625 try testing.expectEqualSlices(u8, obj.get("surrogatepair").?.String, "πŸ˜‚");
2626}
2627
2628test "string copy option" {
2629 const input =
2630 \\{
2631 \\ "noescape": "aąπŸ˜‚",
2632 \\ "simple": "\\\/\n\r\t\f\b\"",
2633 \\ "unicode": "\u0105",
2634 \\ "surrogatepair": "\ud83d\ude02"
2635 \\}
2636 ;
2637
2638 var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
2639 defer arena_allocator.deinit();
2640 const allocator = arena_allocator.allocator();
2641
2642 var parser = Parser.init(allocator, false);
2643 const tree_nocopy = try parser.parse(input);
2644 const obj_nocopy = tree_nocopy.root.Object;
2645
2646 parser = Parser.init(allocator, true);
2647 const tree_copy = try parser.parse(input);
2648 const obj_copy = tree_copy.root.Object;
2649
2650 for ([_][]const u8{ "noescape", "simple", "unicode", "surrogatepair" }) |field_name| {
2651 try testing.expectEqualSlices(u8, obj_nocopy.get(field_name).?.String, obj_copy.get(field_name).?.String);
2652 }
2653
2654 const nocopy_addr = &obj_nocopy.get("noescape").?.String[0];
2655 const copy_addr = &obj_copy.get("noescape").?.String[0];
2656
2657 var found_nocopy = false;
2658 for (input) |_, index| {
2659 try testing.expect(copy_addr != &input[index]);
2660 if (nocopy_addr == &input[index]) {
2661 found_nocopy = true;
2662 }
2663 }
2664 try testing.expect(found_nocopy);
2665}
2666
2667test "stringify alloc" {
2668 const allocator = std.testing.allocator;
2669 const expected =
2670 \\{"foo":"bar","answer":42,"my_friend":"sammy"}
2671 ;
2672 const actual = try stringifyAlloc(allocator, .{ .foo = "bar", .answer = 42, .my_friend = "sammy" }, .{});
2673 defer allocator.free(actual);
2674
2675 try std.testing.expectEqualStrings(expected, actual);
2676}
2677
2678test "json.serialize issue #5959" {
2679 var parser: StreamingParser = undefined;
2680 // StreamingParser has multiple internal fields set to undefined. This causes issues when using
2681 // expectEqual so these are zeroed. We are testing for equality here only because this is a
2682 // known small test reproduction which hits the relevant LLVM issue.
2683 std.mem.set(u8, @ptrCast([*]u8, &parser)[0..@sizeOf(StreamingParser)], 0);
2684 try std.testing.expectEqual(parser, parser);
2685}
2686
2687fn checkNext(p: *TokenStream, id: std.meta.Tag(Token)) !void {
2688 const token = (p.next() catch unreachable).?;
2689 try testing.expect(std.meta.activeTag(token) == id);
2690}
2691
2692test "json.token" {
2693 const s =
2694 \\{
2695 \\ "Image": {
2696 \\ "Width": 800,
2697 \\ "Height": 600,
2698 \\ "Title": "View from 15th Floor",
2699 \\ "Thumbnail": {
2700 \\ "Url": "http://www.example.com/image/481989943",
2701 \\ "Height": 125,
2702 \\ "Width": 100
2703 \\ },
2704 \\ "Animated" : false,
2705 \\ "IDs": [116, 943, 234, 38793]
2706 \\ }
2707 \\}
2708 ;
2709
2710 var p = TokenStream.init(s);
2711
2712 try checkNext(&p, .ObjectBegin);
2713 try checkNext(&p, .String); // Image
2714 try checkNext(&p, .ObjectBegin);
2715 try checkNext(&p, .String); // Width
2716 try checkNext(&p, .Number);
2717 try checkNext(&p, .String); // Height
2718 try checkNext(&p, .Number);
2719 try checkNext(&p, .String); // Title
2720 try checkNext(&p, .String);
2721 try checkNext(&p, .String); // Thumbnail
2722 try checkNext(&p, .ObjectBegin);
2723 try checkNext(&p, .String); // Url
2724 try checkNext(&p, .String);
2725 try checkNext(&p, .String); // Height
2726 try checkNext(&p, .Number);
2727 try checkNext(&p, .String); // Width
2728 try checkNext(&p, .Number);
2729 try checkNext(&p, .ObjectEnd);
2730 try checkNext(&p, .String); // Animated
2731 try checkNext(&p, .False);
2732 try checkNext(&p, .String); // IDs
2733 try checkNext(&p, .ArrayBegin);
2734 try checkNext(&p, .Number);
2735 try checkNext(&p, .Number);
2736 try checkNext(&p, .Number);
2737 try checkNext(&p, .Number);
2738 try checkNext(&p, .ArrayEnd);
2739 try checkNext(&p, .ObjectEnd);
2740 try checkNext(&p, .ObjectEnd);
2741
2742 try testing.expect((try p.next()) == null);
2743}
2744
2745test "json.token mismatched close" {
2746 var p = TokenStream.init("[102, 111, 111 }");
2747 try checkNext(&p, .ArrayBegin);
2748 try checkNext(&p, .Number);
2749 try checkNext(&p, .Number);
2750 try checkNext(&p, .Number);
2751 try testing.expectError(error.UnexpectedClosingBrace, p.next());
2752}
2753
2754test "json.token premature object close" {
2755 var p = TokenStream.init("{ \"key\": }");
2756 try checkNext(&p, .ObjectBegin);
2757 try checkNext(&p, .String);
2758 try testing.expectError(error.InvalidValueBegin, p.next());
2759}
2760
2761test "json.validate" {
2762 try testing.expectEqual(true, validate("{}"));
2763 try testing.expectEqual(true, validate("[]"));
2764 try testing.expectEqual(true, validate("[{[[[[{}]]]]}]"));
2765 try testing.expectEqual(false, validate("{]"));
2766 try testing.expectEqual(false, validate("[}"));
2767 try testing.expectEqual(false, validate("{{{{[]}}}]"));
2768}
2769
2770test "Value.jsonStringify" {
2771 {
2772 var buffer: [10]u8 = undefined;
2773 var fbs = std.io.fixedBufferStream(&buffer);
2774 try @as(Value, .Null).jsonStringify(.{}, fbs.writer());
2775 try testing.expectEqualSlices(u8, fbs.getWritten(), "null");
2776 }
2777 {
2778 var buffer: [10]u8 = undefined;
2779 var fbs = std.io.fixedBufferStream(&buffer);
2780 try (Value{ .Bool = true }).jsonStringify(.{}, fbs.writer());
2781 try testing.expectEqualSlices(u8, fbs.getWritten(), "true");
2782 }
2783 {
2784 var buffer: [10]u8 = undefined;
2785 var fbs = std.io.fixedBufferStream(&buffer);
2786 try (Value{ .Integer = 42 }).jsonStringify(.{}, fbs.writer());
2787 try testing.expectEqualSlices(u8, fbs.getWritten(), "42");
2788 }
2789 {
2790 var buffer: [10]u8 = undefined;
2791 var fbs = std.io.fixedBufferStream(&buffer);
2792 try (Value{ .NumberString = "43" }).jsonStringify(.{}, fbs.writer());
2793 try testing.expectEqualSlices(u8, fbs.getWritten(), "43");
2794 }
2795 {
2796 var buffer: [10]u8 = undefined;
2797 var fbs = std.io.fixedBufferStream(&buffer);
2798 try (Value{ .Float = 42 }).jsonStringify(.{}, fbs.writer());
2799 try testing.expectEqualSlices(u8, fbs.getWritten(), "4.2e+01");
2800 }
2801 {
2802 var buffer: [10]u8 = undefined;
2803 var fbs = std.io.fixedBufferStream(&buffer);
2804 try (Value{ .String = "weeee" }).jsonStringify(.{}, fbs.writer());
2805 try testing.expectEqualSlices(u8, fbs.getWritten(), "\"weeee\"");
2806 }
2807 {
2808 var buffer: [10]u8 = undefined;
2809 var fbs = std.io.fixedBufferStream(&buffer);
2810 var vals = [_]Value{
2811 .{ .Integer = 1 },
2812 .{ .Integer = 2 },
2813 .{ .NumberString = "3" },
2814 };
2815 try (Value{
2816 .Array = Array.fromOwnedSlice(undefined, &vals),
2817 }).jsonStringify(.{}, fbs.writer());
2818 try testing.expectEqualSlices(u8, fbs.getWritten(), "[1,2,3]");
2819 }
2820 {
2821 var buffer: [10]u8 = undefined;
2822 var fbs = std.io.fixedBufferStream(&buffer);
2823 var obj = ObjectMap.init(testing.allocator);
2824 defer obj.deinit();
2825 try obj.putNoClobber("a", .{ .String = "b" });
2826 try (Value{ .Object = obj }).jsonStringify(.{}, fbs.writer());
2827 try testing.expectEqualSlices(u8, fbs.getWritten(), "{\"a\":\"b\"}");
2828 }
2829}