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.
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.
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.
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.