I am using Serde JSON and would like to have functionality where if the supplied string that is being deserialized has a field absent, then I can use the value from data that is already stored in the application. I've been reading the Serde docs and I think what I'm looking for is DeserializeSeed but I'm having trouble figuring out how to use it.
Would someone be able to give me some guidance on how to implement this for the following example:
#[derive(Serialize, Deserialize)]
pub struct House {
location: String,
bedrooms: usize,
land_size: usize,
value: usize,
}
// String that has the "bedrooms" field missing
let house_string = r#"
{
"location": "Australia",
"land_size": 700,
"value": 500000
}"#;
// Seed data
let seed_house = House {
location: "Germany".to_string(),
bedrooms: 4,
land_size: 600,
value: 200_000,
};
// It is not clear to me what would be called to perform the deserialization
// or how to implement the DeserializeSeed
let deserialized: Record = serde_json::from_str(house_string).unwrap();
// Expected deserialized structure
let expected_result = House {
location: "Australia".to_string(),
bedrooms: 4,
land_size: 700,
value: 500_000,
};
There's an example on the page you linked. It looks like DeserializeSeed is a completely different trait to regular Deserialize, so you need to implement it manually yourself for (in this case) the House type. In this case, it should be pretty similar to the example given: provide the "default" House, then have the deserialize code overwrite the existing fields as they come in.
If serde_json doesn't have a convenience from_* function for the seeded variant, you'll also probably need to construct and invoke the deserializer manually.
I think this is the way to do it? I think you only need a Visitor if you need to hide the seed at some point, so that the seed is invisible to the outside. That's impossible when you want to use a application-provided seed (unless it's in global state like a static, file, or env variable).
struct HouseSeed<'a>(&'a House);
impl<'de, 'a> DeserializeSeed<'de> for HouseSeed<'a> {
type Value = House;
#[allow(clippy::clone_on_copy)]
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct HouseOpt {
#[serde(default)]
location: Option<String>,
#[serde(default)]
bedrooms: Option<usize>,
#[serde(default)]
land_size: Option<usize>,
#[serde(default)]
value: Option<usize>,
}
let HouseOpt {
location,
bedrooms,
land_size,
value,
} = HouseOpt::deserialize(deserializer)?;
let location = location.unwrap_or_else(|| self.0.location.clone());
let bedrooms = bedrooms.unwrap_or_else(|| self.0.bedrooms.clone());
let land_size = land_size.unwrap_or_else(|| self.0.land_size.clone());
let value = value.unwrap_or_else(|| self.0.value.clone());
Ok(House {
location,
bedrooms,
land_size,
value,
})
}
}
// ...
let deserialized: House = HouseSeed(&seed_house)
.deserialize(&mut serde_json::Deserializer::from_str(house_string))
.unwrap();
You have flexibility in how HouseSeed stores House. Here it's &House, so the fields get cloned when needed. This is efficient when you want to reuse HouseSeed. You could also store House and take needed fields, which is efficient when you want to use it once. Or you could store &mut House and either take only the fields that are needed, or just store the result directly into that House.
This would be extremely macro-able. I did a quick search but didn't find any crates that have such a macro.
Hi, sorry to bother people more, I have something working but I feel that my implementation could/should be a bit more generic or idiomatic or something around this part:
// Do some work to get the fields for the struct
let v: Value = serde_json::from_str(record_with_missing_field).unwrap();
let data_value = v.get("data").unwrap();
println!("data_value: {:?}", data_value);
let house_value = data_value.get("House").unwrap();
println!("house_value: {:?}", house_value);
How would I go about changing it so that instead of "digging into the data value" to get access to the House key, to instead try to deserialize the data key and passing it seed data which would be of type RecordData::House(House)?