Type Function Serializer [src]
Lower level control over serialization, you can create a new instance with serializer.
Useful when you want control over which fields are serialized, how they're represented,
or want to write a ZON object that does not exist in memory.
You can serialize values with value. To serialize recursive types, the following are provided:
valueMaxDepth
valueArbitraryDepth
You can also serialize values using specific notations:
int
float
codePoint
tuple
tupleMaxDepth
tupleArbitraryDepth
string
multilineString
For manual serialization of containers, see:
beginStruct
beginTuple
Example
var sz = serializer(writer, .{});
var vec2 = try sz.beginStruct(.{});
try vec2.field("x", 1.5, .{});
try vec2.fieldPrefix();
try sz.value(2.5);
try vec2.end();
Prototype
pub fn Serializer(Writer: type) type
Parameters
Writer: type
Source
pub fn Serializer(Writer: type) type {
return struct {
const Self = @This();
options: SerializerOptions,
indent_level: u8,
writer: Writer,
/// Initialize a serializer.
fn init(writer: Writer, options: SerializerOptions) Self {
return .{
.options = options,
.writer = writer,
.indent_level = 0,
};
}
/// Serialize a value, similar to `serialize`.
pub fn value(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void {
comptime assert(!typeIsRecursive(@TypeOf(val)));
return self.valueArbitraryDepth(val, options);
}
/// Serialize a value, similar to `serializeMaxDepth`.
pub fn valueMaxDepth(
self: *Self,
val: anytype,
options: ValueOptions,
depth: usize,
) (Writer.Error || error{ExceededMaxDepth})!void {
try checkValueDepth(val, depth);
return self.valueArbitraryDepth(val, options);
}
/// Serialize a value, similar to `serializeArbitraryDepth`.
pub fn valueArbitraryDepth(
self: *Self,
val: anytype,
options: ValueOptions,
) Writer.Error!void {
comptime assert(canSerializeType(@TypeOf(val)));
switch (@typeInfo(@TypeOf(val))) {
.int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| {
self.codePoint(c) catch |err| switch (err) {
error.InvalidCodepoint => unreachable, // Already validated
else => |e| return e,
};
} else {
try self.int(val);
},
.float, .comptime_float => try self.float(val),
.bool, .null => try std.fmt.format(self.writer, "{}", .{val}),
.enum_literal => try self.ident(@tagName(val)),
.@"enum" => try self.ident(@tagName(val)),
.pointer => |pointer| {
// Try to serialize as a string
const item: ?type = switch (@typeInfo(pointer.child)) {
.array => |array| array.child,
else => if (pointer.size == .slice) pointer.child else null,
};
if (item == u8 and
(pointer.sentinel() == null or pointer.sentinel() == 0) and
!options.emit_strings_as_containers)
{
return try self.string(val);
}
// Serialize as either a tuple or as the child type
switch (pointer.size) {
.slice => try self.tupleImpl(val, options),
.one => try self.valueArbitraryDepth(val.*, options),
else => comptime unreachable,
}
},
.array => {
var container = try self.beginTuple(
.{ .whitespace_style = .{ .fields = val.len } },
);
for (val) |item_val| {
try container.fieldArbitraryDepth(item_val, options);
}
try container.end();
},
.@"struct" => |@"struct"| if (@"struct".is_tuple) {
var container = try self.beginTuple(
.{ .whitespace_style = .{ .fields = @"struct".fields.len } },
);
inline for (val) |field_value| {
try container.fieldArbitraryDepth(field_value, options);
}
try container.end();
} else {
// Decide which fields to emit
const fields, const skipped: [@"struct".fields.len]bool = if (options.emit_default_optional_fields) b: {
break :b .{ @"struct".fields.len, @splat(false) };
} else b: {
var fields = @"struct".fields.len;
var skipped: [@"struct".fields.len]bool = @splat(false);
inline for (@"struct".fields, &skipped) |field_info, *skip| {
if (field_info.default_value_ptr) |ptr| {
const default: *const field_info.type = @ptrCast(@alignCast(ptr));
const field_value = @field(val, field_info.name);
if (std.meta.eql(field_value, default.*)) {
skip.* = true;
fields -= 1;
}
}
}
break :b .{ fields, skipped };
};
// Emit those fields
var container = try self.beginStruct(
.{ .whitespace_style = .{ .fields = fields } },
);
inline for (@"struct".fields, skipped) |field_info, skip| {
if (!skip) {
try container.fieldArbitraryDepth(
field_info.name,
@field(val, field_info.name),
options,
);
}
}
try container.end();
},
.@"union" => |@"union"| {
comptime assert(@"union".tag_type != null);
switch (val) {
inline else => |pl, tag| if (@TypeOf(pl) == void)
try self.writer.print(".{s}", .{@tagName(tag)})
else {
var container = try self.beginStruct(.{ .whitespace_style = .{ .fields = 1 } });
try container.fieldArbitraryDepth(
@tagName(tag),
pl,
options,
);
try container.end();
},
}
},
.optional => if (val) |inner| {
try self.valueArbitraryDepth(inner, options);
} else {
try self.writer.writeAll("null");
},
.vector => |vector| {
var container = try self.beginTuple(
.{ .whitespace_style = .{ .fields = vector.len } },
);
for (0..vector.len) |i| {
try container.fieldArbitraryDepth(val[i], options);
}
try container.end();
},
else => comptime unreachable,
}
}
/// Serialize an integer.
pub fn int(self: *Self, val: anytype) Writer.Error!void {
try std.fmt.formatInt(val, 10, .lower, .{}, self.writer);
}
/// Serialize a float.
pub fn float(self: *Self, val: anytype) Writer.Error!void {
switch (@typeInfo(@TypeOf(val))) {
.float => if (std.math.isNan(val)) {
return self.writer.writeAll("nan");
} else if (std.math.isPositiveInf(val)) {
return self.writer.writeAll("inf");
} else if (std.math.isNegativeInf(val)) {
return self.writer.writeAll("-inf");
} else {
try std.fmt.format(self.writer, "{d}", .{val});
},
.comptime_float => try std.fmt.format(self.writer, "{d}", .{val}),
else => comptime unreachable,
}
}
/// Serialize `name` as an identifier prefixed with `.`.
///
/// Escapes the identifier if necessary.
pub fn ident(self: *Self, name: []const u8) Writer.Error!void {
try self.writer.print(".{p_}", .{std.zig.fmtId(name)});
}
/// Serialize `val` as a Unicode codepoint.
///
/// Returns `error.InvalidCodepoint` if `val` is not a valid Unicode codepoint.
pub fn codePoint(
self: *Self,
val: u21,
) (Writer.Error || error{InvalidCodepoint})!void {
var buf: [8]u8 = undefined;
const len = std.unicode.utf8Encode(val, &buf) catch return error.InvalidCodepoint;
const str = buf[0..len];
try std.fmt.format(self.writer, "'{'}'", .{std.zig.fmtEscapes(str)});
}
/// Like `value`, but always serializes `val` as a tuple.
///
/// Will fail at comptime if `val` is not a tuple, array, pointer to an array, or slice.
pub fn tuple(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void {
comptime assert(!typeIsRecursive(@TypeOf(val)));
try self.tupleArbitraryDepth(val, options);
}
/// Like `tuple`, but recursive types are allowed.
///
/// Returns `error.ExceededMaxDepth` if `depth` is exceeded.
pub fn tupleMaxDepth(
self: *Self,
val: anytype,
options: ValueOptions,
depth: usize,
) (Writer.Error || error{ExceededMaxDepth})!void {
try checkValueDepth(val, depth);
try self.tupleArbitraryDepth(val, options);
}
/// Like `tuple`, but recursive types are allowed.
///
/// It is the caller's responsibility to ensure that `val` does not contain cycles.
pub fn tupleArbitraryDepth(
self: *Self,
val: anytype,
options: ValueOptions,
) Writer.Error!void {
try self.tupleImpl(val, options);
}
fn tupleImpl(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void {
comptime assert(canSerializeType(@TypeOf(val)));
switch (@typeInfo(@TypeOf(val))) {
.@"struct" => {
var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } });
inline for (val) |item_val| {
try container.fieldArbitraryDepth(item_val, options);
}
try container.end();
},
.pointer, .array => {
var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } });
for (val) |item_val| {
try container.fieldArbitraryDepth(item_val, options);
}
try container.end();
},
else => comptime unreachable,
}
}
/// Like `value`, but always serializes `val` as a string.
pub fn string(self: *Self, val: []const u8) Writer.Error!void {
try std.fmt.format(self.writer, "\"{}\"", .{std.zig.fmtEscapes(val)});
}
/// Options for formatting multiline strings.
pub const MultilineStringOptions = struct {
/// If top level is true, whitespace before and after the multiline string is elided.
/// If it is true, a newline is printed, then the value, followed by a newline, and if
/// whitespace is true any necessary indentation follows.
top_level: bool = false,
};
/// Like `value`, but always serializes to a multiline string literal.
///
/// Returns `error.InnerCarriageReturn` if `val` contains a CR not followed by a newline,
/// since multiline strings cannot represent CR without a following newline.
pub fn multilineString(
self: *Self,
val: []const u8,
options: MultilineStringOptions,
) (Writer.Error || error{InnerCarriageReturn})!void {
// Make sure the string does not contain any carriage returns not followed by a newline
var i: usize = 0;
while (i < val.len) : (i += 1) {
if (val[i] == '\r') {
if (i + 1 < val.len) {
if (val[i + 1] == '\n') {
i += 1;
continue;
}
}
return error.InnerCarriageReturn;
}
}
if (!options.top_level) {
try self.newline();
try self.indent();
}
try self.writer.writeAll("\\\\");
for (val) |c| {
if (c != '\r') {
try self.writer.writeByte(c); // We write newlines here even if whitespace off
if (c == '\n') {
try self.indent();
try self.writer.writeAll("\\\\");
}
}
}
if (!options.top_level) {
try self.writer.writeByte('\n'); // Even if whitespace off
try self.indent();
}
}
/// Create a `Struct` for writing ZON structs field by field.
pub fn beginStruct(
self: *Self,
options: SerializeContainerOptions,
) Writer.Error!Struct {
return Struct.begin(self, options);
}
/// Creates a `Tuple` for writing ZON tuples field by field.
pub fn beginTuple(
self: *Self,
options: SerializeContainerOptions,
) Writer.Error!Tuple {
return Tuple.begin(self, options);
}
fn indent(self: *Self) Writer.Error!void {
if (self.options.whitespace) {
try self.writer.writeByteNTimes(' ', 4 * self.indent_level);
}
}
fn newline(self: *Self) Writer.Error!void {
if (self.options.whitespace) {
try self.writer.writeByte('\n');
}
}
fn newlineOrSpace(self: *Self, len: usize) Writer.Error!void {
if (self.containerShouldWrap(len)) {
try self.newline();
} else {
try self.space();
}
}
fn space(self: *Self) Writer.Error!void {
if (self.options.whitespace) {
try self.writer.writeByte(' ');
}
}
/// Writes ZON tuples field by field.
pub const Tuple = struct {
container: Container,
fn begin(parent: *Self, options: SerializeContainerOptions) Writer.Error!Tuple {
return .{
.container = try Container.begin(parent, .anon, options),
};
}
/// Finishes serializing the tuple.
///
/// Prints a trailing comma as configured when appropriate, and the closing bracket.
pub fn end(self: *Tuple) Writer.Error!void {
try self.container.end();
self.* = undefined;
}
/// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`.
pub fn field(
self: *Tuple,
val: anytype,
options: ValueOptions,
) Writer.Error!void {
try self.container.field(null, val, options);
}
/// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`.
pub fn fieldMaxDepth(
self: *Tuple,
val: anytype,
options: ValueOptions,
depth: usize,
) (Writer.Error || error{ExceededMaxDepth})!void {
try self.container.fieldMaxDepth(null, val, options, depth);
}
/// Serialize a field. Equivalent to calling `fieldPrefix` followed by
/// `valueArbitraryDepth`.
pub fn fieldArbitraryDepth(
self: *Tuple,
val: anytype,
options: ValueOptions,
) Writer.Error!void {
try self.container.fieldArbitraryDepth(null, val, options);
}
/// Starts a field with a struct as a value. Returns the struct.
pub fn beginStructField(
self: *Tuple,
options: SerializeContainerOptions,
) Writer.Error!Struct {
try self.fieldPrefix();
return self.container.serializer.beginStruct(options);
}
/// Starts a field with a tuple as a value. Returns the tuple.
pub fn beginTupleField(
self: *Tuple,
options: SerializeContainerOptions,
) Writer.Error!Tuple {
try self.fieldPrefix();
return self.container.serializer.beginTuple(options);
}
/// Print a field prefix. This prints any necessary commas, and whitespace as
/// configured. Useful if you want to serialize the field value yourself.
pub fn fieldPrefix(self: *Tuple) Writer.Error!void {
try self.container.fieldPrefix(null);
}
};
/// Writes ZON structs field by field.
pub const Struct = struct {
container: Container,
fn begin(parent: *Self, options: SerializeContainerOptions) Writer.Error!Struct {
return .{
.container = try Container.begin(parent, .named, options),
};
}
/// Finishes serializing the struct.
///
/// Prints a trailing comma as configured when appropriate, and the closing bracket.
pub fn end(self: *Struct) Writer.Error!void {
try self.container.end();
self.* = undefined;
}
/// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`.
pub fn field(
self: *Struct,
name: []const u8,
val: anytype,
options: ValueOptions,
) Writer.Error!void {
try self.container.field(name, val, options);
}
/// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`.
pub fn fieldMaxDepth(
self: *Struct,
name: []const u8,
val: anytype,
options: ValueOptions,
depth: usize,
) (Writer.Error || error{ExceededMaxDepth})!void {
try self.container.fieldMaxDepth(name, val, options, depth);
}
/// Serialize a field. Equivalent to calling `fieldPrefix` followed by
/// `valueArbitraryDepth`.
pub fn fieldArbitraryDepth(
self: *Struct,
name: []const u8,
val: anytype,
options: ValueOptions,
) Writer.Error!void {
try self.container.fieldArbitraryDepth(name, val, options);
}
/// Starts a field with a struct as a value. Returns the struct.
pub fn beginStructField(
self: *Struct,
name: []const u8,
options: SerializeContainerOptions,
) Writer.Error!Struct {
try self.fieldPrefix(name);
return self.container.serializer.beginStruct(options);
}
/// Starts a field with a tuple as a value. Returns the tuple.
pub fn beginTupleField(
self: *Struct,
name: []const u8,
options: SerializeContainerOptions,
) Writer.Error!Tuple {
try self.fieldPrefix(name);
return self.container.serializer.beginTuple(options);
}
/// Print a field prefix. This prints any necessary commas, the field name (escaped if
/// necessary) and whitespace as configured. Useful if you want to serialize the field
/// value yourself.
pub fn fieldPrefix(self: *Struct, name: []const u8) Writer.Error!void {
try self.container.fieldPrefix(name);
}
};
const Container = struct {
const FieldStyle = enum { named, anon };
serializer: *Self,
field_style: FieldStyle,
options: SerializeContainerOptions,
empty: bool,
fn begin(
sz: *Self,
field_style: FieldStyle,
options: SerializeContainerOptions,
) Writer.Error!Container {
if (options.shouldWrap()) sz.indent_level +|= 1;
try sz.writer.writeAll(".{");
return .{
.serializer = sz,
.field_style = field_style,
.options = options,
.empty = true,
};
}
fn end(self: *Container) Writer.Error!void {
if (self.options.shouldWrap()) self.serializer.indent_level -|= 1;
if (!self.empty) {
if (self.options.shouldWrap()) {
if (self.serializer.options.whitespace) {
try self.serializer.writer.writeByte(',');
}
try self.serializer.newline();
try self.serializer.indent();
} else if (!self.shouldElideSpaces()) {
try self.serializer.space();
}
}
try self.serializer.writer.writeByte('}');
self.* = undefined;
}
fn fieldPrefix(self: *Container, name: ?[]const u8) Writer.Error!void {
if (!self.empty) {
try self.serializer.writer.writeByte(',');
}
self.empty = false;
if (self.options.shouldWrap()) {
try self.serializer.newline();
} else if (!self.shouldElideSpaces()) {
try self.serializer.space();
}
if (self.options.shouldWrap()) try self.serializer.indent();
if (name) |n| {
try self.serializer.ident(n);
try self.serializer.space();
try self.serializer.writer.writeByte('=');
try self.serializer.space();
}
}
fn field(
self: *Container,
name: ?[]const u8,
val: anytype,
options: ValueOptions,
) Writer.Error!void {
comptime assert(!typeIsRecursive(@TypeOf(val)));
try self.fieldArbitraryDepth(name, val, options);
}
fn fieldMaxDepth(
self: *Container,
name: ?[]const u8,
val: anytype,
options: ValueOptions,
depth: usize,
) (Writer.Error || error{ExceededMaxDepth})!void {
try checkValueDepth(val, depth);
try self.fieldArbitraryDepth(name, val, options);
}
fn fieldArbitraryDepth(
self: *Container,
name: ?[]const u8,
val: anytype,
options: ValueOptions,
) Writer.Error!void {
try self.fieldPrefix(name);
try self.serializer.valueArbitraryDepth(val, options);
}
fn shouldElideSpaces(self: *const Container) bool {
return switch (self.options.whitespace_style) {
.fields => |fields| self.field_style != .named and fields == 1,
else => false,
};
}
};
};
}