Skip to content

feat: Decoder.DisableTimestamps(bool) opt-out for implicit timestamp resolution#8

Open
reuvenharrison wants to merge 1 commit into
v3from
feat/disable-timestamps
Open

feat: Decoder.DisableTimestamps(bool) opt-out for implicit timestamp resolution#8
reuvenharrison wants to merge 1 commit into
v3from
feat/disable-timestamps

Conversation

@reuvenharrison
Copy link
Copy Markdown
Collaborator

@reuvenharrison reuvenharrison commented May 10, 2026

Why

YAML 1.1 auto-resolves unquoted date-shaped scalars (e.g. 1344-08-22) to time.Time, including when they appear as map keys. Real-world OpenAPI / JSON Schema specs sometimes use date-shaped strings as map keys (e.g. APIs-guru/openapi-directory/APIs/unicourt.com/1.0.0/openapi.yaml lines 376-380). The auto-resolution silently turns the parsed value into a time.Time-keyed map, breaking any downstream string-keyed lookup.

Closes the gap kin-openapi originally raised in invopop/yaml#10.

API

dec := yaml.NewDecoder(r)
dec.DisableTimestamps(true)
dec.Decode(&v)

When set, untagged date-shaped scalars resolve to strings. Explicit !!timestamp tags in the source still resolve to time.Time, because that is the caller's explicit intent: the flag's purpose is to keep date-shaped untagged scalars as strings.

Plumbing

  • New disableTimestamps bool field on the public Decoder, the internal decoder, and the parser (the parser tags untagged scalars at parse time, so the flag has to flow there as well).
  • resolve() gets a third disableTimestamps parameter. The implicit branch (tag == "" && parseTimestamp(in) ok) is now gated on the flag; the explicit tag == timestampTag branch is unaffected.
  • All five resolve() call sites updated. Encode-side and yaml.go callers pass false to preserve historical behaviour.
  • Default Decode() behaviour unchanged: disableTimestamps defaults to false.

Tests

New TestDecoderDisableTimestamps covers four cases:

Input DisableTimestamps Expected
1344-08-22 true string "1344-08-22"
1344-08-22: hello decoded into map[string]string true {"1344-08-22": "hello"}
!!timestamp 1344-08-22 true time.Time (explicit tag preserved)
1344-08-22 false (default) time.Time (back-compat)

All existing tests pass unchanged.

Downstream

The original request came from kin-openapi via invopop/yaml#10. invopop/yaml is no longer maintained, so kin-openapi switched to oasdiff/yaml (our fork). That makes this PR the actual fix path for kin-openapi.

After this lands and is tagged in oasdiff/yaml3:

  1. oasdiff/yaml exposes the same option on Unmarshal / UnmarshalWithOriginTree.
  2. kin-openapi (and downstream consumers like oasdiff) pick it up on the next oasdiff/yaml bump.

…resolution

YAML 1.1 auto-resolves unquoted date-shaped scalars (e.g. "1344-08-22")
to time.Time, including when they appear as map keys. Real-world
specs (OpenAPI / JSON Schema descendants) sometimes use date-shaped
strings as map keys, which this auto-resolution silently corrupts:
the resulting map has time.Time keys instead of strings, and any
downstream string-keyed lookup fails.

Add a Decoder.DisableTimestamps(disable bool) option that suppresses
the implicit timestamp branch in resolve(). When set, untagged
date-shaped scalars resolve to strings. Explicit '!!timestamp' tags
in the source still resolve to time.Time, because that is the
caller's explicit intent: the flag's purpose is to keep date-shaped
UNTAGGED scalars as strings.

Plumbing:
- Threaded through both the parser (which sets Node.Tag at parse
  time for untagged scalars) and the internal decoder (which calls
  resolve when materialising values).
- All five resolve() call sites updated; encode-side / yaml.go
  callers pass false to preserve historical behaviour.
- Default Decode() behaviour is unchanged (disableTimestamps
  defaults to false).

Closes invopop/yaml#10 (downstream).
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.

1 participant