Clear omitted bindData fields when nullMissing is enabled#15950
Clear omitted bindData fields when nullMissing is enabled#15950jamesfredley wants to merge 1 commit 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.
| private static boolean isPropertyAllowedByWhitelist(Object object, String propertyName) { | ||
| List bindingIncludeList = getBindingIncludeList(object); | ||
| if (bindingIncludeList == null || bindingIncludeList.isEmpty()) { | ||
| return true; | ||
| } | ||
| for (Object includedProperty : bindingIncludeList) { | ||
| if (includedProperty instanceof CharSequence) { | ||
| String includedPropertyName = includedProperty.toString(); | ||
| if (propertyName.equals(includedPropertyName) || includedPropertyName.startsWith(propertyName + ".")) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| } |
|
The observation regarding |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## 8.0.x #15950 +/- ##
==================================================
- Coverage 49.3625% 49.2009% -0.1616%
- Complexity 16777 16814 +37
==================================================
Files 1986 1993 +7
Lines 93336 93854 +518
Branches 16337 16467 +130
==================================================
+ Hits 46073 46177 +104
- Misses 40134 40541 +407
- Partials 7129 7136 +7
🚀 New features to boost your workflow:
|
✅ All tests passed ✅🏷️ Commit: 6309f18 Learn more about TestLens at testlens.app. |
| if (indexedProperty instanceof List) { | ||
| List list = (List) indexedProperty; | ||
| Integer parsedIndex = parseIndex(index); | ||
| return parsedIndex != null && parsedIndex >= 0 && parsedIndex < list.size() ? list.get(parsedIndex) : null; |
There was a problem hiding this comment.
This PR appears to be doing more than what it says it does. It appears to have overlap with the other changes.
Description
What was found
When updating an existing object through
bindData, omitted form fields leave the previous persisted value in place. That is a separate concern from mass assignment:secureBindDataAPIbindDataand splitting concernsWhat changed
bindData(target, source, [include: [...], nullMissing: true])nullMissingis true and an include allowlist is provided, allowlisted properties absent from the source are set tonullnullMissingis absent/falsebindDatareference, upgrade notes, and focused binding testsExplicitly out of scope
@BindAllowednullMissingby defaultExample
Verification
BindDataMethodTestscoverage for nullMissing on/off and allowlist interaction.Related
Contributor Checklist
Issue and Scope
8.0.x, where behavior-hardening changes are permitted for discussion.Code Quality
Licensing and Attribution
ai-generated-starting-point).Documentation
nullMissingbehavior is documented.Assisted-by: Sisyphus:xai/grok-4.5 [gpt-coding]