jsonparsingrusttypesserde-json

How to create a custom type to parse [u8;32] from a json that contains a hex string in Rust


Given the next json:

[
    {
        "dataValueArray": ["0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5"],
        "dataValue": "0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757"
    }
]

What i'm trying to parse those fields of the json as type [u8;32] instead of strings.

My approach has been the next one:


#[derive(Deserialize, Debug)]
struct CommonHash([u8;32]);
impl FromStr for CommonHash {
    type Err = std::num::ParseIntError;
    // Parses a hex string into an instance of CommonHash that it is an alias of [u8;32]
    fn from_str(hex_str: &str) -> Result<Self, Self::Err> {
        let mut data: [u8; 32] = [0; 32];
        let str_stripped = hex_str.strip_prefix("0x").expect("error stripping the prefix 0x");
        hex::decode_to_slice(str_stripped, &mut data).expect("Decoding hex string failed");
        Ok(CommonHash(data))
    }
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct VectorData {
    data_value_array: Vec<CommonHash>,
    data_value: CommonHash,
}


fn main() {
    let file_path = "./src/test/vectors/root_vectors.json";
    let file_content = fs::read_to_string(file_path).expect("Should have been able to read the file");
    let json_file: Vec<VectorData> = serde_json::from_str(&file_content).expect("JSON was not well-formatted");
    println!("{:?}", json_file[0].data_value);
}

The exit of the execution is:

thread 'main' panicked at 'JSON was not well-formatted: Error("invalid type: string \"0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5\", expected an array of length 32", line: 3, column: 95)', src/main.rs:394:74
stack backtrace:
   0: rust_begin_unwind
             at /rustc/8ede3aae28fe6e4d52b38157d7bfe0d3bceef225/library/std/src/panicking.rs:593:5
   1: core::panicking::panic_fmt
             at /rustc/8ede3aae28fe6e4d52b38157d7bfe0d3bceef225/library/core/src/panicking.rs:67:14
   2: core::result::unwrap_failed
             at /rustc/8ede3aae28fe6e4d52b38157d7bfe0d3bceef225/library/core/src/result.rs:1651:5
   3: core::result::Result<T,E>::expect
             at /rustc/8ede3aae28fe6e4d52b38157d7bfe0d3bceef225/library/core/src/result.rs:1033:23
   4: merkle_tree::main
             at ./src/main.rs:394:38
   5: core::ops::function::FnOnce::call_once
             at /rustc/8ede3aae28fe6e4d52b38157d7bfe0d3bceef225/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

The problem seems that serde_json::from_str() is parsing the value as string but the destination struct expects to receive the type [u8;32].

Maybe this approach is completely wrong and I should use another strategy. Any idea?

Thank you


Solution

  • A possible solution is the deserialize_with field attribute. You must specify a custom function to perform deserialization:

        #[serde(deserialize_with = "deserialize_json_string")]
        data_value: CommonHash,
    

    And then implement it:

    fn deserialize_json_string<'de, D: de::Deserializer<'de>>(
        deserializer: D,
    ) -> Result<CommonHash, D::Error> {
        let s: &str = de::Deserialize::deserialize(deserializer)?;
        CommonHash::from_str(s).map_err(de::Error::custom)
    }
    

    Full code:

    use anyhow::{Context, Result};
    use serde::{de, Deserialize};
    
    #[derive(Deserialize, Debug)]
    struct CommonHash([u8; 32]);
    
    impl CommonHash {
        fn from_str(hex_str: &str) -> Result<Self> {
            let mut data: [u8; 32] = [0; 32];
            let str_stripped = hex_str
                .strip_prefix("0x")
                .context("error stripping the prefix 0x")?;
            hex::decode_to_slice(str_stripped, &mut data)?;
            Ok(CommonHash(data))
        }
    }
    
    fn deserialize_json_string<'de, D: de::Deserializer<'de>>(
        deserializer: D,
    ) -> Result<CommonHash, D::Error> {
        let s: &str = de::Deserialize::deserialize(deserializer)?;
        CommonHash::from_str(s).map_err(de::Error::custom)
    }
    
    fn deserialize_json_list<'de, D: de::Deserializer<'de>>(
        deserializer: D,
    ) -> Result<Vec<CommonHash>, D::Error> {
        let arr: Vec<&str> = de::Deserialize::deserialize(deserializer)?;
        arr.into_iter()
            .map(|s| CommonHash::from_str(&s))
            .collect::<Result<Vec<CommonHash>>>()
            .map_err(de::Error::custom)
    }
    
    #[derive(Deserialize, Debug)]
    #[serde(rename_all = "camelCase")]
    struct VectorData {
        #[serde(deserialize_with = "deserialize_json_list")]
        data_value_array: Vec<CommonHash>,
    
        #[serde(deserialize_with = "deserialize_json_string")]
        data_value: CommonHash,
    }
    
    fn main() {
        let file_content = r#"[
            {
                "dataValueArray": ["0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5"],
                "dataValue": "0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757"
            }
        ]"#;
        let json_file: Vec<VectorData> =
            serde_json::from_str(&file_content).expect("JSON was not well-formatted");
        println!("{:?}", json_file[0]);
    }