0

How can I force serde to parse a JSON Number as a Rust u16 type?

Below I parse a JSON file. json_data is of type Value. This only has a as_u64() method, no as_u16(). As a result I first have to parse the JSON Number as a u64, then try and parse that as u16. This seems fairly convoluted. Is there some way I can force serde_json to parse this value as a u16 to avoid this?

(As a side note, the JSON data has already been validated using a JSON schema, which checks the value of x is within the bounds of u16).

$ cat test.json
{
    "x": 100
}
use serde_json::Value;
use std::fs::File;
use std::io::BufReader;

pub struct Test {
    pub x: u16,
}

fn main() {
    let filename = "test.json";
    let file =
        File::open(filename).unwrap_or_else(|e| panic!("Unable to open file {}: {}", filename, e));
    let reader = BufReader::new(file);
    let json_data: Value = serde_json::from_reader(reader)
        .unwrap_or_else(|e| panic!("Unable to parse JSON file {}: {}", filename, e));

    assert!(json_data["x"].is_u64());

    let mut t = Test { x: 5 };

    if json_data["x"].is_u64() {
        let _new_x: u64 = json_data["x"].as_u64().expect("Unable to parse x");
        t.x = u16::try_from(_new_x).expect("Unable to convert x to u16");
    }
}
0

1 Answer 1

5

Don't use the raw serde_json::Value type for this usecase.

While it is possible, it is not what serde is intended for; instead, serde is meant to deserialize directly into your struct.

The magic here is adding a #[derive(Deserialize)] to your struct. Be aware that you need to activate the derive feature of serde for this.

Like so:

use serde::Deserialize;
use std::fs::File;
use std::io::BufReader;

#[derive(Debug, Deserialize)]
pub struct Test {
    pub x: u16,
}

fn main() {
    let filename = "test.json";
    let file =
        File::open(filename).unwrap_or_else(|e| panic!("Unable to open file {}: {}", filename, e));
    let reader = BufReader::new(file);
    let json_data: Test = serde_json::from_reader(reader)
        .unwrap_or_else(|e| panic!("Unable to parse JSON file {}: {}", filename, e));

    println!("{:?}", json_data)
}
Test { x: 100 }

If you now change the test.json to:

{
    "x": 123456
}

You get:

thread 'main' panicked at src\main.rs:16:29:
Unable to parse JSON file test.json: invalid value: integer `123456`, expected u16 at line 2 column 16
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, this has fixed my issues exactly as desired. Out of curiosity, I did read the serde docs, in order to get as far as I did, but I clearly misunderstood something. I had already seen that I could add the #[derive(Deserialize)] attribute, but the solution also requires that I specify the return type of serde_json::from_reader() as my Struct (Test). Both of these are needed for this to work as desired. I don't see this clearly explained in the serde docs anywhere, did I miss it, or is it not clearly documented anywhere?
The second part (specifying the return type) is not really serde specific. It's a Rust mechanism. If the return type is a generic, you need to specify it somewhere. Instead of specifying it in the target object, you could also annotate the function itself, like so: serde_json::from_reader::<_, Test>(reader). But I find this one less intuitive to read.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.