Search code examples
rustserdeserde-json

Is it possible to to handle invalid JSON values globally using Serde / Rust?


What I mean by that is the following:

Given this struct:

struct SomeObj {
  someProp: bool
}

Is there a way to force Serde to assign true / false to someProp when its value is NOT boolean in the JSON input?

Normally, serde would just return an error in such cases (invalid type, expected XY..etc). But would it be possible to suppress the error and just provide some meaningful default value instead?

I know this is possible doing something like:

#[serde(from = "serde_json::Value")]
struct SomeObj {
  someProp: bool
}

impl From<serde_json::Value> for SomeObj {
...
}

But there there are a huge number of complex and large structs I need to deal with, so handling this globally would be much easier.


Solution

  • You can always override serde's choice of deserializer with the attribute deserialize_with. This attribute accepts a function that will be used to deserialize, in place of the usual Deserialize trait.

    #[derive(Serialize, Deserialize)]
    struct SomeObj {
      #[serde(deserialize_with = "deserialize_bool_or_false")]
      someProp: bool
    }
    
    fn deserialize_bool_or_false<'de, D>(deserializer: D) -> Result<bool, D::Error>
    where D: Deserializer<'de> {
      let deserialized = bool::deserialize(deserializer);
      // deserialized is a Result<bool, ...>. You can handle the error
      // case however you like. This example simply returns false.
      Ok(deserialized.unwrap_or(false))
    }
    

    You just need to attach that deserialize_with attribute to every field that you want the custom defaulting behavior on.

    If you're willing to pull in an extra dependency, the serde_with library has DefaultOnError, which returns the Default::default() value for a type (that's false, "", 0, 0.0, etc.) if any deserialization error occurs on that field. So with that structure, you can mark any field as #[serde_as(deserialize_as = "DefaultOnError")] and, provided the underlying type implements the trait Default, it'll just work.

    #[serde_as]
    #[derive(Serialize, Deserialize)]
    struct SomeObj {
      #[serde_as(deserialize_as = "DefaultOnError")]
      someProp: bool
    }
    

    This will work on bool, any signed or unsigned integer type, floating-point types, strings, and anything that satisfies Default.