|
const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; const BitStack = std.BitStack; const OBJECT_MODE = 0; const ARRAY_MODE = 1; |
StringifyOptions |
pub const StringifyOptions = struct { whitespace: enum { minified, indent_1, indent_2, indent_3, indent_4, indent_8, indent_tab, } = .minified, emit_null_optional_fields: bool = true, emit_strings_as_arrays: bool = false, escape_unicode: bool = false, emit_nonportable_numbers_as_strings: bool = false, }; |
stringify() Controls the whitespace emitted. The default |
pub fn stringify( value: anytype, options: StringifyOptions, out_stream: anytype, ) @TypeOf(out_stream).Error!void { var jw = writeStream(out_stream, options); defer jw.deinit(); try jw.write(value); } |
stringifyMaxDepth() Like |
pub fn stringifyMaxDepth( value: anytype, options: StringifyOptions, out_stream: anytype, comptime max_depth: ?usize, ) @TypeOf(out_stream).Error!void { var jw = writeStreamMaxDepth(out_stream, options, max_depth); try jw.write(value); } |
stringifyArbitraryDepth() Like |
pub fn stringifyArbitraryDepth( allocator: Allocator, value: anytype, options: StringifyOptions, out_stream: anytype, ) WriteStream(@TypeOf(out_stream), .checked_to_arbitrary_depth).Error!void { var jw = writeStreamArbitraryDepth(allocator, out_stream, options); defer jw.deinit(); try jw.write(value); } |
stringifyAlloc() Calls |
pub fn stringifyAlloc( allocator: Allocator, value: anytype, options: StringifyOptions, ) error{OutOfMemory}![]const u8 { var list = std.ArrayList(u8).init(allocator); errdefer list.deinit(); try stringifyArbitraryDepth(allocator, value, options, list.writer()); return list.toOwnedSlice(); } |
writeStream() See |
pub fn writeStream( out_stream: anytype, options: StringifyOptions, ) WriteStream(@TypeOf(out_stream), .{ .checked_to_fixed_depth = 256 }) { return writeStreamMaxDepth(out_stream, options, 256); } |
writeStreamMaxDepth() See |
pub fn writeStreamMaxDepth( out_stream: anytype, options: StringifyOptions, comptime max_depth: ?usize, ) WriteStream( @TypeOf(out_stream), if (max_depth) |d| .{ .checked_to_fixed_depth = d } else .assumed_correct, ) { return WriteStream( @TypeOf(out_stream), if (max_depth) |d| .{ .checked_to_fixed_depth = d } else .assumed_correct, ).init(undefined, out_stream, options); } |
writeStreamArbitraryDepth() See |
pub fn writeStreamArbitraryDepth( allocator: Allocator, out_stream: anytype, options: StringifyOptions, ) WriteStream(@TypeOf(out_stream), .checked_to_arbitrary_depth) { return WriteStream(@TypeOf(out_stream), .checked_to_arbitrary_depth).init(allocator, out_stream, options); } |
WriteStream() Writes JSON ([RFC8259](https://tools.ietf.org/html/rfc8259)) formatted data to a stream. |
pub fn WriteStream( comptime OutStream: type, comptime safety_checks_hint: union(enum) { checked_to_arbitrary_depth, checked_to_fixed_depth: usize, // Rounded up to the nearest multiple of 8. assumed_correct, }, ) type { return struct { const Self = @This(); const safety_checks: @TypeOf(safety_checks_hint) = switch (@import("builtin").mode) { .Debug, .ReleaseSafe => safety_checks_hint, .ReleaseFast, .ReleaseSmall => .assumed_correct, }; pub const Stream = OutStream; pub const Error = switch (safety_checks) { .checked_to_arbitrary_depth => Stream.Error || error{OutOfMemory}, .checked_to_fixed_depth, .assumed_correct => Stream.Error, }; options: StringifyOptions, stream: OutStream, indent_level: usize = 0, next_punctuation: enum { the_beginning, none, comma, colon, } = .the_beginning, nesting_stack: switch (safety_checks) { .checked_to_arbitrary_depth => BitStack, .checked_to_fixed_depth => |fixed_buffer_size| [(fixed_buffer_size + 7) >> 3]u8, .assumed_correct => void, }, |
init() |
pub fn init(safety_allocator: Allocator, stream: OutStream, options: StringifyOptions) Self { return .{ .options = options, .stream = stream, .nesting_stack = switch (safety_checks) { .checked_to_arbitrary_depth => BitStack.init(safety_allocator), .checked_to_fixed_depth => |fixed_buffer_size| [_]u8{0} ** ((fixed_buffer_size + 7) >> 3), .assumed_correct => {}, }, }; } |
deinit() |
pub fn deinit(self: *Self) void { switch (safety_checks) { .checked_to_arbitrary_depth => self.nesting_stack.deinit(), .checked_to_fixed_depth, .assumed_correct => {}, } self.* = undefined; } |
beginArray() |
pub fn beginArray(self: *Self) Error!void { try self.valueStart(); try self.stream.writeByte('['); try self.pushIndentation(ARRAY_MODE); self.next_punctuation = .none; } |
beginObject() |
pub fn beginObject(self: *Self) Error!void { try self.valueStart(); try self.stream.writeByte('{'); try self.pushIndentation(OBJECT_MODE); self.next_punctuation = .none; } |
endArray() |
pub fn endArray(self: *Self) Error!void { self.popIndentation(ARRAY_MODE); switch (self.next_punctuation) { .none => {}, .comma => { try self.indent(); }, .the_beginning, .colon => unreachable, } try self.stream.writeByte(']'); self.valueDone(); } |
endObject() |
pub fn endObject(self: *Self) Error!void { self.popIndentation(OBJECT_MODE); switch (self.next_punctuation) { .none => {}, .comma => { try self.indent(); }, .the_beginning, .colon => unreachable, } try self.stream.writeByte('}'); self.valueDone(); } fn pushIndentation(self: *Self, mode: u1) !void { switch (safety_checks) { .checked_to_arbitrary_depth => { try self.nesting_stack.push(mode); self.indent_level += 1; }, .checked_to_fixed_depth => { BitStack.pushWithStateAssumeCapacity(&self.nesting_stack, &self.indent_level, mode); }, .assumed_correct => { self.indent_level += 1; }, } } fn popIndentation(self: *Self, assert_its_this_one: u1) void { switch (safety_checks) { .checked_to_arbitrary_depth => { assert(self.nesting_stack.pop() == assert_its_this_one); self.indent_level -= 1; }, .checked_to_fixed_depth => { assert(BitStack.popWithState(&self.nesting_stack, &self.indent_level) == assert_its_this_one); }, .assumed_correct => { self.indent_level -= 1; }, } } fn indent(self: *Self) !void { var char: u8 = ' '; const n_chars = switch (self.options.whitespace) { .minified => return, .indent_1 => 1 * self.indent_level, .indent_2 => 2 * self.indent_level, .indent_3 => 3 * self.indent_level, .indent_4 => 4 * self.indent_level, .indent_8 => 8 * self.indent_level, .indent_tab => blk: { char = '\t'; break :blk self.indent_level; }, }; try self.stream.writeByte('\n'); try self.stream.writeByteNTimes(char, n_chars); } fn valueStart(self: *Self) !void { if (self.isObjectKeyExpected()) |is_it| assert(!is_it); // Call objectField(), not write(), for object keys. return self.valueStartAssumeTypeOk(); } fn objectFieldStart(self: *Self) !void { if (self.isObjectKeyExpected()) |is_it| assert(is_it); // Expected write(), not objectField(). return self.valueStartAssumeTypeOk(); } fn valueStartAssumeTypeOk(self: *Self) !void { assert(!self.isComplete()); // JSON document already complete. switch (self.next_punctuation) { .the_beginning => { // No indentation for the very beginning. }, .none => { // First item in a container. try self.indent(); }, .comma => { // Subsequent item in a container. try self.stream.writeByte(','); try self.indent(); }, .colon => { try self.stream.writeByte(':'); if (self.options.whitespace != .minified) { try self.stream.writeByte(' '); } }, } } fn valueDone(self: *Self) void { self.next_punctuation = .comma; } // Only when safety is enabled: fn isObjectKeyExpected(self: *const Self) ?bool { switch (safety_checks) { .checked_to_arbitrary_depth => return self.indent_level > 0 and self.nesting_stack.peek() == OBJECT_MODE and self.next_punctuation != .colon, .checked_to_fixed_depth => return self.indent_level > 0 and BitStack.peekWithState(&self.nesting_stack, self.indent_level) == OBJECT_MODE and self.next_punctuation != .colon, .assumed_correct => return null, } } fn isComplete(self: *const Self) bool { return self.indent_level == 0 and self.next_punctuation == .comma; } |
print() An alternative to calling |
pub fn print(self: *Self, comptime fmt: []const u8, args: anytype) Error!void { try self.valueStart(); try self.stream.print(fmt, args); self.valueDone(); } |
objectField() |
pub fn objectField(self: *Self, key: []const u8) Error!void { try self.objectFieldStart(); try encodeJsonString(key, self.options, self.stream); self.next_punctuation = .colon; } |
write() See |
pub fn write(self: *Self, value: anytype) Error!void { const T = @TypeOf(value); switch (@typeInfo(T)) { .Int => { try self.valueStart(); if (self.options.emit_nonportable_numbers_as_strings and (value <= -(1 << 53) or value >= (1 << 53))) { try self.stream.print("\"{}\"", .{value}); } else { try self.stream.print("{}", .{value}); } self.valueDone(); return; }, .ComptimeInt => { return self.write(@as(std.math.IntFittingRange(value, value), value)); }, .Float, .ComptimeFloat => { if (@as(f64, @floatCast(value)) == value) { try self.valueStart(); try self.stream.print("{}", .{@as(f64, @floatCast(value))}); self.valueDone(); return; } try self.valueStart(); try self.stream.print("\"{}\"", .{value}); self.valueDone(); return; }, .Bool => { try self.valueStart(); try self.stream.writeAll(if (value) "true" else "false"); self.valueDone(); return; }, .Null => { try self.valueStart(); try self.stream.writeAll("null"); self.valueDone(); return; }, .Optional => { if (value) |payload| { return try self.write(payload); } else { return try self.write(null); } }, .Enum, .EnumLiteral => { if (comptime std.meta.trait.hasFn("jsonStringify")(T)) { return value.jsonStringify(self); } return self.stringValue(@tagName(value)); }, .Union => { if (comptime std.meta.trait.hasFn("jsonStringify")(T)) { return value.jsonStringify(self); } const info = @typeInfo(T).Union; if (info.tag_type) |UnionTagType| { try self.beginObject(); inline for (info.fields) |u_field| { if (value == @field(UnionTagType, u_field.name)) { try self.objectField(u_field.name); if (u_field.type == void) { // void value is {} try self.beginObject(); try self.endObject(); } else { try self.write(@field(value, u_field.name)); } break; } } else { unreachable; // No active tag? } try self.endObject(); return; } else { @compileError("Unable to stringify untagged union '" ++ @typeName(T) ++ "'"); } }, .Struct => |S| { if (comptime std.meta.trait.hasFn("jsonStringify")(T)) { return value.jsonStringify(self); } if (S.is_tuple) { try self.beginArray(); } else { try self.beginObject(); } inline for (S.fields) |Field| { // don't include void fields if (Field.type == void) continue; var emit_field = true; // don't include optional fields that are null when emit_null_optional_fields is set to false if (@typeInfo(Field.type) == .Optional) { if (self.options.emit_null_optional_fields == false) { if (@field(value, Field.name) == null) { emit_field = false; } } } if (emit_field) { if (!S.is_tuple) { try self.objectField(Field.name); } try self.write(@field(value, Field.name)); } } if (S.is_tuple) { try self.endArray(); } else { try self.endObject(); } return; }, .ErrorSet => return self.stringValue(@errorName(value)), .Pointer => |ptr_info| switch (ptr_info.size) { .One => switch (@typeInfo(ptr_info.child)) { .Array => { // Coerce `*[N]T` to `[]const T`. const Slice = []const std.meta.Elem(ptr_info.child); return self.write(@as(Slice, value)); }, else => { return self.write(value.*); }, }, .Many, .Slice => { if (ptr_info.size == .Many and ptr_info.sentinel == null) @compileError("unable to stringify type '" ++ @typeName(T) ++ "' without sentinel"); const slice = if (ptr_info.size == .Many) std.mem.span(value) else value; if (ptr_info.child == u8) { // This is a []const u8, or some similar Zig string. if (!self.options.emit_strings_as_arrays and std.unicode.utf8ValidateSlice(slice)) { return self.stringValue(slice); } } try self.beginArray(); for (slice) |x| { try self.write(x); } try self.endArray(); return; }, else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), }, .Array => { // Coerce `[N]T` to `*const [N]T` (and then to `[]const T`). return self.write(&value); }, .Vector => |info| { const array: [info.len]info.child = value; return self.write(&array); }, else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), } unreachable; } fn stringValue(self: *Self, s: []const u8) !void { try self.valueStart(); try encodeJsonString(s, self.options, self.stream); self.valueDone(); } pub const arrayElem = @compileError("Deprecated; You don't need to call this anymore."); pub const emitNull = @compileError("Deprecated; Use .write(null) instead."); pub const emitBool = @compileError("Deprecated; Use .write() instead."); pub const emitNumber = @compileError("Deprecated; Use .write() instead."); pub const emitString = @compileError("Deprecated; Use .write() instead."); pub const emitJson = @compileError("Deprecated; Use .write() instead."); pub const writePreformatted = @compileError("Deprecated; Use .print(\"{s}\", .{s}) instead."); }; } fn outputUnicodeEscape(codepoint: u21, out_stream: anytype) !void { if (codepoint <= 0xFFFF) { // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF), // then it may be represented as a six-character sequence: a reverse solidus, followed // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point. try out_stream.writeAll("\\u"); try std.fmt.formatIntValue(codepoint, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); } else { assert(codepoint <= 0x10FFFF); // To escape an extended character that is not in the Basic Multilingual Plane, // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair. const high = @as(u16, @intCast((codepoint - 0x10000) >> 10)) + 0xD800; const low = @as(u16, @intCast(codepoint & 0x3FF)) + 0xDC00; try out_stream.writeAll("\\u"); try std.fmt.formatIntValue(high, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); try out_stream.writeAll("\\u"); try std.fmt.formatIntValue(low, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream); } } fn outputSpecialEscape(c: u8, writer: anytype) !void { switch (c) { '\\' => try writer.writeAll("\\\\"), '\"' => try writer.writeAll("\\\""), 0x08 => try writer.writeAll("\\b"), 0x0C => try writer.writeAll("\\f"), '\n' => try writer.writeAll("\\n"), '\r' => try writer.writeAll("\\r"), '\t' => try writer.writeAll("\\t"), else => try outputUnicodeEscape(c, writer), } } |
encodeJsonString() Write |
pub fn encodeJsonString(string: []const u8, options: StringifyOptions, writer: anytype) !void { try writer.writeByte('\"'); try encodeJsonStringChars(string, options, writer); try writer.writeByte('\"'); } |
encodeJsonStringChars() Write |
pub fn encodeJsonStringChars(chars: []const u8, options: StringifyOptions, writer: anytype) !void { var write_cursor: usize = 0; var i: usize = 0; if (options.escape_unicode) { while (i < chars.len) : (i += 1) { switch (chars[i]) { // normal ascii character 0x20...0x21, 0x23...0x5B, 0x5D...0x7E => {}, 0x00...0x1F, '\\', '\"' => { // Always must escape these. try writer.writeAll(chars[write_cursor..i]); try outputSpecialEscape(chars[i], writer); write_cursor = i + 1; }, 0x7F...0xFF => { try writer.writeAll(chars[write_cursor..i]); const ulen = std.unicode.utf8ByteSequenceLength(chars[i]) catch unreachable; const codepoint = std.unicode.utf8Decode(chars[i..][0..ulen]) catch unreachable; try outputUnicodeEscape(codepoint, writer); i += ulen - 1; write_cursor = i + 1; }, } } } else { while (i < chars.len) : (i += 1) { switch (chars[i]) { // normal bytes 0x20...0x21, 0x23...0x5B, 0x5D...0xFF => {}, 0x00...0x1F, '\\', '\"' => { // Always must escape these. try writer.writeAll(chars[write_cursor..i]); try outputSpecialEscape(chars[i], writer); write_cursor = i + 1; }, } } } try writer.writeAll(chars[write_cursor..chars.len]); } test { _ = @import("./stringify_test.zig"); } |
Generated by zstd-browse2 on 2023-11-04 14:12:19 -0400. |