authorgravatar for benjamin.feng@glassdoor.comBenjamin Feng <benjamin.feng@glassdoor.com> 2020-02-29 18:59:16-06:00
committergravatar for benjamin.feng@glassdoor.comBenjamin Feng <benjamin.feng@glassdoor.com> 2020-03-12 10:41:09-05:00
log2429fdd73bc2405034805893f169ae58c1c34c56
tree09df1497ae2da819d83e14f45b90725b00f5b79b
parent0fbccec000efcfe23188027e474530641daf67cd

Convert JSON to fmtstream


1 files changed, 72 insertions(+), 62 deletions(-)

lib/std/json.zig+72-62
...@@ -2252,45 +2252,43 @@ pub const StringifyOptions = struct {...@@ -2252,45 +2252,43 @@ pub const StringifyOptions = struct {
2252pub fn stringify(2252pub fn stringify(
2253 value: var,2253 value: var,
2254 options: StringifyOptions,2254 options: StringifyOptions,
2255 context: var,2255 out_stream: var,
2256 comptime Errors: type,2256) !void {
2257 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
2258) Errors!void {
2259 const T = @TypeOf(value);2257 const T = @TypeOf(value);
2260 switch (@typeInfo(T)) {2258 switch (@typeInfo(T)) {
2261 .Float, .ComptimeFloat => {2259 .Float, .ComptimeFloat => {
2262 return std.fmt.formatFloatScientific(value, std.fmt.FormatOptions{}, context, Errors, output);2260 return std.fmtstream.formatFloatScientific(value, std.fmtstream.FormatOptions{}, out_stream);
2263 },2261 },
2264 .Int, .ComptimeInt => {2262 .Int, .ComptimeInt => {
2265 return std.fmt.formatIntValue(value, "", std.fmt.FormatOptions{}, context, Errors, output);2263 return std.fmtstream.formatIntValue(value, "", std.fmtstream.FormatOptions{}, out_stream);
2266 },2264 },
2267 .Bool => {2265 .Bool => {
2268 return output(context, if (value) "true" else "false");2266 return out_stream.writeAll(if (value) "true" else "false");
2269 },2267 },
2270 .Optional => {2268 .Optional => {
2271 if (value) |payload| {2269 if (value) |payload| {
2272 return try stringify(payload, options, context, Errors, output);2270 return try stringify(payload, options, out_stream);
2273 } else {2271 } else {
2274 return output(context, "null");2272 return out_stream.writeAll("null");
2275 }2273 }
2276 },2274 },
2277 .Enum => {2275 .Enum => {
2278 if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {2276 if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
2279 return value.jsonStringify(options, context, Errors, output);2277 return value.jsonStringify(options, out_stream);
2280 }2278 }
22812279
2282 @compileError("Unable to stringify enum '" ++ @typeName(T) ++ "'");2280 @compileError("Unable to stringify enum '" ++ @typeName(T) ++ "'");
2283 },2281 },
2284 .Union => {2282 .Union => {
2285 if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {2283 if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
2286 return value.jsonStringify(options, context, Errors, output);2284 return value.jsonStringify(options, out_stream);
2287 }2285 }
22882286
2289 const info = @typeInfo(T).Union;2287 const info = @typeInfo(T).Union;
2290 if (info.tag_type) |UnionTagType| {2288 if (info.tag_type) |UnionTagType| {
2291 inline for (info.fields) |u_field| {2289 inline for (info.fields) |u_field| {
2292 if (@enumToInt(@as(UnionTagType, value)) == u_field.enum_field.?.value) {2290 if (@enumToInt(@as(UnionTagType, value)) == u_field.enum_field.?.value) {
2293 return try stringify(@field(value, u_field.name), options, context, Errors, output);2291 return try stringify(@field(value, u_field.name), options, out_stream);
2294 }2292 }
2295 }2293 }
2296 } else {2294 } else {
...@@ -2299,10 +2297,10 @@ pub fn stringify(...@@ -2299,10 +2297,10 @@ pub fn stringify(
2299 },2297 },
2300 .Struct => |S| {2298 .Struct => |S| {
2301 if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {2299 if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
2302 return value.jsonStringify(options, context, Errors, output);2300 return value.jsonStringify(options, out_stream);
2303 }2301 }
23042302
2305 try output(context, "{");2303 try out_stream.writeAll("{");
2306 comptime var field_output = false;2304 comptime var field_output = false;
2307 inline for (S.fields) |Field, field_i| {2305 inline for (S.fields) |Field, field_i| {
2308 // don't include void fields2306 // don't include void fields
...@@ -2311,39 +2309,39 @@ pub fn stringify(...@@ -2311,39 +2309,39 @@ pub fn stringify(
2311 if (!field_output) {2309 if (!field_output) {
2312 field_output = true;2310 field_output = true;
2313 } else {2311 } else {
2314 try output(context, ",");2312 try out_stream.writeAll(",");
2315 }2313 }
23162314
2317 try stringify(Field.name, options, context, Errors, output);2315 try stringify(Field.name, options, out_stream);
2318 try output(context, ":");2316 try out_stream.writeAll(":");
2319 try stringify(@field(value, Field.name), options, context, Errors, output);2317 try stringify(@field(value, Field.name), options, out_stream);
2320 }2318 }
2321 try output(context, "}");2319 try out_stream.writeAll("}");
2322 return;2320 return;
2323 },2321 },
2324 .Pointer => |ptr_info| switch (ptr_info.size) {2322 .Pointer => |ptr_info| switch (ptr_info.size) {
2325 .One => {2323 .One => {
2326 // TODO: avoid loops?2324 // TODO: avoid loops?
2327 return try stringify(value.*, options, context, Errors, output);2325 return try stringify(value.*, options, out_stream);
2328 },2326 },
2329 // TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972)2327 // TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972)
2330 .Slice => {2328 .Slice => {
2331 if (ptr_info.child == u8 and std.unicode.utf8ValidateSlice(value)) {2329 if (ptr_info.child == u8 and std.unicode.utf8ValidateSlice(value)) {
2332 try output(context, "\"");2330 try out_stream.writeAll("\"");
2333 var i: usize = 0;2331 var i: usize = 0;
2334 while (i < value.len) : (i += 1) {2332 while (i < value.len) : (i += 1) {
2335 switch (value[i]) {2333 switch (value[i]) {
2336 // normal ascii characters2334 // normal ascii characters
2337 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => try output(context, value[i .. i + 1]),2335 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => try out_stream.writeAll(value[i .. i + 1]),
2338 // control characters with short escapes2336 // control characters with short escapes
2339 '\\' => try output(context, "\\\\"),2337 '\\' => try out_stream.writeAll("\\\\"),
2340 '\"' => try output(context, "\\\""),2338 '\"' => try out_stream.writeAll("\\\""),
2341 '/' => try output(context, "\\/"),2339 '/' => try out_stream.writeAll("\\/"),
2342 0x8 => try output(context, "\\b"),2340 0x8 => try out_stream.writeAll("\\b"),
2343 0xC => try output(context, "\\f"),2341 0xC => try out_stream.writeAll("\\f"),
2344 '\n' => try output(context, "\\n"),2342 '\n' => try out_stream.writeAll("\\n"),
2345 '\r' => try output(context, "\\r"),2343 '\r' => try out_stream.writeAll("\\r"),
2346 '\t' => try output(context, "\\t"),2344 '\t' => try out_stream.writeAll("\\t"),
2347 else => {2345 else => {
2348 const ulen = std.unicode.utf8ByteSequenceLength(value[i]) catch unreachable;2346 const ulen = std.unicode.utf8ByteSequenceLength(value[i]) catch unreachable;
2349 const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable;2347 const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable;
...@@ -2351,40 +2349,40 @@ pub fn stringify(...@@ -2351,40 +2349,40 @@ pub fn stringify(
2351 // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF),2349 // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF),
2352 // then it may be represented as a six-character sequence: a reverse solidus, followed2350 // then it may be represented as a six-character sequence: a reverse solidus, followed
2353 // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point.2351 // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point.
2354 try output(context, "\\u");2352 try out_stream.writeAll("\\u");
2355 try std.fmt.formatIntValue(codepoint, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, context, Errors, output);2353 try std.fmtstream.formatIntValue(codepoint, "x", std.fmtstream.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
2356 } else {2354 } else {
2357 // To escape an extended character that is not in the Basic Multilingual Plane,2355 // To escape an extended character that is not in the Basic Multilingual Plane,
2358 // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair.2356 // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair.
2359 const high = @intCast(u16, (codepoint - 0x10000) >> 10) + 0xD800;2357 const high = @intCast(u16, (codepoint - 0x10000) >> 10) + 0xD800;
2360 const low = @intCast(u16, codepoint & 0x3FF) + 0xDC00;2358 const low = @intCast(u16, codepoint & 0x3FF) + 0xDC00;
2361 try output(context, "\\u");2359 try out_stream.writeAll("\\u");
2362 try std.fmt.formatIntValue(high, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, context, Errors, output);2360 try std.fmtstream.formatIntValue(high, "x", std.fmtstream.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
2363 try output(context, "\\u");2361 try out_stream.writeAll("\\u");
2364 try std.fmt.formatIntValue(low, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, context, Errors, output);2362 try std.fmtstream.formatIntValue(low, "x", std.fmtstream.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
2365 }2363 }
2366 i += ulen - 1;2364 i += ulen - 1;
2367 },2365 },
2368 }2366 }
2369 }2367 }
2370 try output(context, "\"");2368 try out_stream.writeAll("\"");
2371 return;2369 return;
2372 }2370 }
23732371
2374 try output(context, "[");2372 try out_stream.writeAll("[");
2375 for (value) |x, i| {2373 for (value) |x, i| {
2376 if (i != 0) {2374 if (i != 0) {
2377 try output(context, ",");2375 try out_stream.writeAll(",");
2378 }2376 }
2379 try stringify(x, options, context, Errors, output);2377 try stringify(x, options, out_stream);
2380 }2378 }
2381 try output(context, "]");2379 try out_stream.writeAll("]");
2382 return;2380 return;
2383 },2381 },
2384 else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),2382 else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
2385 },2383 },
2386 .Array => |info| {2384 .Array => |info| {
2387 return try stringify(value[0..], options, context, Errors, output);2385 return try stringify(value[0..], options, out_stream);
2388 },2386 },
2389 else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),2387 else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
2390 }2388 }
...@@ -2392,10 +2390,26 @@ pub fn stringify(...@@ -2392,10 +2390,26 @@ pub fn stringify(
2392}2390}
23932391
2394fn teststringify(expected: []const u8, value: var) !void {2392fn teststringify(expected: []const u8, value: var) !void {
2395 const TestStringifyContext = struct {2393 const ValidationOutStream = struct {
2394 const Self = @This();
2395 pub const OutStream = std.io.OutStream(*Self, Error, write);
2396 pub const Error = error{
2397 TooMuchData,
2398 DifferentData,
2399 };
2400
2396 expected_remaining: []const u8,2401 expected_remaining: []const u8,
2397 fn testStringifyWrite(context: *@This(), bytes: []const u8) !void {2402
2398 if (context.expected_remaining.len < bytes.len) {2403 fn init(exp: []const u8) Self {
2404 return .{ .expected_remaining = exp };
2405 }
2406
2407 pub fn outStream(self: *Self) OutStream {
2408 return .{ .context = self };
2409 }
2410
2411 fn write(self: *Self, bytes: []const u8) Error!usize {
2412 if (self.expected_remaining.len < bytes.len) {
2399 std.debug.warn(2413 std.debug.warn(
2400 \\====== expected this output: =========2414 \\====== expected this output: =========
2401 \\{}2415 \\{}
...@@ -2403,12 +2417,12 @@ fn teststringify(expected: []const u8, value: var) !void {...@@ -2403,12 +2417,12 @@ fn teststringify(expected: []const u8, value: var) !void {
2403 \\{}2417 \\{}
2404 \\======================================2418 \\======================================
2405 , .{2419 , .{
2406 context.expected_remaining,2420 self.expected_remaining,
2407 bytes,2421 bytes,
2408 });2422 });
2409 return error.TooMuchData;2423 return error.TooMuchData;
2410 }2424 }
2411 if (!mem.eql(u8, context.expected_remaining[0..bytes.len], bytes)) {2425 if (!mem.eql(u8, self.expected_remaining[0..bytes.len], bytes)) {
2412 std.debug.warn(2426 std.debug.warn(
2413 \\====== expected this output: =========2427 \\====== expected this output: =========
2414 \\{}2428 \\{}
...@@ -2416,21 +2430,19 @@ fn teststringify(expected: []const u8, value: var) !void {...@@ -2416,21 +2430,19 @@ fn teststringify(expected: []const u8, value: var) !void {
2416 \\{}2430 \\{}
2417 \\======================================2431 \\======================================
2418 , .{2432 , .{
2419 context.expected_remaining[0..bytes.len],2433 self.expected_remaining[0..bytes.len],
2420 bytes,2434 bytes,
2421 });2435 });
2422 return error.DifferentData;2436 return error.DifferentData;
2423 }2437 }
2424 context.expected_remaining = context.expected_remaining[bytes.len..];2438 self.expected_remaining = self.expected_remaining[bytes.len..];
2439 return bytes.len;
2425 }2440 }
2426 };2441 };
2427 var buf: [100]u8 = undefined;2442
2428 var context = TestStringifyContext{ .expected_remaining = expected };2443 var vos = ValidationOutStream.init(expected);
2429 try stringify(value, StringifyOptions{}, &context, error{2444 try stringify(value, StringifyOptions{}, vos.outStream());
2430 TooMuchData,2445 if (vos.expected_remaining.len > 0) return error.NotEnoughData;
2431 DifferentData,
2432 }, TestStringifyContext.testStringifyWrite);
2433 if (context.expected_remaining.len > 0) return error.NotEnoughData;
2434}2446}
24352447
2436test "stringify basic types" {2448test "stringify basic types" {
...@@ -2498,13 +2510,11 @@ test "stringify struct with custom stringifier" {...@@ -2498,13 +2510,11 @@ test "stringify struct with custom stringifier" {
2498 pub fn jsonStringify(2510 pub fn jsonStringify(
2499 value: Self,2511 value: Self,
2500 options: StringifyOptions,2512 options: StringifyOptions,
2501 context: var,2513 out_stream: var,
2502 comptime Errors: type,
2503 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
2504 ) !void {2514 ) !void {
2505 try output(context, "[\"something special\",");2515 try out_stream.writeAll("[\"something special\",");
2506 try stringify(42, options, context, Errors, output);2516 try stringify(42, options, out_stream);
2507 try output(context, "]");2517 try out_stream.writeAll("]");
2508 }2518 }
2509 }{ .foo = 42 });2519 }{ .foo = 42 });
2510}2520}