diff --git a/schemars/src/_private/mod.rs b/schemars/src/_private/mod.rs index 1e4f768c..3989f496 100644 --- a/schemars/src/_private/mod.rs +++ b/schemars/src/_private/mod.rs @@ -134,41 +134,30 @@ pub fn apply_internal_enum_variant_tag( } } -pub fn insert_object_property( +pub fn insert_object_property( schema: &mut Schema, key: &str, - may_be_omitted: bool, - required_attr: bool, + is_optional: bool, sub_schema: Schema, ) { - fn insert_object_property_impl( - schema: &mut Schema, - key: &str, - required: bool, - sub_schema: Schema, - ) { - let obj = schema.ensure_object(); - if let Some(properties) = obj - .entry("properties") - .or_insert(Value::Object(Map::new())) - .as_object_mut() - { - properties.insert(key.to_owned(), sub_schema.into()); - } + let obj = schema.ensure_object(); + if let Some(properties) = obj + .entry("properties") + .or_insert(Value::Object(Map::new())) + .as_object_mut() + { + properties.insert(key.to_owned(), sub_schema.into()); + } - if required { - if let Some(req) = obj - .entry("required") - .or_insert(Value::Array(Vec::new())) - .as_array_mut() - { - req.push(key.into()); - } + if !is_optional { + if let Some(req) = obj + .entry("required") + .or_insert(Value::Array(Vec::new())) + .as_array_mut() + { + req.push(key.into()); } } - - let required = !may_be_omitted && (required_attr || !T::_schemars_private_is_option()); - insert_object_property_impl(schema, key, required, sub_schema); } pub fn insert_metadata_property(schema: &mut Schema, key: &str, value: impl Into) { diff --git a/schemars/tests/contract.rs b/schemars/tests/contract.rs index 25bbb549..bd5160c0 100644 --- a/schemars/tests/contract.rs +++ b/schemars/tests/contract.rs @@ -16,6 +16,7 @@ struct MyStruct { skip_serializing_if: bool, #[schemars(rename(serialize = "ser_renamed", deserialize = "de_renamed"))] renamed: bool, + option: Option, } #[test] diff --git a/schemars/tests/expected/contract_deserialize.json b/schemars/tests/expected/contract_deserialize.json index b8f6680c..6b24d01d 100644 --- a/schemars/tests/expected/contract_deserialize.json +++ b/schemars/tests/expected/contract_deserialize.json @@ -16,6 +16,12 @@ }, "de_renamed": { "type": "boolean" + }, + "option": { + "type": [ + "boolean", + "null" + ] } }, "required": [ diff --git a/schemars/tests/expected/contract_serialize.json b/schemars/tests/expected/contract_serialize.json index 5d50b371..b82231fc 100644 --- a/schemars/tests/expected/contract_serialize.json +++ b/schemars/tests/expected/contract_serialize.json @@ -17,11 +17,18 @@ }, "ser_renamed": { "type": "boolean" + }, + "OPTION": { + "type": [ + "boolean", + "null" + ] } }, "required": [ "READ-ONLY", "DEFAULT", - "ser_renamed" + "ser_renamed", + "OPTION" ] } \ No newline at end of file diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index 91d4f61d..0b30e2e6 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -509,16 +509,17 @@ fn expr_for_struct( let has_default = set_container_default.is_some() || !field.serde_attrs.default().is_none(); let has_skip_serialize_if = field.serde_attrs.skip_serializing_if().is_some(); - let may_be_omitted = if has_default == has_skip_serialize_if { - has_default.into_token_stream() + let required_attr = field.attrs.validation.required; + + let is_optional = if has_skip_serialize_if && has_default { + quote!(true) } else { quote!(if #GENERATOR.contract().is_serialize() { #has_skip_serialize_if } else { - #has_default + #has_default || (!#required_attr && <#ty as schemars::JsonSchema>::_schemars_private_is_option()) }) }; - let required_attr = field.attrs.validation.required; let mut schema_expr = SchemaExpr::from(if field.attrs.validation.required { quote_spanned! {ty.span()=> @@ -538,11 +539,11 @@ fn expr_for_struct( }) } - // embed `#type_def` outside of `#schema_expr`, because it's used as the type param - // (i.e. `#type_def` is the definition of `#ty`) + // embed `#type_def` outside of `#schema_expr`, because it's used as a type param + // in `#is_optional` (`#type_def` is the definition of `#ty`) field.with_contract_check(quote!({ #type_def - schemars::_private::insert_object_property::<#ty>(&mut #SCHEMA, #name, #may_be_omitted, #required_attr, #schema_expr); + schemars::_private::insert_object_property(&mut #SCHEMA, #name, #is_optional, #schema_expr); })) } })