How to stringify complex struct

Hello. A beginner here.

I’m trying to strinfigy a complex struct (struct of arrays of struct). I’m stuck withe following error:

unable to stringify type '[*]align(8) u8' without sentinel

Code snippet of what I’m trying:

const std = @import("std");
const zap = @import("zap");

const Item = struct { id: []const u8 };

const ResponseArgs = struct { docs: std.MultiArrayList(Item) };

const Response = struct {
    status: u8,
    msg: []const u8,
    args: ResponseArgs,
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer _ = gpa.deinit(); // Clean up the allocator

    // or default: present menu
    var docs = std.MultiArrayList(Item){};
    defer docs.deinit(allocator);

    // Using try catch for error handling
    try docs.append(allocator, .{ .id = "e0b596e5-eb14-4d48-8e21-63feb82a788c" });

    const response = Response{
        .status = 200,
        .msg = "Found 0 docs",
        .args = ResponseArgs{ .docs = docs },
    };

    var buf: [100]u8 = undefined;
    var json_to_send: []const u8 = undefined;
    if (zap.stringifyBuf(&buf, response, .{})) |json| {
        json_to_send = json;
    } else {
        json_to_send = "null";
    }
    std.debug.print("<< json: {s}\n", .{json_to_send});
}

I’m guessing the error originates from zap.stringifyBuf, which uses std.json.stringify. The error message means somewhere you’re trying to write a string of type [*]align(8) u8, which does not contain a length or a null-terminator, so it’s length cannot be derived, and so it cannot be written.

This type is used in std.MultiArrayList’s bytes field, making MultiArrayList impossible to stringify. You will need to find another way to format your MultiArrayList, or just use a regular std.ArrayList, because I don’t see the need for you to use a MultiArrayList in the first place.

1 Like

Note that you can provide a custom jsonStringify function in your type and std.json will use it. From the doc comment of std.json.stringify.WriteStream.write:

///  * Zig `struct` -> JSON object with each field in declaration order.
///      * If the struct declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`. See `std.json.Value` for an example.
///      * See `StringifyOptions.emit_null_optional_fields`.
///  * Zig `union(enum)` -> JSON object with one field named for the active tag and a value representing the payload.
///      * If the payload is `void`, then the emitted value is `{}`.
///      * If the union declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`.
///  * Zig `enum` -> JSON string naming the active tag.
///      * If the enum declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`.
///      * If the enum is non-exhaustive, unnamed values are rendered as integers.

Example:

1 Like

Thanks for your reply.

I tried moving from MultiArrayList to ArrayList however, the step to convert to JSON is now failing with this:

run
└─ run zig-playground
   └─ zig build-exe zig-playground Debug native 4 errors
/nix/store/vm8x0fdp3gn99adhig5g0z9ri40hrq3w-zig-0.13.0/lib/zig/std/json/stringify.zig:552:52: error: cannot load opaque type 'anyopaque'
                            return self.write(value.*);
                                              ~~~~~^~
referenced by:
    write__anon_9883: /nix/store/vm8x0fdp3gn99adhig5g0z9ri40hrq3w-zig-0.13.0/lib/zig/std/json/stringify.zig:533:43
    write__anon_9561: /nix/store/vm8x0fdp3gn99adhig5g0z9ri40hrq3w-zig-0.13.0/lib/zig/std/json/stringify.zig:533:43
    remaining reference traces hidden; use '-freference-trace' to see all reference traces
/nix/store/vm8x0fdp3gn99adhig5g0z9ri40hrq3w-zig-0.13.0/lib/zig/std/json/stringify.zig:552:52: error: values of type 'fn (*anyopaque, usize, u8, u
size) ?[*]u8' must be comptime-known, but operand value is runtime-known
                            return self.write(value.*);
                                              ~~~~~^~
/nix/store/vm8x0fdp3gn99adhig5g0z9ri40hrq3w-zig-0.13.0/lib/zig/std/json/stringify.zig:552:52: note: use '*const fn (*anyopaque, usize, u8, usize)
 ?[*]u8' for a function pointer type
