Open Swivelgames opened 1 year ago
I just found myself in the same need (and implementing Deserialize by hand). I'd prefer if the fallback name was not an explicit field of the struct though, but rather something handled internally by serde.
Other alternative I thought would work but doesn't is just being able to provide the same alias to different fields. I don't know if it's better to have a separate mechanism, maybe it helps give priority to aliases over fallbacks.
@jmi2k
I just found myself in the same need (and implementing Deserialize by hand). I'd prefer if the fallback name was not an explicit field of the struct though, but rather something handled internally by serde.
This may already be possible, depending on your use case, with Default
:
#[derive(Debug, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct MyObnoxiousDataset {
pub some_value: String,
pub some_other_value: String,
}
impl Default for MyObnoxiousDataset {
fn default() -> Self {
Self {
some_value: "some field's value".to_owned(),
some_other_value: "the other field's default value".to_owned(),
}
}
}
[!IMPORTANT] It's important to note that a Default will be constructed every time Serde deserializes, regardless of whether or not a default value is used.
The struggle is that there is currently no real way to have the Default value for a field fallback to another field in the JSON structure that's being deserialized.
Other alternative I thought would work but doesn't is just being able to provide the same alias to different fields.
That was kind of the idea behind fallback
. It might not be the best name, but effectively it would operate in the same way, while providing multiple fields to fallback on, without changing the functionality / backwards compatibility with alias
.
Not to shoot my own proposal in the foot (I'd love for the implementation to take this into account), but: One of the scenarios that this may or may not be able to take into account (which may actually be the more common use-case) is falling back to the value of an ancestor's or child's field. That probably won't be possible, even for this.
One workaround for all of this would be to have an interface for retrieving the values you need from your series of structs. That adds a bit of redundancy but leaves the deserialization to serde.
That might look like (mind the pseudo code):
struct cfg {
pub sometree: OtherStruct,
pub tree_two: SecondStruct,
}
struct OtherStruct {
pub my_field: Option<String>,
}
struct SecondStruct {
pub fallback_field: String,
}
pub fn get_my_field(): String {
match CONFIG.sometree.my_field {
Some(v) => v,
None => CONFIG.tree_two.fallback_field,
}
}
Again... kind of defeats half of the purpose of serde. But at least the half that you don't have to write is the deserialization, and instead the interface for your structs.
[!WARNING] But, this may introduce performance issues, because you're moving this sort of logic from when the deserialization occurs to every time the field is read. That being said, if you're only reading that particular field once, or infrequently, then that may not matter.
Not to shoot my own proposal in the foot (I'd love for the implementation to take this into account), but: One of the scenarios that this may or may not be able to take into account (which may actually be the more common use-case) is falling back to the value of an ancestor's or child's field. That probably won't be possible, even for this.
This would actually be nice, but I think it doesn't necessarily undermines the usefulness of having a fallback field.
One workaround for all of this would be to have an interface for retrieving the values you need from your series of structs. That adds a bit of redundancy but leaves the deserialization to serde.
This is my current approach for other structs: A RawT
for deserializing, which I refine into a T
. Still, it would be nice to have a better alternative for this case in particular.
I'm in a bit of a conundrum. I have a pretty nasty set of data coming from an outside source that includes fields that should inherit other field's values if they're not present in the original data. I have quite a few fields like this.
Something like
#[serde(fallback = "name")]
would make life so much easier in this instance.Effectively, this would be similar to
#[serde(alias = "name")]
(similarly allowing for multiple fallbacks, preferably), but rather than completely remapping the field, it would allow the field to be deserialized into multiple fields as needed (including its original Rust name field).