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, }; } }; }; }