ARR
ARR

Reputation: 35

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

Upvotes: 0

Views: 70

Answers (1)

aedm
aedm

Reputation: 6614

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]);
}

Upvotes: 1

Related Questions