Deserializers
Every Getty deserializer implements the getty.Deserializer
interface, shown below.
// (1)!
fn Deserializer(
comptime Context: type, // (2)!
comptime E: type, // (3)!
// (4)!
comptime user_dbt: anytype,
comptime deserializer_dbt: anytype,
// (5)!
comptime methods: struct {
// (6)!
deserializeAny: ?fn (Context, std.mem.Allocator, v: anytype) E!@TypeOf(v).Value = null,
deserializeBool: ?fn (Context, std.mem.Allocator, v: anytype) E!@TypeOf(v).Value = null,
deserializeEnum: ?fn (Context, std.mem.Allocator, v: anytype) E!@TypeOf(v).Value = null,
deserializeFloat: ?fn (Context, std.mem.Allocator, v: anytype) E!@TypeOf(v).Value = null,
deserializeIgnored: ?fn (Context, std.mem.Allocator, v: anytype) E!@TypeOf(v).Value = null,
deserializeInt: ?fn (Context, std.mem.Allocator, v: anytype) E!@TypeOf(v).Value = null,
deserializeMap: ?fn (Context, std.mem.Allocator, v: anytype) E!@TypeOf(v).Value = null,
deserializeOptional: ?fn (Context, std.mem.Allocator, v: anytype) E!@TypeOf(v).Value = null,
deserializeSeq: ?fn (Context, std.mem.Allocator, v: anytype) E!@TypeOf(v).Value = null,
deserializeString: ?fn (Context, std.mem.Allocator, v: anytype) E!@TypeOf(v).Value = null,
deserializeStruct: ?fn (Context, std.mem.Allocator, v: anytype) E!@TypeOf(v).Value = null,
deserializeUnion: ?fn (Context, std.mem.Allocator, v: anytype) E!@TypeOf(v).Value = null,
deserializeVoid: ?fn (Context, std.mem.Allocator, v: anytype) E!@TypeOf(v).Value = null,
},
) type
-
A
getty.Deserializer
deserializes values from a data format into Getty's data model. -
Context
is a namespace that owns the method implementations passed to themethods
parameter.Usually, this is the type implementing
getty.Deserializer
or a pointer to it if mutability is required. -
E
is the error set returned bygetty.Deserializer
's methods upon failure.The value of
E
must containgetty.de.Error
, a base error set defined by Getty. -
user_dbt
anddeserializer_dbt
are optional user- and deserializer-defined derialization blocks or tuples, respectively.They allow users and deserializers to customize Getty's deserialization behavior. If user- or deserializer-defined customization isn't supported,
null
can be passed in for these parameters. -
methods
lists every method that aDeserializer
must provide or can override. -
These methods are responsible for deserializing into a specific type in Getty's data model from a data format.
The
v
parameter in these methods is agetty.de.Visitor
interface value.The
deserializeAny
anddeserializeIgnored
methods are pretty niche, so we can ignore them for this tutorial.
Quite the parameter list!
Luckily, most of the parameters have default values we can use. So let's kick things off with the following implementation:
src/main.zig
const std = @import("std");
const getty = @import("getty");
const Deserializer = struct {
tokens: std.json.TokenStream, // (1)!
const Self = @This();
pub usingnamespace getty.Deserializer(
*Self,
Error,
null,
null,
.{},
);
const Error = getty.de.Error ||
std.json.TokenStream.Error ||
std.fmt.ParseIntError ||
std.fmt.ParseFloatError;
const De = Self.@"getty.Deserializer"; // (2)!
pub fn init(json: []const u8) Self {
return .{ .tokens = std.json.TokenStream.init(json) };
}
};
-
A JSON parser provided by the standard library.
-
A convenient alias for our
getty.Deserializer
interface type.
Scalar Deserialization
To deserialize a value with our brand new Deserializer
, we can call
getty.deserialize
, which takes an
optional allocator, a type to deserialize into, and a
getty.Deserializer
interface
value.
src/main.zig
const std = @import("std");
const getty = @import("getty");
const ally = std.heap.page_allocator;
const Deserializer = struct {
tokens: std.json.TokenStream,
const Self = @This();
pub usingnamespace getty.Deserializer(
*Self,
Error,
null,
null,
.{},
);
const Error = getty.de.Error ||
std.json.TokenStream.Error ||
std.fmt.ParseIntError ||
std.fmt.ParseFloatError;
const De = Self.@"getty.Deserializer";
pub fn init(json: []const u8) Self {
return .{ .tokens = std.json.TokenStream.init(json) };
}
};
pub fn main() !void {
var d = Deserializer.init("true");
const dd = d.deserializer();
const result = try getty.deserialize(ally, bool, dd);
defer result.deinit();
const value = result.value;
std.debug.print("{} ({})\n", .{ value, @TypeOf(value) });
}
$ zig build run
[...] error: deserializeBool is not implemented by type: *main.Deserializer
A compile error!
Looks like Getty can't deserialize into the bool
type unless
deserializeBool
is implemented. Let's fix that.
src/main.zig
const std = @import("std");
const getty = @import("getty");
const Allocator = std.mem.Allocator;
const ally = std.heap.page_allocator;
const Deserializer = struct {
tokens: std.json.TokenStream,
const Self = @This();
pub usingnamespace getty.Deserializer(
*Self,
Error,
null,
null,
.{
.deserializeBool = deserializeBool,
},
);
const Error = getty.de.Error ||
std.json.TokenStream.Error ||
std.fmt.ParseIntError ||
std.fmt.ParseFloatError;
const De = Self.@"getty.Deserializer";
pub fn init(json: []const u8) Self {
return .{ .tokens = std.json.TokenStream.init(json) };
}
// (1)!
fn deserializeBool(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .True or token == .False) {
return try v.visitBool(arena, De, token == .True);
}
}
return error.InvalidType;
}
};
pub fn main() !void {
var d = Deserializer.init("true");
const dd = d.deserializer();
const result = try getty.deserialize(ally, bool, dd);
defer result.deinit();
const value = result.value;
std.debug.print("{} ({})\n", .{ value, @TypeOf(value) });
}
-
What we're doing in this function is:
- Parsing a token from the JSON data.
- Checking to see if the token is a JSON Boolean.
- Deserializing the token into a Boolean (
token == .True
). - Passing the Boolean to the visitor,
v
.
Success!
Now let's do the same thing for the other scalar types.
src/main.zig
const std = @import("std");
const getty = @import("getty");
const Allocator = std.mem.Allocator;
const ally = std.heap.page_allocator;
const Deserializer = struct {
tokens: std.json.TokenStream,
const Self = @This();
pub usingnamespace getty.Deserializer(
*Self,
Error,
null,
null,
.{
.deserializeBool = deserializeBool,
.deserializeEnum = deserializeEnum,
.deserializeFloat = deserializeFloat,
.deserializeInt = deserializeInt,
.deserializeString = deserializeString,
.deserializeVoid = deserializeVoid,
.deserializeOptional = deserializeOptional,
},
);
const Error = getty.de.Error ||
std.json.TokenStream.Error ||
std.fmt.ParseIntError ||
std.fmt.ParseFloatError;
const De = Self.@"getty.Deserializer";
pub fn init(json: []const u8) Self {
return .{ .tokens = std.json.TokenStream.init(json) };
}
fn deserializeBool(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .True or token == .False) {
return try v.visitBool(arena, De, token == .True);
}
}
return error.InvalidType;
}
// (1)!
fn deserializeEnum(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .String) {
const str = token.String.slice(self.tokens.slice, self.tokens.i - 1);
return try v.visitString(arena, De, str);
}
}
return error.InvalidType;
}
fn deserializeFloat(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .Number) {
const str = token.Number.slice(self.tokens.slice, self.tokens.i - 1);
return try v.visitFloat(arena, De, try std.fmt.parseFloat(f64, str));
}
}
return error.InvalidType;
}
fn deserializeInt(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .Number) {
const str = token.Number.slice(self.tokens.slice, self.tokens.i - 1);
if (token.Number.is_integer) {
return try switch (str[0]) {
'-' => v.visitInt(arena, De, try std.fmt.parseInt(i64, str, 10)),
else => v.visitInt(arena, De, try std.fmt.parseInt(u64, str, 10)),
};
}
}
}
return error.InvalidType;
}
fn deserializeString(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .String) {
const str = token.String.slice(self.tokens.slice, self.tokens.i - 1);
return try v.visitString(arena, De, try allocator.?.dupe(u8, str));
}
}
return error.InvalidType;
}
fn deserializeVoid(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .Null) {
return try v.visitVoid(arena, De);
}
}
return error.InvalidType;
}
// (2)!
fn deserializeOptional(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
const backup = self.tokens;
if (try self.tokens.next()) |token| {
if (token == .Null) {
return try v.visitNull(arena, De);
}
self.tokens = backup;
return try v.visitSome(arena, self.deserializer());
}
return error.InvalidType;
}
};
pub fn main() !void {
const types = .{ i32, f32, []u8, enum { foo }, ?u8, void };
const jsons = .{ "10", "10.0", "\"ABC\"", "\"foo\"", "null", "null" };
inline for (jsons, 0..) |data, i| {
var d = Deserializer.init(data);
const dd = d.deserializer();
const result = try getty.deserialize(ally, types[i], dd);
defer result.deinit();
const value = result.value;
std.debug.print("{any} ({})\n", .{ value, @TypeOf(value) });
}
}
-
Just like in
deserializeBool
, all we're doing in these functions is parsing tokens, turning them into Getty values, and passing those values to a visitor.By the way, you'll see
token.X.slice
come up pretty often in our deserializer. All it's doing is getting the string that corresponds to our token from the JSON data. -
deserializeOptional
is a bit different from the other methods. Instead of passing a Getty value to a visitor, you pass a deserializer tovisitSome
. The visitor will then restart the deserialization process using the optional's payload.You can think of this method as a place to do some pre-processing before deserializing an actual payload value.
$ zig build run
10 (i32)
1.0e+01 (f32)
{ 65, 66, 67 } ([]u8)
main.main__enum_1315.foo (main.main__enum_1315)
null (?u8)
void (void)
Easy peasy!
The deserialize*
methods
When Getty calls deserializeBool
, it is not telling Deserializer
that
it should parse and deserialize a JSON Boolean from its input data.
Instead, Getty is simply providing a hint about the type that is being
deserialized into.
That is, Getty is telling Deserializer
, "Hey, the type that the user is
deserializing into can most likely be constructed from a Getty Boolean, so you
should probably deserialize your input data into one."
What this means is that we don't have to limit ourselves to parsing only
JSON Booleans in deserializeBool
. We could, for instance, have
deserializeBool
support JSON numbers as well.
fn deserializeBool(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
// JSON Booleans -> Getty Booleans
if (token == .True or token == .False) {
return try v.visitBool(arena, De, token == .True);
}
// JSON Numbers -> Getty Booleans
if (token == .Number) {
if (token.Number.is_integer) {
const str = token.Number.slice(self.tokens.slice, self.tokens.i - 1);
const int = try std.fmt.parseInt(i64, str, 10);
return try v.visitBool(arena, De, int != 0);
}
}
}
return error.InvalidType;
}
Aggregate Deserialization
Alright, now let's take a look at deserialization for aggregate types.
The difference between scalar and aggregate deserialization is that the
aggregate types in Getty's data model do not directly map to any particular Zig
type (or set of Zig types). That is, while Booleans are represented by bool
s
and Integers are represented by any Zig integer type, there is no native data
type in Zig that can generically represent Sequences or Maps.
This is where the aggregate deserialization interfaces come in. They represent the aggregate types within Getty's data model (from a deserialization perspective). There are four of them:
getty.de.SeqAccess
-
Represents a Sequence.
getty.de.MapAccess
-
Represents a Map.
getty.de.UnionAccess
,getty.de.VariantAccess
-
Represents a Union.
Let's start by implementing deserializeSeq
, which uses the
getty.de.SeqAccess
interface.
getty.de.SeqAccess
// (1)!
fn SeqAccess(
comptime Context: type,
comptime E: type,
comptime methods: struct {
// (2)!
nextElementSeed: ?fn (Context, ?std.mem.Allocator, seed: anytype) E!?@TypeOf(seed).Value = null,
},
) type
-
A
getty.de.SeqAccess
is responsible for deserializing elements of a Sequence into Zig. -
The
seed
parameter ofnextElementSeed
is agetty.de.Seed
interface value, which allows for stateful deserialization.By default, Getty passes in
getty.de.DefaultSeed
seed
. The default seed just callsgetty.deserialize
and can therefore be used for stateless deserialization.
src/main.zig
const std = @import("std");
const getty = @import("getty");
const Allocator = std.mem.Allocator;
const ally = std.heap.page_allocator;
const Deserializer = struct {
tokens: std.json.TokenStream,
const Self = @This();
pub usingnamespace getty.Deserializer(
*Self,
Error,
null,
null,
.{
.deserializeBool = deserializeBool,
.deserializeEnum = deserializeEnum,
.deserializeFloat = deserializeFloat,
.deserializeInt = deserializeInt,
.deserializeString = deserializeString,
.deserializeVoid = deserializeVoid,
.deserializeOptional = deserializeOptional,
.deserializeSeq = deserializeSeq,
},
);
const Error = getty.de.Error ||
std.json.TokenStream.Error ||
std.fmt.ParseIntError ||
std.fmt.ParseFloatError;
const De = Self.@"getty.Deserializer";
pub fn init(json: []const u8) Self {
return .{ .tokens = std.json.TokenStream.init(json) };
}
fn deserializeBool(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .True or token == .False) {
return try v.visitBool(arena, De, token == .True);
}
}
return error.InvalidType;
}
fn deserializeEnum(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .String) {
const str = token.String.slice(self.tokens.slice, self.tokens.i - 1);
return try v.visitString(arena, De, str);
}
}
return error.InvalidType;
}
fn deserializeFloat(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .Number) {
const str = token.Number.slice(self.tokens.slice, self.tokens.i - 1);
return try v.visitFloat(arena, De, try std.fmt.parseFloat(f64, str));
}
}
return error.InvalidType;
}
fn deserializeInt(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .Number) {
const str = token.Number.slice(self.tokens.slice, self.tokens.i - 1);
if (token.Number.is_integer) {
return try switch (str[0]) {
'-' => v.visitInt(arena, De, try std.fmt.parseInt(i64, str, 10)),
else => v.visitInt(arena, De, try std.fmt.parseInt(u64, str, 10)),
};
}
}
}
return error.InvalidType;
}
fn deserializeString(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .String) {
const str = token.String.slice(self.tokens.slice, self.tokens.i - 1);
return try v.visitString(arena, De, try allocator.?.dupe(u8, str));
}
}
return error.InvalidType;
}
fn deserializeVoid(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .Null) {
return try v.visitVoid(arena, De);
}
}
return error.InvalidType;
}
fn deserializeOptional(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
const backup = self.tokens;
if (try self.tokens.next()) |token| {
if (token == .Null) {
return try v.visitNull(arena, De);
}
self.tokens = backup;
return try v.visitSome(arena, self.deserializer());
}
return error.InvalidType;
}
fn deserializeSeq(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .ArrayBegin) {
var sa = SeqAccess{ .de = self };
return try v.visitSeq(arena, De, sa.seqAccess());
}
}
return error.InvalidType;
}
};
const SeqAccess = struct {
de: *Deserializer,
pub usingnamespace getty.de.SeqAccess(
*@This(),
Deserializer.Error,
.{ .nextElementSeed = nextElementSeed },
);
fn nextElementSeed(self: *@This(), arena: Allocator, seed: anytype) Deserializer.Error!?@TypeOf(seed).Value {
// Deserialize element.
const element = seed.deserialize(arena, self.de.deserializer()) catch |err| {
// End of input was encountered early.
if (self.de.tokens.i - 1 >= self.de.tokens.slice.len) {
return err;
}
return switch (self.de.tokens.slice[self.de.tokens.i - 1]) {
']' => null, // End of sequence was encountered.
else => err, // Unexpected token was encountered.
};
};
return element;
}
};
pub fn main() !void {
var d = Deserializer.init("[1,2,3]");
const dd = d.deserializer();
const result = try getty.deserialize(ally, std.ArrayList(i32), dd);
defer result.deinit();
const value = result.value;
defer value.deinit();
std.debug.print("{any} ({})\n", .{ value.items, @TypeOf(value) });
}
Hooray!
Just like before, notice how we didn't have to write any iteration- or
access-related code specific to std.ArrayList
or any other Zig type. We just
had to specify how JSON sequences (arrays) should be deserialized and
Getty took care of the rest!
Okay, that leaves us with deserializeMap
and deserializeUnion
. Let's
implement the former, which uses the
getty.de.MapAccess
interface.
getty.de.MapAccess
// (1)!
fn MapAccess(
comptime Context: type,
comptime E: type,
comptime methods: struct {
nextKeySeed: ?fn (Context, ?std.mem.Allocator, seed: anytype) E!?@TypeOf(seed).Value = null,
nextValueSeed: ?fn (Context, ?std.mem.Allocator, seed: anytype) E!@TypeOf(seed).Value = null,
},
) type
- A
getty.de.MapAccess
is responsible for deserializing entries of a Map into Zig.
src/main.zig
const std = @import("std");
const getty = @import("getty");
const Allocator = std.mem.Allocator;
const ally = std.heap.page_allocator;
const Deserializer = struct {
tokens: std.json.TokenStream,
const Self = @This();
pub usingnamespace getty.Deserializer(
*Self,
Error,
null,
null,
.{
.deserializeBool = deserializeBool,
.deserializeEnum = deserializeEnum,
.deserializeFloat = deserializeFloat,
.deserializeInt = deserializeInt,
.deserializeString = deserializeString,
.deserializeVoid = deserializeVoid,
.deserializeOptional = deserializeOptional,
.deserializeSeq = deserializeSeq,
.deserializeMap = deserializeMap,
},
);
const Error = getty.de.Error ||
std.json.TokenStream.Error ||
std.fmt.ParseIntError ||
std.fmt.ParseFloatError;
const De = Self.@"getty.Deserializer";
pub fn init(json: []const u8) Self {
return .{ .tokens = std.json.TokenStream.init(json) };
}
fn deserializeBool(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .True or token == .False) {
return try v.visitBool(arena, De, token == .True);
}
}
return error.InvalidType;
}
fn deserializeEnum(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .String) {
const str = token.String.slice(self.tokens.slice, self.tokens.i - 1);
return try v.visitString(arena, De, str);
}
}
return error.InvalidType;
}
fn deserializeFloat(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .Number) {
const str = token.Number.slice(self.tokens.slice, self.tokens.i - 1);
return try v.visitFloat(arena, De, try std.fmt.parseFloat(f64, str));
}
}
return error.InvalidType;
}
fn deserializeInt(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .Number) {
const str = token.Number.slice(self.tokens.slice, self.tokens.i - 1);
if (token.Number.is_integer) {
return try switch (str[0]) {
'-' => v.visitInt(arena, De, try std.fmt.parseInt(i64, str, 10)),
else => v.visitInt(arena, De, try std.fmt.parseInt(u64, str, 10)),
};
}
}
}
return error.InvalidType;
}
fn deserializeString(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .String) {
const str = token.String.slice(self.tokens.slice, self.tokens.i - 1);
return try v.visitString(arena, De, try allocator.?.dupe(u8, str));
}
}
return error.InvalidType;
}
fn deserializeVoid(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .Null) {
return try v.visitVoid(arena, De);
}
}
return error.InvalidType;
}
fn deserializeOptional(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
const backup = self.tokens;
if (try self.tokens.next()) |token| {
if (token == .Null) {
return try v.visitNull(arena, De);
}
self.tokens = backup;
return try v.visitSome(arena, self.deserializer());
}
return error.InvalidType;
}
fn deserializeSeq(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .ArrayBegin) {
var sa = SeqAccess{ .de = self };
return try v.visitSeq(arena, De, sa.seqAccess());
}
}
return error.InvalidType;
}
fn deserializeMap(self: *Self, arena: Allocator, v: anytype) Error!@TypeOf(v).Value {
if (try self.tokens.next()) |token| {
if (token == .ObjectBegin) {
var ma = MapAccess{ .de = self };
return try v.visitMap(arena, De, ma.mapAccess());
}
}
return error.InvalidType;
}
};
const SeqAccess = struct {
de: *Deserializer,
pub usingnamespace getty.de.SeqAccess(
*@This(),
Deserializer.Error,
.{ .nextElementSeed = nextElementSeed },
);
fn nextElementSeed(self: *@This(), arena: Allocator, seed: anytype) Deserializer.Error!?@TypeOf(seed).Value {
const element = seed.deserialize(arena, self.de.deserializer()) catch |err| {
// End of input was encountered early.
if (self.de.tokens.i - 1 >= self.de.tokens.slice.len) {
return err;
}
return switch (self.de.tokens.slice[self.de.tokens.i - 1]) {
']' => null, // End of sequence was encountered.
else => err, // Unexpected token was encountered.
};
};
return element;
}
};
const MapAccess = struct {
de: *Deserializer,
pub usingnamespace getty.de.MapAccess(
*@This(),
Deserializer.Error,
.{
.nextKeySeed = nextKeySeed,
.nextValueSeed = nextValueSeed,
},
);
fn nextKeySeed(self: *@This(), arena: Allocator, seed: anytype) Deserializer.Error!?@TypeOf(seed).Value {
const tokens = self.d.tokens;
if (try self.d.tokens.next()) |token| {
// End of map was encountered.
if (token == .ObjectEnd) {
return null;
}
// Key was encountered.
if (token == .String) {
// Restore key.
self.de.tokens = tokens;
// Deserialize key.
return try seed.deserialize(arena, self.de.deserializer());
}
}
return error.InvalidType;
}
fn nextValueSeed(self: *@This(), arena: Allocator, seed: anytype) Deserializer.Error!@TypeOf(seed).Value {
return try seed.deserialize(arena, self.d.deserializer());
}
};
pub fn main() !void {
var d = Deserializer.init("\"x\":1,\"y\":2");
const dd = d.deserializer();
const result = try getty.deserialize(ally, struct{ x: i32, y: i32 }, dd);
defer result.deinit();
const value = result.value;
std.debug.print("{any} ({})\n", .{ value, @TypeOf(value) });
}
-
What we're doing here is telling Getty to perform deserialization again (by calling
seed.deserialize
) so that we can deserialize an element from the deserializer's input data.If there are no elements left (i.e., if
]
was encountered) thennull
is returned. Otherwise, the deserialized element is.The
seed
parameter ofnextElementSeed
is agetty.de.Seed
interface value, which allows for stateful deserialization. We don't really need that for this tutorial, but we can still useseed
since the default seed of Getty just callsgetty.deserialize
.