Customization
So far, Getty has taken care of all of the little (de)serialization details for us behind the scenes. But sometimes, you need more control. That's where Getty's customization features come in.
Getty allows both users and (de)serializers to customize the (de)serialization
process for types that you've defined yourself, as well as for types that you
didn't define such as those in the standard library. Moreover, the
customization enabled by Getty can be used in a local manner. That is, you can
serialize a bool
value as a String in one function and as an
Integer in another, all without having to convert the value to a new or
intermediate type.
Customization in Getty revolves around Blocks and
Tuples, which can be passed to
Getty via the *_sbt
and *_dbt
parameters of the
getty.Serializer
or
getty.Deserializer
interfaces.
Out-of-Band Customization
Here, we define a serialization block that serializes bool
values as
Integers.
const std = @import("std");
const getty = @import("getty");
// (1)!
const Serializer = struct {
pub usingnamespace getty.Serializer(
@This(),
Ok,
Error,
block, // (2)!
null,
null,
null,
null,
.{ .serializeInt = serializeInt },
);
const Ok = void;
const Error = error{};
fn serializeInt(_: @This(), value: anytype) Error!Ok {
std.debug.print("{}\n", .{value});
}
};
const block = struct {
// (3)!
pub fn is(comptime T: type) bool {
return T == bool;
}
// (4)!
pub fn serialize(value: anytype, serializer: anytype) !@TypeOf(serializer).Ok {
const v: i32 = if (value) 1 else 0;
return try serializer.serializeInt(v);
}
};
pub fn main() !void {
const s = (Serializer{}).serializer();
try getty.serialize(null, true, s);
try getty.serialize(null, false, s);
}
-
This serializer only knows how to serialize Integers.
-
With
block
being passed to Getty,bool
values will now be serialized into Getty's data model as Integers, which, of course, is a type thatSerializer
knows how to serialize. -
is
specifies which typesblock
applies to. -
serialize
specifies how to serialize values relevant toblock
.In this case, we serialize the incoming
bool
value as an Integer before passing it on to the serializer.
We can also make Serializer
generic over a BT to make customization even
easier for users.
const std = @import("std");
const getty = @import("getty");
fn Serializer(comptime user_sbt: anytype) type {
return struct {
pub usingnamespace getty.Serializer(
@This(),
Ok,
Error,
user_sbt,
null,
null,
null,
null,
.{
.serializeInt = serializeInt,
.serializeString = serializeString,
},
);
const Ok = void;
const Error = error{};
fn serializeInt(_: @This(), value: anytype) Error!Ok {
std.debug.print("{}\n", .{value});
}
fn serializeString(_: @This(), value: anytype) Error!Ok {
std.debug.print("\"{s}\"\n", .{value});
}
};
}
const int_block = struct {
pub fn is(comptime T: type) bool {
return T == bool;
}
pub fn serialize(value: anytype, serializer: anytype) !@TypeOf(serializer).Ok {
const v: i32 = if (value) 1 else 0;
return try serializer.serializeInt(v);
}
};
const string_block = struct {
pub fn is(comptime T: type) bool {
return T == bool;
}
pub fn serialize(value: anytype, serializer: anytype) !@TypeOf(serializer).Ok {
const v = if (value) "true" else "false";
return try serializer.serializeString(v);
}
};
pub fn main() !void {
// Integer
{
const s = (Serializer(int_block){}).serializer();
try getty.serialize(null, true, s);
try getty.serialize(null, false, s);
}
// String
{
const s = (Serializer(string_block){}).serializer();
try getty.serialize(null, true, s);
try getty.serialize(null, false, s);
}
}
In-Band Customization
Out-of-band customization has its uses, such as when you want to customize a
type that you didn't define. However, there's a more convenient way to do
things for struct
and union
types that you did define yourself.
If you define a BT within a struct
or union
, Getty will automatically
process it without you having to pass it in directly through a (de)serializer.
Just make sure the BT is public and named either @"getty.sb"
or @"getty.db"
(sb
for serialization, db
for deserialization).
src/main.zig
const std = @import("std");
const getty = @import("getty");
const Point = struct {
x: i32,
y: i32,
pub const @"getty.sb" = struct {
pub const attributes = .{
.x = .{ .rename = "X" },
.y = .{ .skip = true },
};
};
};
const Serializer = struct {
pub usingnamespace getty.Serializer(
@This(),
Ok,
Error,
null,
null,
null,
null,
Struct,
.{
.serializeInt = serializeInt,
.serializeString = serializeString,
.serializeStruct = serializeStruct,
},
);
const Ok = void;
const Error = error{};
fn serializeInt(_: @This(), value: anytype) Error!Ok {
std.debug.print("{}", .{value});
}
fn serializeString(_: @This(), value: anytype) Error!Ok {
std.debug.print("\"{s}\"", .{value});
}
fn serializeStruct(_: @This(), comptime _: []const u8, _: usize) Error!Struct {
std.debug.print("{{", .{});
return Struct{};
}
};
const Struct = struct {
first: bool = true,
pub usingnamespace getty.ser.Structure(
*@This(),
Ok,
Error,
.{
.serializeField = serializeField,
.end = end,
},
);
const Ok = Serializer.Ok;
const Error = Serializer.Error;
fn serializeField(self: *@This(), comptime key: []const u8, value: anytype) Error!void {
// Serialize key.
switch (self.first) {
true => self.first = false,
false => std.debug.print(", ", .{}),
}
try getty.serialize(null, key, (Serializer{}).serializer());
// Serialize value.
std.debug.print(": ", .{});
try getty.serialize(null, value, (Serializer{}).serializer());
}
fn end(_: *@This()) Error!Ok {
std.debug.print("}}\n", .{});
}
};
pub fn main() !void {
const v = Point{ .x = 1, .y = 2 };
const s = (Serializer{}).serializer();
try getty.serialize(null, v, s);
}