Renders the given Zig value as JSON.
Supported types:
bool -> JSON true or false.?T -> null or the rendering of T.i32, u64, etc. -> JSON number or string.emit_nonportable_numbers_as_strings is true, if the value is outside the range +-1<<53 (the precise integer range of f64), it is rendered as a JSON string in base 10. Otherwise, it is rendered as JSON number.[]const u8, []u8, *[N]u8, @Vector(N, u8), and similar -> JSON string.Options.emit_strings_as_arrays.[]T, [N]T, *[N]T, @Vector(N, T), and similar -> JSON array of the rendering of each item.struct -> JSON object with each field in declaration order.pub fn jsonStringify(self: *@This(), jw: anytype) !void, it is called to do the serialization instead of the default behavior. The given jw is a pointer to this Stringify. See std.json.Value for an example.Options.emit_null_optional_fields.union(enum) -> JSON object with one field named for the active tag and a value representing the payload.void, then the emitted value is {}.pub fn jsonStringify(self: *@This(), jw: anytype) !void, it is called to do the serialization instead of the default behavior. The given jw is a pointer to this Stringify.enum -> JSON string naming the active tag.pub fn jsonStringify(self: *@This(), jw: anytype) !void, it is called to do the serialization instead of the default behavior. The given jw is a pointer to this Stringify.*T -> the rendering of T. Note there is no guard against circular-reference infinite recursion.See also alternative functions print and beginWriteRaw.
For writing object field names, use objectField instead.
pub fn write(self: *Stringify, v: anytype) Error!void
pub fn write(self: *Stringify, v: anytype) Error!void {
if (build_mode_has_safety) assert(self.raw_streaming_mode == .none);
const T = @TypeOf(v);
switch (@typeInfo(T)) {
.int => {
try self.valueStart();
if (self.options.emit_nonportable_numbers_as_strings and
(v <= -(1 << 53) or v >= (1 << 53)))
{
try self.writer.print("\"{}\"", .{v});
} else {
try self.writer.print("{}", .{v});
}
self.valueDone();
return;
},
.comptime_int => {
return self.write(@as(std.math.IntFittingRange(v, v), v));
},
.float, .comptime_float => {
if (@as(f64, @floatCast(v)) == v) {
try self.valueStart();
try self.writer.print("{}", .{@as(f64, @floatCast(v))});
self.valueDone();
return;
}
try self.valueStart();
try self.writer.print("\"{}\"", .{v});
self.valueDone();
return;
},
.bool => {
try self.valueStart();
try self.writer.writeAll(if (v) "true" else "false");
self.valueDone();
return;
},
.null => {
try self.valueStart();
try self.writer.writeAll("null");
self.valueDone();
return;
},
.optional => {
if (v) |payload| {
return try self.write(payload);
} else {
return try self.write(null);
}
},
.@"enum" => |enum_info| {
if (std.meta.hasFn(T, "jsonStringify")) {
return v.jsonStringify(self);
}
if (enum_info.mode == .nonexhaustive) {
inline for (enum_info.field_names) |field_name| {
if (v == @field(T, field_name)) {
break;
}
} else {
return self.write(@backingInt(v));
}
}
return self.stringValue(@tagName(v));
},
.enum_literal => {
return self.stringValue(@tagName(v));
},
.@"union" => {
if (std.meta.hasFn(T, "jsonStringify")) {
return v.jsonStringify(self);
}
const info = @typeInfo(T).@"union";
if (info.tag_type) |UnionTagType| {
try self.beginObject();
inline for (info.field_names, info.field_types) |u_field_name, u_field_type| {
if (v == @field(UnionTagType, u_field_name)) {
try self.objectField(u_field_name);
if (u_field_type == void) {
// void v is {}
try self.beginObject();
try self.endObject();
} else {
try self.write(@field(v, 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 (std.meta.hasFn(T, "jsonStringify")) {
return v.jsonStringify(self);
}
if (S.is_tuple) {
try self.beginArray();
} else {
try self.beginObject();
}
inline for (S.field_names, S.field_types) |field_name, field_type| {
// 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(v, field_name) == null) {
emit_field = false;
}
}
}
if (emit_field) {
if (!S.is_tuple) {
try self.objectField(field_name);
}
try self.write(@field(v, field_name));
}
}
if (S.is_tuple) {
try self.endArray();
} else {
try self.endObject();
}
return;
},
.error_set => return self.stringValue(@errorName(v)),
.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, v));
},
else => {
return self.write(v.*);
},
},
.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(v) else v;
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(&v);
},
.vector => |info| {
const array: [info.len]info.child = v;
return self.write(&array);
},
else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
}
unreachable;
}