Xml unmarshalling using std.meta.fieldNames

I’m after feedback on some code for unmarshalling xml data into a struct, where the field names match the xml element names.

After quite a bit of fiddling, my tests are passing, it all seems to work fine… but is it appropriate to use an inline loop, std.meta.fieldNames & the @field builtin in this way? Is there a more idiomatic approach I should be taking for this kind of task?

pub const Metadata = struct {
    dbname: ?[]const u8 = null,
    dataOwner: ?[]const u8 = null,
    dataOriginTimespan: ?[]const u8 = null,
    // snipped - lots more fields

    fn init(self: *Metadata, alloc: Allocator, root: xml.xmlNodePtr) !xml.xmlNodePtr {
        var curr = xml.xmlFirstElementChild(root);
        while (curr != null and !std.mem.eql(u8, std.mem.span(curr.*.name), "schemas")) : (curr = xml.xmlNextElementSibling(curr)) {
            const value = try alloc.dupe(u8, std.mem.trim(u8, std.mem.span(xml.xmlNodeGetContent(curr)), " \t\r\n"));
            inline for (comptime std.meta.fieldNames(@TypeOf(self.*))) |nm| {
                if (std.mem.eql(u8, nm, std.mem.span(curr.*.name))) {
                    @field(self, nm) = value;
                    break;
                }
            } else alloc.free(value);
        }
        return curr;
    }

    fn deinit(self: *Metadata, alloc: Allocator) void {
        inline for (comptime std.meta.fieldNames(@TypeOf(self.*))) |nm| {
            if (@field(self, nm)) |v| alloc.free(v);
        }
    }
};

test "metadata" {
    const example =
        \\ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
        \\ <?xml-stylesheet type="text/xsl" href="metadata.xsl"?><siardArchive xmlns="http://www.bar.admin.ch/xmlns/siard/1.0/metadata.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://www.bar.admin.ch/xmlns/siard/1.0/metadata.xsd metadata.xsd">
        \\   <dbname>testnt</dbname>
        \\   <dataOwner>(...)</dataOwner>
        \\   <dataOriginTimespan>2015</dataOriginTimespan>
        \\   <!-- snipped -->
        \\   <schemas/>
        \\   </siardArchive>
    ;
    const d = xml.xmlReadMemory(example.ptr, @intCast(example.len), null, "utf-8", xml.XML_PARSE_NOBLANKS | xml.XML_PARSE_RECOVER | xml.XML_PARSE_NOERROR | xml.XML_PARSE_NOWARNING);
    const root = xml.xmlDocGetRootElement(d);
    var m = Metadata{};
    const schema_ptr = try m.init(std.testing.allocator, root);
    try std.testing.expect(schema_ptr != null);
    try std.testing.expect(m.databaseProduct != null);
    try std.testing.expectEqualStrings(m.databaseProduct.?, "Microsoft SQL Server 12.00.2000");
    m.deinit(std.testing.allocator);
}

An example is std.json, I think the general approach is similar, just more refined, so I think this is a reasonable direction to go.

2 Likes