Parse json.Value into other type

Hi! I just started learning some Zig the other night and have been doing small examples since then, and I find it very fun to learn!

Yesterday I started an example where I want to convert a json.Value to another type using the json.parseFromValue function. I get it work, partially. It works for basic types such as u16 and bool, but I cannot make it work with []const u8 fields.

Here’s my code:

const std = @import("std");
const json = std.json;
const t = std.testing;
const expect = t.expect;

pub const Message = struct {
    id: u16,
    method: []const u8,
    params: ?json.Value,

    pub fn paramsTo(self: Message, all: std.mem.Allocator, comptime param_type: type) !t {
        if (self.params == null) {
            return error.NullParams;
        }

        const res = try json.parseFromValue(param_type, all, self.params.?, .{});
        defer res.deinit();
        return res.value;
    }
};

pub const SampleParams = struct {
    message: []const u8, // Doesn't work
    number: u16, // OK
    booler: bool, // OK
};

test "deserialize message params" {
    const raw_message = (
        \\{
        \\  "id": 1,
        \\  "method": "initialize",
        \\  "params": {"message": "hello", "number": 16, "booler": true}
        \\}
    );
    const parsed = try std.json.parseFromSlice(Message, t.allocator, raw_message, .{});
    defer parsed.deinit();

    try t.expectEqual(1, parsed.value.id);
    try t.expectEqualStrings("initialize", parsed.value.method);
    try t.expect(parsed.value.params != null);

    // Convert the json.Value to another type
    const params = try parsed.value.paramsTo(t.allocator, SampleParams);
    try t.expectEqual(16, params.number);
    try t.expect(params.booler);
    try t.expectEqual(5, params.message.len);
    try t.expectEqualStrings("hello", params.message); // <-- Boom!
}

It fails when running the test:
image

What I’m doing wrong and how can I achieve what I’m trying to do here?
Thanks in advance!

This is not the first error message I get when running your test. The paramsTo method claims to return !std.testing, which doesn’t make sense in the first place because std.testing is a namespace, not meant to be used as a type. paramsTo returns param_type, so the return type should be !param_type.

Now, the reason why your test fails is because you call res.deinit() before the value is returned from paramsTo, which means you are returning a pointer ([]const u8) to already freed memory (this is why you get a aa byte, because free sets the freed data to undefined, which in debug mode writes 0xaa bytes).

The reason you are getting the failure on the string specifically is because the int/bool value are copied, whereas only the pointer/length of the string is copied, the actual memory containing the characters is not. You should be returning res without calling deinit and letting the caller free the memory after it is used:

    pub fn paramsTo(self: Message, all: std.mem.Allocator, comptime param_type: type) !json.Parsed(param_type) {
        if (self.params == null) {
            return error.NullParams;
        }

        return json.parseFromValue(param_type, all, self.params.?, .{});
    }

    // Convert the json.Value to another type
    const params_res = try parsed.value.paramsTo(t.allocator, SampleParams);
    defer params_res.deinit();
    const params = params_res.value;
    try t.expectEqual(16, params.number);

And:

$ zig test json.zig
All 1 tests passed.

P.S: Welcome to Ziggit :smile:

2 Likes

Regarding !t that was what I first called the param_type but I forgot to change it when I copied it over here :sweat_smile:

That makes so much sense. Thank you so much for the help!

I guess it takes a while to get used to manage the memory like you do in zig.
I come from a Go/Rust background so using defer like I did was more of a reflex than a conscious thing – I learned something valuable because of this.

Again, thanks for the help!

1 Like

No problem. Another thing is that it is convention to name type parameters with capital letters, e.g Params instead of param_type.

1 Like

All right, thanks.