Example using diagnostics for error handling with std.json

I can’t seem to find an example of using diagnostics to report error details.

For example, I have this code that fails with a SyntaxError, but which line?

const std = @import("std");

const Place = struct { lat: f32, long: f32 };

test "json parse" {
    const parsed = try std.json.parseFromSlice(
        Place,
        std.testing.allocator,
        \\{
        \\  "lat": 40.684540,
        \\  "long": -74.401422,
        \\}
    ,
        .{},
    );
    defer parsed.deinit();

    const place = parsed.value;

    try std.testing.expect(place.lat == 40.684540);
    try std.testing.expect(place.long == -74.401422);
}

I tried finding an example in scanner.zig, but didn’t find any.

I also did see the

To enable diagnostics, declare var diagnostics = Diagnostics{}; then call source.enableDiagnostics(&diagnostics); where source is either a std.json.Reader or a std.json.Scanner that has just been initialized.

but this isn’t helpful enough for me. I’d like to see a full real example.

Related: Allow returning a value with an error - #4 by kj4tmp - How I learned to love Zig's diagnostic pattern | Mike Belousov's Website

    var scanner = json.Scanner.initCompleteInput(allocator, raw);
    defer scanner.deinit();
    var jd = json.Diagnostics{};
    scanner.enableDiagnostics(&jd);
    //...
    const parsed = try json.parseFromTokenSource(T, allocator, &scanner, .{});

yoinked from my own code

and json.Scanner can be replaced with a json.Reader without any changes if you have a reader you want to get the json from.

FYI json.Diagnostics contains a pointer used to calculate some of the information, so if you wont to propogate the diagnostic info making my own diagnostic type to contain the results was the nicest solution i found
alternatively you could have the Scanner/Reader created further up but it didnt like that idea in my situation

2 Likes

Thanks! I was able to put together a full example now.

const std = @import("std");

const Place = struct { lat: f32, long: f32 };

test "json parse" {
    const raw =
        \\{
        \\  "lat": 40.684540,
        \\  "long": -74.401422,
        \\}
    ;

    var scanner = std.json.Scanner.initCompleteInput(
        std.testing.allocator,
        raw,
    );
    defer scanner.deinit();

    var diag = std.json.Diagnostics{};
    scanner.enableDiagnostics(&diag);

    _ = std.json.parseFromTokenSource(
        Place,
        std.testing.allocator,
        &scanner,
        .{},
    ) catch |err| {
        std.debug.print("{any} on line {d}\n", .{ err, diag.line_number });
    };
}

Much better error message.

$ zig test wat.zig 
error.SyntaxError on line 4
All 1 tests passed.

“SyntaxError” with no other details about exactly what’s wrong with the syntax isn’t very helpful. What’s wrong with that line? In isolation, there’s nothing actually wrong with that line – it’s what comes after that line that makes it an issue on that line. Where on the line is it? Knowing the character offset on the line would be very helpful in understanding what the actual issue is.

True it is an improvement on just “SyntaxError”.

What’s wrong with that line? In isolation, there’s nothing actually wrong with that line

I mean, I agree with ya. It’s more like line 3, than line 4.

1 {
2  "lat": 40.684540,
3  "long": -74.401422,
4 }

But, I always take “error on line X” to mean “error somewhere around line X”. :person_shrugging: Room for improvement here, sure.

Here’s is the info available for the diagnostics: std.json.Diagnostics

I updated the code to see what other info I could get.

const std = @import("std");

const Place = struct { lat: f32, long: f32 };

test "json parse" {
    const raw =
        \\{
        \\  "lat": 40.684540,
        \\  "long": -74.401422,
        \\}
    ;

    var scanner = std.json.Scanner.initCompleteInput(
        std.testing.allocator,
        raw,
    );
    defer scanner.deinit();

    var diag = std.json.Diagnostics{};
    scanner.enableDiagnostics(&diag);

    _ = std.json.parseFromTokenSource(
        Place,
        std.testing.allocator,
        &scanner,
        .{},
    ) catch |err| {
        std.debug.print("{any}\n", .{ err });
        std.debug.print("byte offset: {d}\n", .{ diag.getByteOffset() });
        std.debug.print("column: {d}\n", .{ diag.getColumn() });
        std.debug.print("line: {d}\n", .{ diag.getLine() });
    };
}
$ zig test wat.zig
error.SyntaxError
byte offset: 44
column: 1
line: 4
All 1 tests passed.