What if serde deserialization errors had "backtraces"

I've started using serde in a personal project and I just love how simple it is to use.

However, I ended up getting a bit frustrated having to debug de-serialization errors caused by bad typing. When you use serde to de-serialize JSON that doesn't fit in the type you use to de-serialize it because of bad typing, you get an error that tells you that the expected type doesn't match the parsed value. It tells you where the error comes from in the JSON input: Error("invalid type: null, expected u32", line: 1, column: 49)

This is great when you have access to the JSON, because you can quickly know where the type error is and go fix it. However, it is likely that you don't have access to the input JSON, because it could contain private info that shouldn't be logged for example.

If your have such an error that appears in your logs, it is very hard to know which type should be wrapped in an Option. The best solution is just to go look manually with the documentation of where the JSON comes from, but it's often coming from a language where anything can be null and thus what can and can't be null is likely to be poorly documented.

On the other hand, I have used GraphQL a bit, and the way errors are handled gave me an idea. What if Serde de-serialization errors had a "backtrace" or a path to the item that serde was unable to de-serialize.

For example:

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
struct Point {
    x: u32,
    y: u32,
    sub: Vec<SubStruct>,
}

#[derive(Serialize, Deserialize, Debug)]
struct SubStruct {
    x: u32,
    y: u32,
}

fn main() {
    let serialized = r#"{"x": 1,"y": 2,"sub": [{"x": 1, "y":3},{"x": null, y:3}]}"#;

    dbg!(serde_json::from_str::<Point>(&serialized));
}

When run, this program gives the following error:

[src/main.rs:19] serde_json::from_str::<Point>(&serialized) = Err(
    Error("invalid type: null, expected u32", line: 1, column: 49),
)

What I'm thinking is that the error could be improved by adding more context to the error, that would enable one to quickly find the exact location of the type error:

[src/main.rs:19] serde_json::from_str::<Point>(&serialized) = Err(
    Error("invalid type: null, expected u32", line: 1, column: 49, path: ["Point","Sub","2","x"]),
)

Reading this error (in a log for example) a dev could immediately know that the "x" member of the SubStruct likely needs to be a Option<u32>.

1 Like

https://crates.io/crates/serde_path_to_error

3 Likes

Thanks, I didn't know about that!