Parsing JSON with error reporting - more concise form?

This works, but is rather verbose. Is there a better way to safely parse data structures from JSON?

pub fn json_to_profile_params_data(item: &json::JsonValue) -> Result<ProfileParamsData, Error> {
    Ok(ProfileParamsData{
        m_curve_type: item["m_curve_type"].as_u8().ok_or_else(|| anyhow!("Bad or missing m_curve_type in {:?}", item))?,
        m_begin: item["m_begin"].as_f32().ok_or_else(|| anyhow!("Bad or missing m_begin in {:?}", item))?,
        m_end: item["m_end"].as_f32().ok_or_else(|| anyhow!("Bad or missing m_end in {:?}", item))?,
        m_hollow: item["m_hollow"].as_f32().ok_or_else(|| anyhow!("Bad or missing m_hollow in {:?}", item))?,
    })
}

(Why JSON? Readability. This is for a debug mode.)

I would just add #[derive(Deserialize)] to ProfileParamsData.

use anyhow::Error;
use serde::Deserialize;
use serde_json::Value;

#[derive(serde::Deserialize)]
pub struct ProfileParamsData {
    m_curve_type: u8,
    m_begin: f32,
    m_end: f32,
    m_hollow: f32,
}

pub fn json_to_profile_params_data(item: &Value) -> Result<ProfileParamsData, Error> {
    ProfileParamsData::deserialize(item).map_err(Error::from)
}

The code generated by #[derive(Deserialize)] is already quite robust and handles malformed input very well. The declarative code you get is also a lot easier to read than any imperative solution.

If you don't want to add the annotation to ProfileParamsData directly (e.g. because it has other fields or complex invariants), a trick I've often done is to define an intermediate struct inside the json_to_profile_params_data() function that gets #[derive(Deserialize)] and convert that into a ProfileParamsData.

1 Like

Don't use serde_json::Value if you have some expected structure of the JSON data. In most cases it makes your code verbose, more error prone and less performant - yes, all three.

2 Likes

I'm coming around to that, after several hours of converting from "json" to "serde_json". The two crates are not quite compatible.

I'm coming around to that, after several hours of converting from "json" to "serde_json". The two crates are not quite compatible. I finally went back. I was going to save about 20 lines of code at the cost of changing several hundred.

Yeah, they have quite different approaches. The json provides pretty common interface like the one in JS and python - a Json type which is a mixture of null, number, string, bool, and array/dictionary of itselves. The serde_json have interface similar to the Gson(haven't seen similar lib in C++ utilizing RTTI) but the runtime operation similar to the RapidJSON, both are based on the serde framework.

Performance isn't a big issue for this JSON. It's a tool to allow replay for debugging. So, I'll stay with the wordy but working form for now.

1 Like

Just realized I have to convert, because another part of the program reads and processes a huge JSON file (gigabytes, it's a debug capture) with serde-json, so it can process while reading.

Bleah.

"There's more than one way to do it".