authorgravatar for johnnymarler@gmail.comJonathan Marler <johnnymarler@gmail.com> 2022-06-30 15:02:09-06:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-07-24 11:51:59+03:00
logf598234ee820ca0d57946542a852f193b21d847f
tree5ab1dda8719c06cd4b52e2b7ce7c8f0c4b8f417a
parenta2ab9e36faded9755ecc1fe809c49140120c3c61

std.json: expose encodeJsonString and encodeJsonStringChars

Expose 2 functions from std.json. These functions take a slice of bytes and forward them to a given writer as a JSON encoded string. The use case I have for this is in a custom JsonStringWriter. This writer takes data and automatically encodes it as JSON string characters and forwards it to an underlying writer. I use this JsonStringWriter in combination with std.fmt.format to go directly from a format string/arg pair to JSON. This way I don't have to format my string into a separate buffer first and encode it afterwards, which avoids the need to create a temporary buffer to hold the unencoded but formatted string.

1 files changed, 27 insertions(+), 21 deletions(-)

lib/std/json.zig+27-21
...@@ -2163,45 +2163,51 @@ fn outputUnicodeEscape(...@@ -2163,45 +2163,51 @@ fn outputUnicodeEscape(
2163 }2163 }
2164}2164}
21652165
2166fn outputJsonString(value: []const u8, options: StringifyOptions, out_stream: anytype) !void {2166/// Write `string` to `writer` as a JSON encoded string.
2167 try out_stream.writeByte('\"');2167pub fn encodeJsonString(string: []const u8, options: StringifyOptions, writer: anytype) !void {
2168 try writer.writeByte('\"');
2169 try encodeJsonStringChars(string, options, writer);
2170 try writer.writeByte('\"');
2171}
2172
2173/// Write `chars` to `writer` as JSON encoded string characters.
2174pub fn encodeJsonStringChars(chars: []const u8, options: StringifyOptions, writer: anytype) !void {
2168 var i: usize = 0;2175 var i: usize = 0;
2169 while (i < value.len) : (i += 1) {2176 while (i < chars.len) : (i += 1) {
2170 switch (value[i]) {2177 switch (chars[i]) {
2171 // normal ascii character2178 // normal ascii character
2172 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => |c| try out_stream.writeByte(c),2179 0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => |c| try writer.writeByte(c),
2173 // only 2 characters that *must* be escaped2180 // only 2 characters that *must* be escaped
2174 '\\' => try out_stream.writeAll("\\\\"),2181 '\\' => try writer.writeAll("\\\\"),
2175 '\"' => try out_stream.writeAll("\\\""),2182 '\"' => try writer.writeAll("\\\""),
2176 // solidus is optional to escape2183 // solidus is optional to escape
2177 '/' => {2184 '/' => {
2178 if (options.string.String.escape_solidus) {2185 if (options.string.String.escape_solidus) {
2179 try out_stream.writeAll("\\/");2186 try writer.writeAll("\\/");
2180 } else {2187 } else {
2181 try out_stream.writeByte('/');2188 try writer.writeByte('/');
2182 }2189 }
2183 },2190 },
2184 // control characters with short escapes2191 // control characters with short escapes
2185 // TODO: option to switch between unicode and 'short' forms?2192 // TODO: option to switch between unicode and 'short' forms?
2186 0x8 => try out_stream.writeAll("\\b"),2193 0x8 => try writer.writeAll("\\b"),
2187 0xC => try out_stream.writeAll("\\f"),2194 0xC => try writer.writeAll("\\f"),
2188 '\n' => try out_stream.writeAll("\\n"),2195 '\n' => try writer.writeAll("\\n"),
2189 '\r' => try out_stream.writeAll("\\r"),2196 '\r' => try writer.writeAll("\\r"),
2190 '\t' => try out_stream.writeAll("\\t"),2197 '\t' => try writer.writeAll("\\t"),
2191 else => {2198 else => {
2192 const ulen = std.unicode.utf8ByteSequenceLength(value[i]) catch unreachable;2199 const ulen = std.unicode.utf8ByteSequenceLength(chars[i]) catch unreachable;
2193 // control characters (only things left with 1 byte length) should always be printed as unicode escapes2200 // control characters (only things left with 1 byte length) should always be printed as unicode escapes
2194 if (ulen == 1 or options.string.String.escape_unicode) {2201 if (ulen == 1 or options.string.String.escape_unicode) {
2195 const codepoint = std.unicode.utf8Decode(value[i .. i + ulen]) catch unreachable;2202 const codepoint = std.unicode.utf8Decode(chars[i .. i + ulen]) catch unreachable;
2196 try outputUnicodeEscape(codepoint, out_stream);2203 try outputUnicodeEscape(codepoint, writer);
2197 } else {2204 } else {
2198 try out_stream.writeAll(value[i .. i + ulen]);2205 try writer.writeAll(chars[i .. i + ulen]);
2199 }2206 }
2200 i += ulen - 1;2207 i += ulen - 1;
2201 },2208 },
2202 }2209 }
2203 }2210 }
2204 try out_stream.writeByte('\"');
2205}2211}
22062212
2207pub fn stringify(2213pub fn stringify(
...@@ -2288,7 +2294,7 @@ pub fn stringify(...@@ -2288,7 +2294,7 @@ pub fn stringify(
2288 if (child_options.whitespace) |child_whitespace| {2294 if (child_options.whitespace) |child_whitespace| {
2289 try child_whitespace.outputIndent(out_stream);2295 try child_whitespace.outputIndent(out_stream);
2290 }2296 }
2291 try outputJsonString(Field.name, options, out_stream);2297 try encodeJsonString(Field.name, options, out_stream);
2292 try out_stream.writeByte(':');2298 try out_stream.writeByte(':');
2293 if (child_options.whitespace) |child_whitespace| {2299 if (child_options.whitespace) |child_whitespace| {
2294 if (child_whitespace.separator) {2300 if (child_whitespace.separator) {
...@@ -2321,7 +2327,7 @@ pub fn stringify(...@@ -2321,7 +2327,7 @@ pub fn stringify(
2321 // 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)
2322 .Slice => {2328 .Slice => {
2323 if (ptr_info.child == u8 and options.string == .String and std.unicode.utf8ValidateSlice(value)) {2329 if (ptr_info.child == u8 and options.string == .String and std.unicode.utf8ValidateSlice(value)) {
2324 try outputJsonString(value, options, out_stream);2330 try encodeJsonString(value, options, out_stream);
2325 return;2331 return;
2326 }2332 }
23272333