Skip to content

fix(json): preserve floats with trailing zero when encoding YAML to JSON - #2701

Merged
mikefarah merged 1 commit into
mikefarah:masterfrom
ChrisJr404:fix-json-float-preservation
May 14, 2026
Merged

mikefarah merged 1 commit into
mikefarah:masterfrom
ChrisJr404:fix-json-float-preservation

Conversation

@ChrisJr404

Copy link
Copy Markdown
Contributor

Closes #2683.

Problem

YAML scalars tagged !!float were round-tripped through float64 and re-serialized by Go's JSON encoder, which strips the decimal part of whole-number floats. The reporter's example:

percentiles: [50.0, 95.0, 99.0, 99.9]

was emitted as:

{"percentiles":[50,95,99,99.9]}

JSON does not distinguish ints from floats at the syntactic level, but every common library (Go's encoding/json, Python's json, jq) preserves the fractional form of values that came in as floats. yq's YAML decoder already parses these scalars as !!float with the original text intact, so the encoder can emit them verbatim instead of round-tripping through a float64.

Fix

MarshalJSON on ScalarNode now special-cases !!float:

  • If the scalar's Value is already a JSON-shaped number literal containing a . or exponent (e.g. 50.0, 99.9, 1.5e-3, -7.0), it is emitted verbatim.
  • If Value is an integer-shaped string tagged !!float (e.g. !!float 5), it is formatted with strconv.FormatFloat and .0 is appended so the output is still a JSON number with a fractional part (5.0).
  • Otherwise (empty Value, parse error, or non-finite result) the code falls back to the existing path, so the current behaviour for .inf / .nan is unchanged and stays a separate issue.

!!int nodes go through the unchanged path and still encode as JSON integers.

Behaviour after the fix

$ echo 'percentiles: [50.0, 95.0, 99.0, 99.9]' | ./yq -o json -I 0 '.'
{"percentiles":[50.0,95.0,99.0,99.9]}

$ echo 'a: 50' | ./yq -o json -I 0 '.'
{"a":50}

$ echo 'a: !!float 5' | ./yq -o json -I 0 '.'
{"a":5.0}

$ echo 'a: 1.5e-3' | ./yq -o json -I 0 '.'
{"a":1.5e-3}

$ echo 'a: [1, 2.0, 3, 4.5]' | ./yq -o json -I 0 '.'
{"a":[1,2.0,3,4.5]}

Scope notes

  • This only changes YAML to JSON. The JSON to YAML direction still collapses 50.0 to 50 in the JSON decoder (setScalarFromJson in pkg/yqlib/candidate_node_json.go), and the existing "numbers" decode test in json_test.go codifies that. Changing that direction would be a larger, opinionated change and is out of scope here.
  • Special floats (.inf, .nan) are left on the existing error path; the YAML to JSON conversion for those values was already broken (strconv.ParseFloat: parsing ".inf": invalid syntax) and is a separate problem.

Tests

Added six scenarios to jsonScenarios in pkg/yqlib/json_test.go:

  • preserve whole-number floats including the reporter's [50.0, 95.0, 99.0, 99.9] case (documented scenario)
  • !!int whole numbers stay integers
  • !!float 5 becomes 5.0
  • scientific notation preserved
  • negative floats preserved
  • mixed int and float arrays encode each value with the right shape

go test ./... is green locally.

YAML scalars tagged `!!float` were round-tripped through `float64` and
re-serialized by Go's JSON encoder, which strips the decimal part of
whole-number floats. As a result, `50.0` came out as `50` and a
sequence like `[50.0, 95.0, 99.0, 99.9]` became `[50,95,99,99.9]`,
turning a uniform array of floats into a mixed int/float array that
downstream consumers (Horreum, JSON Schema validators, jq, etc.)
reject.

The JSON spec does not distinguish ints from floats, but every common
JSON library (Go's `encoding/json`, Python's `json`, jq) preserves the
fractional form of values that came in as floats. yq's YAML decoder
already parses these as `!!float` with the original text intact, so we
can emit them verbatim instead of round-tripping.

