I wrote a deserialization for my structures and found an asymmetry in the parsing implementation from &str and serde_json::Value. Because of this, for a long time I could not understand why my structure is not deserialized. I got an error: data did not match any variant of untagged enum. Here is a simplified example of this, giving a different error:
use serde::Deserialize;
#[derive(Debug, Deserialize, PartialEq)]
#[serde(try_from = "&str")]
enum Variant {
A,
B,
}
impl<'a> TryFrom<&'a str> for Variant {
type Error = &'a str;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
match value {
"a" => Ok(Self::A),
"b" => Ok(Self::B),
_ => Err(value),
}
}
}
fn main() {
let var: Variant = serde_json::from_str("\"a\"").unwrap(); // Works fine
assert_eq!(var, Variant::A);
let var: Variant = serde_json::from_value(serde_json::json!("a")).unwrap(); // Error
assert_eq!(var, Variant::A);
}
I write TryFrom<&str> here because it is a more general implementation than TryFrom<String>.
Looks like try_from = &str means that serde will deserialize into &str, which can only works with zero-copy/borrowing deserialization (it borrows directly from the input string). And serde_json::from_valuerequires DeserializeOwned, so they're kinda incompatible. I wonder if it'd be possible to return nicer compile time error like Variant doesn't impl DeserializeOwned instead of runtime error.
Anyway, what you can do to support both cases efficiently, is to try Cow<str> instead of &str. I've never used try_from attribute before, but it should work.
Anyway, I think you've simplified your example a bit too much.. can you share the full code? There might be a simpler fix there.
You can deserialize from a Value while also borrowing, if you change your code to let var = Variant::deserialize(&serde_json::json!("a")).unwrap();.
To fix the actual problem you need to avoid creating a &str in the first place. The smaller change is using #[serde(try_from = "String")]. Cow<str> should work too, but it always creates an owned version, so using String is simpler.
The proper way for 0-copy deserialization requires a Visitor somewhere, for example by implementing Deserialize.
impl<'de> Deserialize<'de> for Variant2 {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct Helper;
impl<'de> Visitor<'de> for Helper {
type Value = Variant2;
fn expecting(&self, _: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
todo!()
}
fn visit_str<E: Error>(self, s: &str) -> Result<Self::Value, E> {
match s {
"a" => Ok(Variant2::A),
"b" => Ok(Variant2::B),
_ => Err(E::unknown_variant(s, &["a", "b"])),
}
}
}
deserializer.deserialize_str(Helper)
}
}
If you are willing to rely on other crates, you can also use a different derive (e.g., serde_with::DeserializeFromStr) and use FromStr instead of TryFrom<&str>.
Yeah, I was a little bit imprecise, sorry. What I meant it does indeed implement DeserializeOwned, but it only supports visit_borrowed_str (and not visit_str or visit_string), which makes it unusable in DeserializeOwned scenarios.
Thanks for the help, now it's working!
I wrote the implementation manually. Still, I would like to just work with derive. I hope serde can be improved in this way.