[
https://issues.apache.org/jira/browse/GROOVY-12352?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Paul King updated GROOVY-12352:
-------------------------------
Description:
{{JsonSlurper.setCheckDates(boolean)}} decides whether a string that looks like
a date is returned as a {{java.util.Date}}. A boolean can only pick between two
of the three answers a caller might want, and the one it can reach is the one
fewest callers would choose today.
h3. Behaviour now
Parsing {{{"when":"2026-09-04T10:00:00.000Z"}}}:
|| parser type || checkDates=true || checkDates=false ||
| INDEX_OVERLAY | {{Date}} | {{String}} |
| LAX | {{Date}} | {{String}} |
| CHARACTER_SOURCE | {{String}} | {{String}} |
| CHAR_BUFFER | {{String}} | {{String}} |
The default is {{true}}. Only full ISO-8601 and JSON-date forms convert; a bare
{{"2026-09-04"}} stays a {{String}}.
Two things are wrong beyond the missing option:
* The javadoc on both accessors says "Index overlay only". LAX converts as
well, and the flag has no effect at all on the other two, which have no date
path — {{JsonParserCharArray}} and {{JsonParserUsingCharacterSource}} contain
no reference to {{Dates}}.
* {{java.util.Date}} is an instant, so an offset in the source is discarded.
{{"2026-09-04T10:00:00+10:00"}} and its UTC equivalent become indistinguishable.
Neither is discoverable: the option is absent from the user guide, so it is
reachable only by reading {{JsonSlurper}}.
h3. Proposal
Replace the boolean with an option naming the type to produce, leaving the
default as it is so that nothing changes for an existing caller:
{code:java}
public enum JsonDateHandling { STRING, UTIL_DATE, INSTANT, OFFSET_DATE_TIME }
{code}
{{OFFSET_DATE_TIME}} earns its place beside {{INSTANT}} by being the one that
keeps the offset.
The old accessors delegate and are deprecated, so this is additive:
{code:java}
@Deprecated
public JsonSlurper setCheckDates(boolean checkDates) {
return setDateHandling(checkDates ? UTIL_DATE : STRING);
}
@Deprecated
public boolean isCheckDates() {
return dateHandling != STRING;
}
{code}
h3. Implementation
The conversion is one site, the {{case STRING:}} block of
{{CharSequenceValue.toValue()}}, reached by a {{checkDate}} boolean threaded
from {{JsonSlurper}} through {{JsonFastParser}} and {{JsonParserLax}}. That
parameter becomes the enum and the block chooses its target.
{{Dates}} scans the fields by hand and assembles a {{Date}} through
{{Calendar}} with a UTC zone. INDEX_OVERLAY exists to be fast, so keep that
scan and build the chosen type from the fields it already extracts, rather than
handing the text to {{OffsetDateTime.parse}}.
h3. Decisions this needs
* *Scope.* The option covers the two parsers that convert. Extending it to the
other two means giving them a date path they have never had, which changes
their current always-{{String}} result. Leaving it at two is the smaller change
and lets the threat model describe what is true.
* *The default.* Nothing here requires moving it. Whether {{UTIL_DATE}} should
eventually give way to {{INSTANT}} or {{STRING}} is a separate question about a
default that has stood since 2.3, and is better decided on its own.
h3. Compatibility
{{CharSequenceValue}}'s constructor is public but sits in
{{org.apache.groovy.json.internal}} and has no caller outside that package in
this repository. Changing the parameter is defensible; an overload alongside
the existing one costs little if the package name is not thought to be enough.
Additive with the default unchanged, so it suits 6.x. Only a change of default
would need to wait.
h3. Related
Raised by the 2026-09-01 security scan as f013, and by tmd-5 against §8 P3 of
{{THREAT_MODEL.md}}, which asserts the slurpers return only
Map/List/String/Number/Boolean/null. The no-gadget-instantiation guarantee is
unaffected: {{Date}} is built from a validated lexical form, not from a class
named in the document. What P3 has wrong is the list of types, and it should be
corrected whatever is decided here.
> improve consistency of JSON date parsing
> ----------------------------------------
>
> Key: GROOVY-12352
> URL: https://issues.apache.org/jira/browse/GROOVY-12352
> Project: Groovy
> Issue Type: Improvement
> Reporter: Paul King
> Assignee: Paul King
> Priority: Major
> Labels: breaking
>
> {{JsonSlurper.setCheckDates(boolean)}} decides whether a string that looks
> like a date is returned as a {{java.util.Date}}. A boolean can only pick
> between two of the three answers a caller might want, and the one it can
> reach is the one fewest callers would choose today.
> h3. Behaviour now
> Parsing {{{"when":"2026-09-04T10:00:00.000Z"}}}:
> || parser type || checkDates=true || checkDates=false ||
> | INDEX_OVERLAY | {{Date}} | {{String}} |
> | LAX | {{Date}} | {{String}} |
> | CHARACTER_SOURCE | {{String}} | {{String}} |
> | CHAR_BUFFER | {{String}} | {{String}} |
> The default is {{true}}. Only full ISO-8601 and JSON-date forms convert; a
> bare {{"2026-09-04"}} stays a {{String}}.
> Two things are wrong beyond the missing option:
> * The javadoc on both accessors says "Index overlay only". LAX converts as
> well, and the flag has no effect at all on the other two, which have no date
> path — {{JsonParserCharArray}} and {{JsonParserUsingCharacterSource}} contain
> no reference to {{Dates}}.
> * {{java.util.Date}} is an instant, so an offset in the source is discarded.
> {{"2026-09-04T10:00:00+10:00"}} and its UTC equivalent become
> indistinguishable.
> Neither is discoverable: the option is absent from the user guide, so it is
> reachable only by reading {{JsonSlurper}}.
> h3. Proposal
> Replace the boolean with an option naming the type to produce, leaving the
> default as it is so that nothing changes for an existing caller:
> {code:java}
> public enum JsonDateHandling { STRING, UTIL_DATE, INSTANT, OFFSET_DATE_TIME }
> {code}
> {{OFFSET_DATE_TIME}} earns its place beside {{INSTANT}} by being the one that
> keeps the offset.
> The old accessors delegate and are deprecated, so this is additive:
> {code:java}
> @Deprecated
> public JsonSlurper setCheckDates(boolean checkDates) {
> return setDateHandling(checkDates ? UTIL_DATE : STRING);
> }
> @Deprecated
> public boolean isCheckDates() {
> return dateHandling != STRING;
> }
> {code}
> h3. Implementation
> The conversion is one site, the {{case STRING:}} block of
> {{CharSequenceValue.toValue()}}, reached by a {{checkDate}} boolean threaded
> from {{JsonSlurper}} through {{JsonFastParser}} and {{JsonParserLax}}. That
> parameter becomes the enum and the block chooses its target.
> {{Dates}} scans the fields by hand and assembles a {{Date}} through
> {{Calendar}} with a UTC zone. INDEX_OVERLAY exists to be fast, so keep that
> scan and build the chosen type from the fields it already extracts, rather
> than handing the text to {{OffsetDateTime.parse}}.
> h3. Decisions this needs
> * *Scope.* The option covers the two parsers that convert. Extending it to
> the other two means giving them a date path they have never had, which
> changes their current always-{{String}} result. Leaving it at two is the
> smaller change and lets the threat model describe what is true.
> * *The default.* Nothing here requires moving it. Whether {{UTIL_DATE}}
> should eventually give way to {{INSTANT}} or {{STRING}} is a separate
> question about a default that has stood since 2.3, and is better decided on
> its own.
> h3. Compatibility
> {{CharSequenceValue}}'s constructor is public but sits in
> {{org.apache.groovy.json.internal}} and has no caller outside that package in
> this repository. Changing the parameter is defensible; an overload alongside
> the existing one costs little if the package name is not thought to be enough.
> Additive with the default unchanged, so it suits 6.x. Only a change of
> default would need to wait.
> h3. Related
> Raised by the 2026-09-01 security scan as f013, and by tmd-5 against §8 P3 of
> {{THREAT_MODEL.md}}, which asserts the slurpers return only
> Map/List/String/Number/Boolean/null. The no-gadget-instantiation guarantee is
> unaffected: {{Date}} is built from a validated lexical form, not from a class
> named in the document. What P3 has wrong is the list of types, and it should
> be corrected whatever is decided here.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)