feat: add Decoder.UseNumber and the Number type
Test / test (push) Successful in 1m52s

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-21 23:49:39 +02:00
parent 582222738b
commit ce0c1ebd9d
9 changed files with 320 additions and 4 deletions
+33 -1
View File
@@ -157,7 +157,9 @@ and every 64 fields during the reflection walk.
When decoding into a struct, these values convert onto the destination's
concrete types: any integer or unsigned width, floats, slices, nested structs
and `map[string]T`.
and `map[string]T`. `Decoder.UseNumber` replaces the two numeric rows of the
table with `Number`, which keeps the literal; see
[Numbers as literals](#numbers-as-literals).
### Target constraints
@@ -211,6 +213,29 @@ The decoder converts to the destination type with explicit overflow checks:
A conversion that the rules do not allow produces an error wrapped with the
offending key or index, for example `p: interpres: integer 300 overflows uint8`.
### Numbers as literals
`NewDecoder().UseNumber()` decodes every integer and float into `Number`, a
string type that carries the literal the document wrote: `0x1f`, `1_000`,
`+1.0`, `inf`. The shape is validated as strictly as ever, so `01` and `1__0`
remain parse errors; only the evaluated value is replaced by the literal. A
round trip through the value tree and `Marshal` keeps the spelling, where the
default tree normalises `0x1f` to `31` and `+1.0` to `1.0`.
```go
var tree map[string]any
err := interpres.NewDecoder().UseNumber().Decode(data, &tree)
lit := tree["rate"].(interpres.Number) // "1_000"
```
A destination of a concrete kind is unaffected: an `int64` field, a `float64`
field and a `time.Duration` field take the evaluated value they always took,
and a `Number` field takes the literal. `Number.Float64` and `Number.Int64`
evaluate the literal on demand, with an error for a float asked as an integer
and for a literal that is not a valid TOML number. `Marshal` writes a `Number`
as its bare literal and rejects one that is not a valid TOML number, whether it
stands alone or inside a value array.
### Date-time values
Offset date-times decode into `OffsetDateTime`, whose embedded `time.Time` is the
@@ -639,6 +664,7 @@ concurrent use.
| Method | Default | Effect |
|---|---|---|
| `DisallowUnknownFields()` | off | a key with no matching struct field is an error |
| `UseNumber()` | off | integers and floats decode into `Number`, which carries the literal; see [Numbers as literals](#numbers-as-literals) |
| `MaxDepth(depth int)` | `10000` | bound how deeply arrays and inline tables may nest |
| `MaxInputSize(size int)` | no limit | bound the size of the document, in bytes |
@@ -688,6 +714,12 @@ See [Custom encoding](#custom-encoding-marshaler).
See [Custom decoding](#custom-decoding-unmarshaler).
### `type Number string`
The literal a number was written with, what `UseNumber` decodes into and what
`Marshal` writes back as it is. See
[Numbers as literals](#numbers-as-literals).
### Date-time wrappers
```go