How to print struct with strings

Hi! I wonder how to print a struct in a human readable format

const Response = struct {
products: struct {
id: i32,
title: u8,
description: u8,
images: u8,
},
};

Which I print like this

for (response.products) |product| {                                                                                                                                                      
    print("{any}\n", .{product});                                                                                                                                                          
}      

and get the following output from

main.Response.Response__struct_6372{ .id = 1, .title = { 105, 80, 104, 111, 110, 101, 32, 57 }, .description = { 65, 110, 32, 97, 112, 112, 108, 101, 32, 109, 111, 98, 105, 108, 101, 32, 11
9, 104, 105, 99, 104, 32, 105, 115, 32, 110, 111, 116, 104, 105, 110, 103, 32, 108, 105, 107, 101, 32, 97, 112, 112, 108, 101 }, .images = { { 104, 116, 116, 112, 115, 58, 47, 47, 105, 46,
100, 117, 109, 109, 121, 106, 115, 111, 110, 46, 99, 111, 109, 47, 100, 97, 116, 97, 47, 112, 114, 111, 100, 117, 99, 116, 115, 47, 49, 47, 49, 46, 106, 112, 103 }, { 104, 116, 116, 112, 11
5, 58, 47, 47, 105, 46, 100, 117, 109, 109, 121, 106, 115, 111, 110, 46, 99, 111, 109, 47, 100, 97, 116, 97, 47, 112, 114, 111, 100, 117, 99, 116, 115, 47, 49, 47, 50, 46, 106, 112, 103 },
{ 104, 116, 116, 112, 115, 58, 47, 47, 105, 46, 100, 117, 109, 109, 121, 106, 115, 111, 110, 46, 99, 111, 109, 47, 100, 97, 116, 97, 47, 112, 114, 111, 100, 117, 99, 116, 115, 47, 49, 47, 5
1, 46, 106, 112, 103 }, { 104, 116, 116, 112, 115, 58, 47, 47, 105, 46, 100, 117, 109, 109, 121, 106, 115, 111, 110, 46, 99, 111, 109, 47, 100, 97, 116, 97, 47, 112, 114, 111, 100, 117, 99,
116, 115, 47, 49, 47, 52, 46, 106, 112, 103 }, { 104, 116, 116, 112, 115, 58, 47, 47, 105, 46, 100, 117, 109, 109, 121, 106, 115, 111, 110, 46, 99, 111, 109, 47, 100, 97, 116, 97, 47, 112,
114, 111, 100, 117, 99, 116, 115, 47, 49, 47, 116, 104, 117, 109, 98, 110, 97, 105, 108, 46, 106, 112, 103 } } }

Can I tell print to print u8 inside a struct as a string, not as a list of u8 without adding a custom formatter?

I think you might be able to iterate over the “fields” field, and print using the {s} formatting specifier. There is also std.fmt.AllocPrint, which might be useful to print different types as formatted strings

The default struct formatter uses the fallback format for all fields, the fallback formatter for slices prints each element. If you want to print the []u8 fields as a string you’ll have to use the {s} formatter on each individual field like:

print("title: {s}\ndescription: {s}\n", .{ product.title, product.description });
1 Like

If you’d like avoid putting each individual field in the print function each time, you can also try defining a custom formatting function, as documented in the format function.

4 Likes

Here’s an example of implementing std.fmt.format to make a struct printable as @desttinghim mentioned:

 cat src/main.zig
const std = @import("std");

const Product = struct {
    id: i32,
    title: []const u8,
    description: []const u8,
    images: []const []const u8,

    pub fn format(
        product: Product,
        comptime _: []const u8,
        _: std.fmt.FormatOptions,
        writer: anytype,
    ) !void {
        try writer.writeAll("Product{\n");
        _ = try writer.print("\tid: {},\n", .{product.id});
        _ = try writer.print("\ttitle: {s},\n", .{product.title});
        _ = try writer.print("\tdescription: {s},\n", .{product.description});
        try writer.writeAll("\timages: [\n");

        for (product.images) |image| _ = try writer.print("\t\t{s},\n", .{image});

        try writer.writeAll("\t],\n");
        try writer.writeAll("}\n");
    }
};

pub fn main() !void {
    const p = Product{
        .id = 1,
        .title = "Zed Plushy",
        .description = "Cool Zed",
        .images = &.{
            "Image 1",
            "Image 2",
            "Image 3",
        },
    };

    // Now you can print with {} format specifier.
    std.debug.print("{}\n", .{p});
}
❯ zig build run
Product{
	id: 1,
	title: Zed Plushy,
	description: Cool Zed,
	images: [
		Image 1,
		Image 2,
		Image 3,
	],
}
7 Likes

I think you’re going to need a custom formatter. If you look at the format implementation on github, I think you’re hitting this block:

 .Struct => |info| { ...

            try writer.writeAll("{");
            inline for (info.fields, 0..) |f, i| {
                if (i == 0) {
                    try writer.writeAll(" .");
                } else {
                    try writer.writeAll(", .");
                }
                try writer.writeAll(f.name);
                try writer.writeAll(" = ");
                try formatType(@field(value, f.name), ANY, options, writer, max_depth - 1);
            }
            try writer.writeAll(" }");

It’s defaulting to using “ANY” for the field type. If you think about the question, you’re going to end up creating a custom format for the struct you’re printing anyway - you’d just skip giving it a named function.

For instance - you’ll notice that in the implementation, there’s this:

 inline for (info.fields, 0..) |f, i| {

That line is doing the heavy lifting of running through the struct fields and then printing them.

If I was writing this project, I’d consider making those images each their own struct type and giving that struct a custom formatter. That way, anything that uses them can compose with their formatting functions.

4 Likes

Thanks for all the help, but I finally found what answer myself

try std.json.stringify(&value, .{}, std.io.getStdOut().writer());

will print value in JSON format, where u8 are printed as strings.

8 Likes

Clever solution! Thanks for sharing your find.

2 Likes

Brilliant idea. We could make this even more convenient by adding a JsonFormatter in the std library, i.e.

std.log.info("response is {}", .{std.json.fmt(response)});
6 Likes

That was a very clever solution indeed. Great job!

1 Like