Skip to content

feat(renderer)!: add Serde omission step options - #326

Merged
Bergmann89 merged 1 commit into
Bergmann89:masterfrom
sundermann:feat/serde-omission-rules
Nov 12, 2026
Merged

Bergmann89 merged 1 commit into
Bergmann89:masterfrom
sundermann:feat/serde-omission-rules

Conversation

@sundermann

@sundermann sundermann commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Adds independent, opt-in omission of None fields and empty vectors during Serde serialization.

Changes

  • Add skip_none and skip_empty_vecs to the Serde type render steps, both disabled by default. Plain types and direct quick-xml generation remain unaffected.
  • Add with_serde_quick_xml_config and with_serde_xml_rs_config convenience methods. Existing helpers delegate to them with all options disabled.
  • Collect adapters, defaults, skip predicates, and renames into one generated #[serde(...)] attribute per field. User-supplied extra attributes remain separate; explicit codecs, defaults, and skip predicates take precedence. Share schema-default method naming between backends.
  • Share field-shape and typedef resolution between array adapters and omission rules, borrowing the original shape without allocation when there is no typedef to resolve. Preserve tuple and enum payloads, custom codecs, and custom defaults.
  • Add defaults when omitting empty vector aliases, including required aliases and required list attributes, so omitted empty values round-trip correctly.
  • Keep generated schema-default state separate from user-supplied defaults. Use a scalar omission shape for the enum-values field emitted by serde-xml-rs v0.8, with a compiled regression covering optional and list metadata.
  • Consolidate omission snapshots and share pipeline setup across all omission and large-array tests. Preserve the plain-type and direct quick-xml isolation check, and compare equivalent configured and direct render pipelines using both array and schema-default fixtures. Cover independent option selection, empty and populated JSON values, required aliases, array adapters, wrapper boundaries, and XML compatibility. Disabled baselines cover every XML backend: they demonstrate a skip_none output change for quick-xml and serde-xml-rs v0.7, and unchanged native omission for the tested v0.8 message.
  • Extend the existing serde_large_arrays example to demonstrate array adapters and omission options in one model, using the new configuration helper. Omission requires only Serde; large-array adapters additionally require serde_with.

Behavior change with omission disabled: explicit field-level #[serde(default ...)] attributes now take precedence over generated backend defaults. Previously, both defaults could be emitted, causing duplicate Serde attribute errors. This also applies when neither omission option is enabled.

Breaking configuration changes: RenderStep::TypesSerdeQuickXml and RenderStep::TypesSerdeXmlRs gain required skip_none and skip_empty_vecs fields. Set both to false to leave omission disabled. Concrete Serde steps gain the same fields; their Default implementations and existing configuration helpers keep omission disabled.

Custom default functions retain empty values to prevent substitution with a different default. Custom deserializers require a plain field default before automatic omission is applied. Conditional cfg_attr overrides are not evaluated; select unconditional field attributes during generation instead. Struct-level custom defaults require explicit field defaults. The example README documents these limitations.

Checklist

  • Tests for the change (a bug fix has a test that fails without the fix)
  • Changed snapshots are regenerated with update-expectations and reviewed
  • cargo fmt, cargo clippy and cargo test pass (see CONTRIBUTING.md)
  • Breaking changes of the public API or the generated code are described above

@sundermann sundermann changed the title Feat/serde omission rules feat(renderer)!: add Serde omission step options Nov 12, 2026
@sundermann
sundermann force-pushed the feat/serde-omission-rules branch from 8532385 to 83fb4f0 Compare November 12, 2026 20:31

@Bergmann89 Bergmann89 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Being able to omit None values and empty vectors is a nice addition, and having it as opt-in options on the serde render steps is the right place for it.

Please also add with_serde_quick_xml_config / with_serde_xml_rs_config helpers, similar to with_quick_xml_deserialize_config, and let the existing helpers delegate to them (see the comment below). Otherwise users have to build the render steps by hand as soon as they want to enable one of the options.

