From e91ae6e01828b9609b17b78c4b488dea3b6f1b50 Mon Sep 17 00:00:00 2001 From: Marsel Mavletkulov Date: Thu, 16 Apr 2026 20:43:05 -0400 Subject: [PATCH 1/3] Add json escaping --- src/any.zig | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/any.zig b/src/any.zig index d98dddd..1c35b71 100644 --- a/src/any.zig +++ b/src/any.zig @@ -36,14 +36,11 @@ pub const Value = union(enum) { } } - /// Formats the Value as JSON using a writer (unbounded output). - /// Strings are not escaped. + /// Formats the Value as JSON using a writer. pub fn format(self: Value, writer: anytype) !void { switch (self) { .string => |s| { - try writer.writeByte('"'); - try writer.writeAll(s); - try writer.writeByte('"'); + try std.json.Stringify.encodeJsonString(s, .{}, writer); }, .int32 => |v| try writer.print("{}", .{v}), .uint16, .uint32, .uint64 => |v| try writer.print("{}", .{v}), @@ -72,9 +69,7 @@ pub const Value = union(enum) { try writer.writeByte(','); } - try writer.writeByte('"'); - try writer.writeAll(entry.key); - try writer.writeByte('"'); + try std.json.Stringify.encodeJsonString(entry.key, .{}, writer); try writer.writeByte(':'); try entry.value.format(writer); } @@ -106,6 +101,18 @@ test "encode scalars" { .value = .{ .string = "" }, .want = "\"\"", }, + .{ + .value = .{ .string = "\"VOLZ\" LLC" }, + .want = "\"\\\"VOLZ\\\" LLC\"", + }, + .{ + .value = .{ .string = "back\\slash" }, + .want = "\"back\\\\slash\"", + }, + .{ + .value = .{ .string = "line\nnewline" }, + .want = "\"line\\nnewline\"", + }, .{ .value = .{ .int32 = 0 }, .want = "0", From baca00d15c8fb35af6229d6c22710761aab38bdf Mon Sep 17 00:00:00 2001 From: Marsel Mavletkulov Date: Thu, 16 Apr 2026 21:41:36 -0400 Subject: [PATCH 2/3] Check a string before escaping Escaping causes 40% regression, but adding a check before escaping a string lowers the regression to 24%. --- src/any.zig | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/any.zig b/src/any.zig index 1c35b71..561de35 100644 --- a/src/any.zig +++ b/src/any.zig @@ -36,11 +36,29 @@ pub const Value = union(enum) { } } + // Checks if a string contains bytes that must be escaped in JSON: + // control characters (0x00-0x1F), double quote, or backslash. + fn jsonStringNeedsEscape(s: []const u8) bool { + for (s) |c| { + if (c < 0x20 or c == '"' or c == '\\') { + return true; + } + } + + return false; + } + /// Formats the Value as JSON using a writer. pub fn format(self: Value, writer: anytype) !void { switch (self) { .string => |s| { - try std.json.Stringify.encodeJsonString(s, .{}, writer); + if (!jsonStringNeedsEscape(s)) { + try writer.writeByte('"'); + try writer.writeAll(s); + try writer.writeByte('"'); + } else { + try std.json.Stringify.encodeJsonString(s, .{}, writer); + } }, .int32 => |v| try writer.print("{}", .{v}), .uint16, .uint32, .uint64 => |v| try writer.print("{}", .{v}), @@ -69,7 +87,13 @@ pub const Value = union(enum) { try writer.writeByte(','); } - try std.json.Stringify.encodeJsonString(entry.key, .{}, writer); + if (!jsonStringNeedsEscape(entry.key)) { + try writer.writeByte('"'); + try writer.writeAll(entry.key); + try writer.writeByte('"'); + } else { + try std.json.Stringify.encodeJsonString(entry.key, .{}, writer); + } try writer.writeByte(':'); try entry.value.format(writer); } From 1afed30e68c8e117ec3e3cb20423028fab60fd96 Mon Sep 17 00:00:00 2001 From: Marsel Mavletkulov Date: Thu, 16 Apr 2026 22:04:07 -0400 Subject: [PATCH 3/3] Don't escape field names, only 14% regression --- src/any.zig | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/any.zig b/src/any.zig index 561de35..4184b97 100644 --- a/src/any.zig +++ b/src/any.zig @@ -87,13 +87,10 @@ pub const Value = union(enum) { try writer.writeByte(','); } - if (!jsonStringNeedsEscape(entry.key)) { - try writer.writeByte('"'); - try writer.writeAll(entry.key); - try writer.writeByte('"'); - } else { - try std.json.Stringify.encodeJsonString(entry.key, .{}, writer); - } + // No need to escape field names, e.g., "city", "names", + try writer.writeByte('"'); + try writer.writeAll(entry.key); + try writer.writeByte('"'); try writer.writeByte(':'); try entry.value.format(writer); }