Clear omitted bindData fields when nullMissing is enabled - #15950
Clear omitted bindData fields when nullMissing is enabled#15950jamesfredley wants to merge 14 commits into
Conversation
When nullMissing is true and an include allowlist is provided, omitted allowlisted properties are set to null. Default remains leave-unchanged. Assisted-by: Sisyphus:xai/grok-4.5 [gpt-coding]
There was a problem hiding this comment.
Pull request overview
Adds opt-in “null missing” semantics to bindData so that, when nullMissing: true is provided alongside an explicit include allowlist, included properties omitted from the binding source are actively cleared (null) rather than leaving stale values on the target object. This extends Grails’ web data binding behavior to better support typical “edit/update” form semantics without enabling the behavior by default.
Changes:
- Introduces a
nullMissingoption plumbed throughDataBinder→DataBindingUtils, and applies clearing only when an explicitincludelist is provided. - Implements missing-field clearing logic in
DataBindingUtils(including nested indexed collection paths and map-indexed paths). - Adds test coverage and updates documentation + upgrading notes to describe the new opt-in behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| grails-web-databinding/src/main/groovy/grails/web/databinding/DataBindingUtils.java | Adds nullMissing overloads and implements missing included-property clearing logic after binding. |
| grails-web-databinding/src/main/groovy/grails/web/databinding/DataBinder.groovy | Wires nullMissing: true from the bindData options map into the binding call. |
| grails-test-suite-web/src/test/groovy/org/grails/web/servlet/BindDataMethodTests.groovy | Adds new controller-backed specs covering nullMissing clearing, excludes, nested indexed paths, map paths, and bindable whitelist interactions. |
| grails-doc/src/en/ref/Controllers/bindData.adoc | Documents nullMissing usage and constraints (opt-in; requires include). |
| grails-doc/src/en/guide/upgrading/upgrading80x.adoc | Notes the behavior change for Grails 8.x upgrades (opt-in; only with include). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
The observation regarding |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## fix/binddata-mass-assignment #15950 +/- ##
======================================================================
- Coverage 52.2735% 52.0565% -0.2169%
- Complexity 18383 18394 +11
======================================================================
Files 2036 2038 +2
Lines 96856 97300 +444
Branches 16961 17097 +136
======================================================================
+ Hits 50630 50651 +21
- Misses 38749 39170 +421
- Partials 7477 7479 +2
🚀 New features to boost your workflow:
|
Preserve the strict binding allowlist while adding opt-in stale-data clearing, including indexed paths and explicit include handling. Assisted-by: opencode:gpt-5.6-sol
|
Final update pushed in The branch now incorporates #15947 as its deny-by-default security base and keeps Verification passed for |
Assisted-by: opencode:gpt-5.6-sol
…le-data Assisted-by: opencode:gpt-5.6-sol
|
Thanks for splitting this out — since #15950 is branched directly off #15947, its diff currently duplicates all of #15947's changes on top of the Would you be open to one of:
Whichever is easier on your end — just flagging so we don't duplicate review effort. |
|
PR updated to target fix/binddata-mass-assignment |
jdaugherty
left a comment
There was a problem hiding this comment.
The nullMissing contract is well scoped — opt-in, requires an explicit include, defaults unchanged — and the reference docs read clearly.
Two structural concerns before this lands.
First, the clearing pass re-derives "which included properties were absent from the source" by re-parsing the binding source after binding has already finished. That is why DataBindingUtils grows roughly 640 lines of bespoke path handling, duplicating traversal the binder just performed and already has type information for.
Second, every nullMissing spec inherits legacyBindableDefault=false from the BindDataMethodTests setup(), so the feature has no coverage under the shipping default — even though the docs present it as a plain bindData option with no mention of the binding mode.
Smaller items inline.
…ssing-stale-data Preserve permissive compatibility binding and the nullMissing stale-data clearing behavior while resolving overlapping documentation, tests, and binding utilities. Assisted-by: opencode:gpt-5.6-sol
Use one shared property-name set across the core and web binders while preserving the internal bind-all marker across package boundaries. Assisted-by: opencode:gpt-5.6-sol
Move omitted-property path handling into a dedicated collaborator, preserve existing binding errors, report clear failures, and reset primitive properties to type defaults. Assisted-by: opencode:gpt-5.6-sol
Remove the duplicate specification cleanup that referenced per-feature state and prevented the merged test source from compiling. Assisted-by: opencode:gpt-5.6-sol
SimpleDataBinder must only hard-deny intrinsic runtime properties. Grails-managed id/version/dateCreated/lastUpdated/errors remain excluded from default allowlists and nullMissing clearing, but can still bind when explicitly allowed (bindable: true). Assisted-by: Sisyphus:xai/grok-4.5
|
@jdaugherty Addressed after merging the updated #15947 base ( Review responses
Verification
|
Bring the reviewed deny-by-default setting and listener fixes into the stacked branch. Preserve nullMissing behavior while documenting its public overloads and correcting the remaining Java style violations. Assisted-by: Sisyphus:gpt-5.6-sol [gpt-terra] [codex-review]
✅ All tests passed ✅🏷️ Commit: bcb05af Learn more about TestLens at testlens.app. |
Description
Adds opt-in stale-data clearing to
bindData, stacked on the binding base from #15947.When
nullMissing: trueis supplied together with an explicitincludelist, included properties omitted from the source are cleared (reference types tonull, primitives to their type default). Existing behavior is unchanged when the option is absent, false, or no explicit include was supplied.Final contract
bindData(target, source, [include: [...], nullMissing: true])bindable: false, and framework-managed properties remain protected0/false)BindingResultnullMissing; it cannot broaden normal request bindingnullMissing = falseImplementation notes
NullMissingPropertyClearer(not expanded public API).FrameworkPropertyNames(intrinsic runtime vs Grails-managed).bindable: falsealways honored.Example
Verification
:grails-web-databinding:test:grails-databinding-core:testBindDataMethodTests(default-mode and secure-mode nullMissing paths, primitives, BindingResult errors, nested/indexed/maps, excludes,bindable: false)DefaultASTDatabindingHelperDomainClassSpecialPropertiesSpec(explicit bindable special properties)Related
Contributor Checklist
bindDatareference and Grails 8 upgrade guideai-generated-starting-pointAssisted-by: Sisyphus:xai/grok-4.5 [gpt-coding]