Besides one correctness issue with Vec aliases, most of my remarks are about the structure of the serde attribute rendering: every field should get a single generated #[serde(...)] attribute, and the logic that builds it is duplicated in a lot of places. I suggest a small helper type for this, see the comment in steps/serde/mod.rs. A few smaller things that I did not add inline:

  • DynamicData::render_type_serde_* takes an _options: SerdeOptions parameter in all three backends that is never used. Please remove it.
  • use super::{field_overrides, SerdeOptions}; sits above the crate:: imports in the three backends, and there is already a use super::{get_derive, get_dyn_type_traits};. Please merge them.
  • let default = if has_default { quote!(default,) } else { quote!() }; can simply be let default = has_default.then(|| quote!(default,));.
  • The helpers in large_arrays.rs as well as SerdeOptions and FieldOverrides are only used inside steps/serde, so pub(super) is enough instead of pub(crate).
  • In the workspace Cargo.toml serde-xml-rs should come before serde_json / serde_with, like in xsd-parser/Cargo.toml.
  • tests/feature/serde_large_arrays/mod.rs imports RenderStep again inside the first test, and several tests call .with_derive(["Debug", "PartialEq"]) although config() already sets it. The 3-line comment in wrappers.rs:87-89 can be one line.
  • A user-supplied serde(default ...) now suppresses the generated default instead of producing a duplicate attribute. That's a behaviour change for users that do not use the new options, please mention it in the PR description.
  • The snapshots none.rs, vecs.rs, both.rs, serde_xml_rs_v7.rs and serde_xml_rs_v8.rs differ only in a few lines, and the three schema_default_* snapshots pin the same negative case. disabled + both + one snapshot per backend is enough, the runtime test already covers that both options work independently.
  • The parse/interpret/optimize/generate/render pipeline is copied three times in the tests (serde_skip/mod.rs, aliases.rs, schema_defaults.rs). Please move it into one helper in tests/utils.rs.
  • The XML checks in serde_skip/mod.rs:340-382 probably pass without the options as well, because the XML backends do not write None or empty sequences anyway. Either add a disabled row or do not claim omission for XML in the PR description.
  • round_trip_json asserts inside a helper, so failures point to the helper. Please return the decoded value and assert at the call site.

Comment thread xsd-parser/src/config/mod.rs Resolved
Comment thread examples/serde_large_arrays/README.md Resolved
@sundermann
sundermann force-pushed the feat/serde-omission-rules branch 2 times, most recently from f3944ba to 2175b39 Compare November 12, 2026 21:58
Add opt-in skip_none and skip_empty_vecs options to the Serde type steps
and configurable Serde helpers while leaving both options disabled by
default. Keep plain type and direct quick-xml rendering unaffected.

Collect each field's generated adapter, default, skip predicate and rename
into one Serde attribute. Resolve typedef shapes once for array adaptation
and omission, preserve explicit field policies, and default empty vector
aliases so omitted values can be deserialized again.

Let explicit field defaults replace backend defaults, including when
omission is disabled. Cover required vector aliases and list attributes,
custom codecs and defaults, boxed fields, payloads and XML wrappers.
Share test pipeline setup and default-method naming, consolidate omission
fixtures, and preserve plain and direct quick-xml isolation checks. Compare
equivalent render pipelines using array and schema-default fixtures.
Demonstrate the configuration helpers in the array example.
Keep generated schema defaults separate from user overrides, avoid alias
resolution allocations for plain fields, and treat v8 enum content as a
scalar when selecting omission predicates.

BREAKING CHANGE: TypesSerdeQuickXml and TypesSerdeXmlRs require skip_none
and skip_empty_vecs fields. Concrete Serde steps gain the same fields;
their Default implementations and existing configuration helpers keep
omission disabled.
@sundermann
sundermann force-pushed the feat/serde-omission-rules branch from 2175b39 to 96c4cfc Compare November 12, 2026 22:15
@sundermann

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review, points should be adressed now

@Bergmann89
Bergmann89 merged commit 18ffae5 into Bergmann89:master Nov 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants