schemars icon indicating copy to clipboard operation
schemars copied to clipboard

Deriving JsonSchema seems to break serde's "with" field annotation

Open joel-wright opened this issue 4 years ago • 10 comments

I have been using #[derive(JsonSchema)], but recently ran into issues attempting to use serde's field attributes and the serde_with crate in order to report an error on duplicate keys in a mapping.

When I add the field attribute to the struct:

#[derive(JsonSchema, Serialize, Deserialize, Debug, PartialEq)]
#[serde(deny_unknown_fields)]
pub(crate) struct MyStruct {
    ...
    #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
    pub mapping: BTreeMap<String, String>,
    ...
}

I get the following compilation error:

error[E0573]: expected type, found module `serde_with::rust::maps_duplicate_key_is_error`
  --> data/structs.rs:24:20
   |
24 |     #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not a type

error: aborting due to previous error

If I remove JsonSchema from the list of derives it seems to work fine.

joel-wright avatar Jul 12 '21 13:07 joel-wright

I ran into the same problem with a custom module, when I add JsonSchema to derive #[serde(with = ...)] suddenly expects a type instead of a module. Schemars version is 0.8.3, serde is 1.0.126.

Edit: I dug around a little and it seems like in schemars_derive::attr line 87, a #[serde(with="literal")] will only be parsed to a WithAttr::Type. Maybe I am not understanding enough of what is going on but since it coincides with rust expecting a type I thought I'd mention it.

Edit2: I realized now that it would be very hard to generate a JsonSchema for the custom (de)serialization. But the way it stops working looks like a bug not like an incapability.

Haifischbecken avatar Aug 04 '21 14:08 Haifischbecken

I also have trouble with remotes for some specific data structures, seems to have to do with some resolution order. And I also have a recursive enum that breaks schema generation with a stackoverflow :)

Igosuki avatar Sep 14 '21 19:09 Igosuki

I seem to have found a workaround.

  #[derive(Debug, Insertable, Deserialize, JsonSchema)]
  #[serde(crate = "rocket::serde")]
  #[table_name = "contents"]
  pub struct InsertableContent {
      pub title: String,
      pub story: String,
      #[serde(deserialize_with = "chrono::serde::ts_seconds::deserialize")]
      #[serde(serialize_with = "chrono::serde::ts_seconds::serialize")]
      pub published: DateTime<chrono::Utc>,
      pub user_id: i32,
  }

If you break the #[serde(with = "module") to separate serialize and deserialize functions it seems to compile fine.

uttarayan21 avatar Oct 04 '21 18:10 uttarayan21

@uttarayan21 sounds like something should get patched in the code though

Igosuki avatar Oct 05 '21 08:10 Igosuki

Definitely. I just added it here so that people can compile in the meantime.

uttarayan21 avatar Oct 05 '21 10:10 uttarayan21

I came across this today as well and while I think the error when this happens should (and easily can) be better and point to the way things should be done, but there is a straightforward way to use with and this might even be the intended way.

For the original example the following would be the solution

#[derive(JsonSchema, Serialize, Deserialize, Debug, PartialEq)]
#[serde(deny_unknown_fields)]
pub(crate) struct MyStruct {
    ...
    #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
    #[schemars(with = BTreeMap<String, String>)]
    pub mapping: BTreeMap<String, String>,
    ...
}

or ideally a type that more specifically does the "no duplicate keys validation" and records that in the schema.

Generally the allowed attributes and their semantics need to be better documented, like they are for serde since I also found it hard to figure out what is and what is not possible.

In my code I've settled on the pattern

#[derive(JsonSchema, Serialize, Deserialize, Debug, PartialEq)]
pub(crate) struct MyStruct {
    #[serde(with = "foo")]
    #[schemars(with = MyStructSchema)]
    pub mapping: SomeType,
}

impl JsonSchema for MyStructSchema {
  ...
}

in the cases where I needed custom and precise schemas.

abizjak avatar Apr 18 '22 19:04 abizjak

I can confirm the bug and the workaround by @uttarayan21 🙏

wackazong avatar Mar 09 '23 21:03 wackazong