/nix/store/vm8x0fdp3gn99adhig5g0z9ri40hrq3w-zig-0.13.0/lib/zig/std/json/stringify.zig:552:52: error: values of type 'fn (*anyopaque, []u8, u8, us
ize, usize) bool' must be comptime-known, but operand value is runtime-known
                            return self.write(value.*);
                                              ~~~~~^~
/nix/store/vm8x0fdp3gn99adhig5g0z9ri40hrq3w-zig-0.13.0/lib/zig/std/json/stringify.zig:552:52: note: use '*const fn (*anyopaque, []u8, u8, usize, 
usize) bool' for a function pointer type
/nix/store/vm8x0fdp3gn99adhig5g0z9ri40hrq3w-zig-0.13.0/lib/zig/std/json/stringify.zig:552:52: error: values of type 'fn (*anyopaque, []u8, u8, us
ize) void' must be comptime-known, but operand value is runtime-known
                            return self.write(value.*);
                                              ~~~~~^~
/nix/store/vm8x0fdp3gn99adhig5g0z9ri40hrq3w-zig-0.13.0/lib/zig/std/json/stringify.zig:552:52: note: use '*const fn (*anyopaque, []u8, u8, usize) 
void' for a function pointer type
error: the following command failed with 4 compilation errors:

The re-written code is this:

const std = @import("std");
const zap = @import("zap");

const Item = struct {
    id: []const u8,
};

const ResponseArgs = struct {
    docs: std.ArrayList(Item),
};

const Response = struct {
    status: u8,
    msg: []const u8,
    args: ResponseArgs,
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer _ = gpa.deinit(); // Clean up the allocator

    // or default: present menu
    var docs = std.ArrayList(Item).init(allocator);
    defer docs.deinit();

    // Using try catch for error handling
    try docs.append(.{ .id = "e0b596e5-eb14-4d48-8e21-63feb82a788c" });
    try docs.append(.{ .id = "42c12917-6759-435c-8fc0-7f30812b4545" });

    const response = Response{
        .status = 200,
        .msg = "Found 0 docs",
        .args = ResponseArgs{ .docs = docs },
    };

    var buf: [100]u8 = undefined;
    var json_to_send: []const u8 = undefined;
    if (zap.stringifyBuf(&buf, response, .{})) |json| {
        json_to_send = json;
    } else {
        json_to_send = "null";
    }
    std.debug.print("<< json: {s}\n", .{json_to_send});
}

Thanks for your reply.

I failed to find the method for adding a list to jsonWriter, such as I can add to struct ResponseArgs.

This is because it is trying to format the ArrayList’s Allocator field, which contains function pointers. The solution is to make a jsonStringify method for ResponseArgs as @squeek502 suggested. Here’s what I came up with:

This will require a bigger buffer len than 100.

const ResponseArgs = struct {
    docs: std.ArrayList(Item),

    pub fn jsonStringify(self: ResponseArgs, jw: anytype) !void {
        try jw.beginArray();
        for (self.docs.items) |doc| {
            try jw.write(doc.id);
        }
        try jw.endArray();
    }
};

Another option is removing the ArrayList from ResponseArgs and simply using a slice:

diff --git a/stringify-complex-structs-original.zig b/stringify-complex-structs.zig
index 17359f3..3e0b2df 100644
--- a/stringify-complex-structs-original.zig
+++ b/stringify-complex-structs.zig
@@ -6,7 +6,7 @@ const Item = struct {
 };
 
 const ResponseArgs = struct {
-    docs: std.ArrayList(Item),
+    docs: []const Item,
 };
 
 const Response = struct {
@@ -31,7 +31,7 @@ pub fn main() !void {
     const response = Response{
         .status = 200,
         .msg = "Found 0 docs",
-        .args = ResponseArgs{ .docs = docs },
+        .args = ResponseArgs{ .docs = docs.items },
     };
 
     var buf: [100]u8 = undefined;
1 Like