`MarshalJSON` for `ScalarNode` now special-cases `!!float`:
- if `Value` is already a JSON-shaped number literal containing a `.`
  or exponent, emit it verbatim (e.g. `50.0`, `99.9`, `1.5e-3`, `-7.0`);
- if `Value` is an integer-shaped string tagged `!!float` (e.g.
  `!!float 5`), format the parsed float and append `.0` so it stays a
  JSON number with a fractional part;
- otherwise (empty value, parse error, or non-finite result), fall back
  to the existing encoding path so behaviour for `.inf` / `.nan` and
  anything unusual is unchanged.

`!!int` nodes still encode as JSON integers.

Closes mikefarah#2683

Signed-off-by: ChrisJr404 <chris@hacknow.com>
@mikefarah
mikefarah merged commit 2861815 into mikefarah:master May 14, 2026
3 checks passed
eleboucher pushed a commit to eleboucher/runner that referenced this pull request Jul 9, 2026
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [forgejo-runner-service-yq](https://github.com/mikefarah/yq) | patch | `v4.53.2` → `v4.53.3` |

---

### Release Notes

<details>
<summary>mikefarah/yq (forgejo-runner-service-yq)</summary>

### [`v4.53.3`](https://github.com/mikefarah/yq/releases/tag/v4.53.3)

[Compare Source](mikefarah/yq@v4.53.2...draft-4.53.3)

- Add `--ini-preserve-quotes` flag for INI round-trip quote preservation ([#&#8203;2728](mikefarah/yq#2728)) Thanks [@&#8203;toller892](https://github.com/toller892)!
- Fix: reset INI decoder state on init ([#&#8203;2719](mikefarah/yq#2719)) Thanks [@&#8203;xieby1](https://github.com/xieby1)!
- Fix: decode properties array bracket paths ([#&#8203;2693](mikefarah/yq#2693)) Thanks [@&#8203;cyphercodes](https://github.com/cyphercodes)!
- Fix: preserve floats with trailing zero when encoding YAML to JSON ([#&#8203;2701](mikefarah/yq#2701)) Thanks [@&#8203;ChrisJr404](https://github.com/ChrisJr404)!
- Fix: JSON to TOML root scope and null handling ([#&#8203;2689](mikefarah/yq#2689)) Thanks [@&#8203;LovesAsuna](https://github.com/LovesAsuna)!
- Fix: reset TOML decoder finished flag on Init for multi-doc evaluation ([#&#8203;2704](mikefarah/yq#2704)) Thanks [@&#8203;terminalchai](https://github.com/terminalchai)!
- Fix: reset TOML decoder between files when evaluating all at once ([#&#8203;2685](mikefarah/yq#2685)) Thanks [@&#8203;terminalchai](https://github.com/terminalchai)!
- Fix: preserve TOML inline table array scope ([#&#8203;2694](mikefarah/yq#2694)) Thanks [@&#8203;cyphercodes](https://github.com/cyphercodes)!
- Fix: preserve empty TOML arrays in tables ([#&#8203;2686](mikefarah/yq#2686)) Thanks [@&#8203;cyphercodes](https://github.com/cyphercodes)!
- Fix: TOML encoder uses inline tables for YAML FlowStyle mappings ([#&#8203;2687](mikefarah/yq#2687))
- Fix nested inline YAML merge explode ([#&#8203;2699](mikefarah/yq#2699)) Thanks [@&#8203;cyphercodes](https://github.com/cyphercodes)!
- Fix repeatString overflow test on 32-bit platforms ([#&#8203;2680](mikefarah/yq#2680)) Thanks [@&#8203;jandubois](https://github.com/jandubois)!
- Bumped dependencies

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM (`* 0-3 * * *`)
- Automerge
  - Between 12:00 AM and 03:59 AM (`* 0-3 * * *`)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yMTQuNSIsInVwZGF0ZWRJblZlciI6IjQzLjIxNC41IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJLaW5kL0RlcGVuZGVuY3lVcGRhdGUiLCJydW4tZW5kLXRvLWVuZC10ZXN0cyJdfQ==-->

Reviewed-on: https://code.forgejo.org/forgejo/runner/pulls/1561
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.code.forgejo.org>
hbjydev pushed a commit to hbjydev/phoebe that referenced this pull request Aug 27, 2026
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [yq](https://github.com/mikefarah/yq) | minor | `4.52.4` → `4.53.6` |

---

> ⚠️ **Warning**
>
> Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/141) for more information.

---

### Release Notes

<details>
<summary>mikefarah/yq (yq)</summary>

### [`v4.53.6`](https://github.com/mikefarah/yq/releases/tag/v4.53.6)

[Compare Source](mikefarah/yq@v4.53.4...v4.53.6)

- Fixing release build issue
  - Fixed line wrapping bug ([#&#8203;2824](mikefarah/yq#2824), [#&#8203;2823](mikefarah/yq#2823)) Thanks [@&#8203;mxey](https://github.com/mxey)
  - Bumped dependencies

### [`v4.53.4`](https://github.com/mikefarah/yq/releases/tag/v4.53.4)

[Compare Source](mikefarah/yq@v4.53.3...v4.53.4)

- Close input files after processing each one ([#&#8203;2796](mikefarah/yq#2796)) ([#&#8203;2808](mikefarah/yq#2808)) Thanks [@&#8203;MsfPablo](https://github.com/MsfPablo)
- Fixed non string HCL string keys ([#&#8203;2795](mikefarah/yq#2795))
- Fixed heap allocation bug ([#&#8203;2809](mikefarah/yq#2809)) Thanks [@&#8203;MsfPablo](https://github.com/MsfPablo)
- Fix deleting commented empty list YAML output ([#&#8203;2765](mikefarah/yq#2765)) Thanks [@&#8203;dpersek](https://github.com/dpersek)
- Fix (has) return false for negative array indices ([#&#8203;2769](mikefarah/yq#2769)) Thanks [@&#8203;maximilize](https://github.com/maximilize)
- Fix (sort): avoid int64 overflow comparing large integers ([#&#8203;2771](mikefarah/yq#2771)) Thanks [@&#8203;maximilize](https://github.com/maximilize)
- Fix: preserve correct parent references in explode merge anchor reconstruction ([#&#8203;2730](mikefarah/yq#2730)) Thanks [@&#8203;vomba](https://github.com/vomba)
- Improve Guard ExpressionParser initialization with sync.Once ([#&#8203;2789](mikefarah/yq#2789)) Thanks [@&#8203;arcaven](https://github.com/arcaven)
- Fix: reject negative indent instead of panicking ([#&#8203;2746](mikefarah/yq#2746)) Thanks [@&#8203;StressTestor](https://github.com/StressTestor)
- Fix: parse signed hex and octal integers ([#&#8203;2749](mikefarah/yq#2749)) Thanks [@&#8203;StressTestor](https://github.com/StressTestor)
- Fix: skip UTF-8 BOM when processing front matter ([#&#8203;2751](mikefarah/yq#2751)) Thanks [@&#8203;StressTestor](https://github.com/StressTestor)
- Fix !!merge tag regression for yq  ([#&#8203;2705](mikefarah/yq#2705)) Thanks [@&#8203;W-Floyd](https://github.com/W-Floyd)
- Default to yaml when a file's extension is not a recognised format ([#&#8203;2785](mikefarah/yq#2785)) Thanks [@&#8203;devthedevil](https://github.com/devthedevil)
- Bumped dependencies

### [`v4.53.3`](https://github.com/mikefarah/yq/releases/tag/v4.53.3)

[Compare Source](mikefarah/yq@v4.53.2...v4.53.3)

- Add `--ini-preserve-quotes` flag for INI round-trip quote preservation ([#&#8203;2728](mikefarah/yq#2728)) Thanks [@&#8203;toller892](https://github.com/toller892)!
- Fix: reset INI decoder state on init ([#&#8203;2719](mikefarah/yq#2719)) Thanks [@&#8203;xieby1](https://github.com/xieby1)!
- Fix: decode properties array bracket paths ([#&#8203;2693](mikefarah/yq#2693)) Thanks [@&#8203;cyphercodes](https://github.com/cyphercodes)!
- Fix: preserve floats with trailing zero when encoding YAML to JSON ([#&#8203;2701](mikefarah/yq#2701)) Thanks [@&#8203;ChrisJr404](https://github.com/ChrisJr404)!
- Fix: JSON to TOML root scope and null handling ([#&#8203;2689](mikefarah/yq#2689)) Thanks [@&#8203;LovesAsuna](https://github.com/LovesAsuna)!
- Fix: reset TOML decoder finished flag on Init for multi-doc evaluation ([#&#8203;2704](mikefarah/yq#2704)) Thanks [@&#8203;terminalchai](https://github.com/terminalchai)!
- Fix: reset TOML decoder between files when evaluating all at once ([#&#8203;2685](mikefarah/yq#2685)) Thanks [@&#8203;terminalchai](https://github.com/terminalchai)!
- Fix: preserve TOML inline table array scope ([#&#8203;2694](mikefarah/yq#2694)) Thanks [@&#8203;cyphercodes](https://github.com/cyphercodes)!
- Fix: preserve empty TOML arrays in tables ([#&#8203;2686](mikefarah/yq#2686)) Thanks [@&#8203;cyphercodes](https://github.com/cyphercodes)!
- Fix: TOML encoder uses inline tables for YAML FlowStyle mappings ([#&#8203;2687](mikefarah/yq#2687))
- Fix nested inline YAML merge explode ([#&#8203;2699](mikefarah/yq#2699)) Thanks [@&#8203;cyphercodes](https://github.com/cyphercodes)!
- Fix repeatString overflow test on 32-bit platforms ([#&#8203;2680](mikefarah/yq#2680)) Thanks [@&#8203;jandubois](https://github.com/jandubois)!
- Bumped dependencies

### [`v4.53.2`](https://github.com/mikefarah/yq/releases/tag/v4.53.2)

[Compare Source](mikefarah/yq@v4.52.5...v4.53.2)

- Releases and tags now signed and immutable!
- Add system(command; args) operator (disabled by default) ([#&#8203;2640](mikefarah/yq#2640))
- TOML encoder: prefer readable table sections over inline tables ([#&#8203;2649](mikefarah/yq#2649))
- Fix TOML encoder to quote keys containing special characters ([#&#8203;2648](mikefarah/yq#2648))
- Add string slicing support ([#&#8203;2639](mikefarah/yq#2639))
- Fix findInArray misuse on MappingNodes in equality and contains ([#&#8203;2645](mikefarah/yq#2645)) Thanks [@&#8203;jandubois](https://github.com/jandubois)!
- Fix panic on negative slice indices that underflow after adjustment ([#&#8203;2646](mikefarah/yq#2646)) Thanks [@&#8203;jandubois](https://github.com/jandubois)!
- Fix stack overflow from circular alias in traverse ([#&#8203;2647](mikefarah/yq#2647)) Thanks [@&#8203;jandubois](https://github.com/jandubois)!
- Fix panic and OOM in repeatString for large repeat counts ([#&#8203;2644](mikefarah/yq#2644)) Thanks [@&#8203;jandubois](https://github.com/jandubois)!
- Bumped dependencies

### [`v4.52.5`](https://github.com/mikefarah/yq/releases/tag/v4.52.5)

[Compare Source](mikefarah/yq@v4.52.4...v4.52.5)

- Fix: reset TOML decoder state between files ([#&#8203;2634](mikefarah/yq#2634)) thanks [@&#8203;terminalchai](https://github.com/terminalchai)
  - Fix: preserve original filename when using --front-matter ([#&#8203;2613](mikefarah/yq#2613)) thanks [@&#8203;cobyfrombrooklyn-bot](https://github.com/cobyfrombrooklyn-bot)
  - Fix typo in filename ([#&#8203;2611](mikefarah/yq#2611)) thanks [@&#8203;alexandear](https://github.com/alexandear)
  - Bumped dependencies

</details>

---

### Configuration

📅 **Schedule**: (in timezone Europe/London)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNjAuMiIsInVwZGF0ZWRJblZlciI6IjQzLjI2MC4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9naXRodWItcmVsZWFzZSIsInR5cGUvbWlub3IiXX0=-->

Reviewed-on: https://forgejo.hayden.moe/hayden/phoebe/pulls/363
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.

YAML-to-JSON conversion drops .0 from whole-number floats (50.0 → 50)

2 participants