Add support for json.Number to dataconv.Marshal#128
Conversation
…167) Two related JSON-number gaps, both surfaced by the diagnosis in #128 (thanks @phughes-scwx): 1. Marshal had no json.Number case, so a json.Number fell through to "unrecognized type" and errored. A caller decoding raw JSON into map[string]interface{} with dec.UseNumber() (the way to keep the int/float distinction that a plain json.Unmarshal collapses to float64) could not then Marshal it. Add the case, mapping by literal form: an integer literal -> Int (big.Int beyond int64), otherwise -> Float. This matches json.decode and serial; mapping to a string (as proposed in #128) would make Marshal the only converter that turns a number into a non-number and break value == 6. Also accept *big.Int (the inverse of Unmarshal, so the round-trip stays exact). 2. UnmarshalStarlarkJSON decoded WITHOUT UseNumber and re-derived int-ness from float64, silently saturating integers past 2^53 (it is on the lib/http request-body path, so real JSON ids were affected). Decode with UseNumber and the same number-preserving logic; impose single-document strictness the previous json.Unmarshal had. interface.go doc updated: numbers now map by literal form (whole floats like 6.0 become Float, not Int — no test pinned the old heuristic). Test-first: TestMarshal gains json.Number/*big.Int cases and TestUnmarshalStarlarkJSONNumberFidelity pins exact big-int decoding. Requirement: LET-28. Refs #128. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks for this, @phughes-scwx — and sorry for the slow look. The gap you're pointing at is real: today The one thing we wanted to settle before merging is the target type. Mapping So rather than merge this PR as-is, we've taken your diagnosis and landed the fix in #167 (credited to you), mapping Verifying this also surfaced a sibling bug: Does the implementation in #167 work for your use case? If it covers what you needed we'll close this PR in favor of it; if anything's missing or you'd prefer a different shape, very happy to adjust. Thanks again — the report and repro pinpointed a real inconsistency in how we handle JSON numbers. |
Ah, my good friend Claude. Thank you for your support.
Yeah sure! Looks good. |
What
Adds a case to the
dataconv.Marshaltype switch that retains values passed asjson.Numberas their underlying string value.Why
In instances where a user is consuming raw JSON without a predetermined struct for unmarshaling, they often unmarshal into a
map[string]interface{}. When doing this all numbers are converted to floats, even if they have no fractional part. Eg:Therefore a user, in a situation where they are unmarshaling deeply-nested JSON into a
map[string]interface{}and then marshaling usingdataconv.Marshalfor use in Starlark, can expect integers to be misidentified as floats. Checks inside the script will fail to distinguish between integers and floats.To simplify this situation, we ask the Go JSON decoder to use
json.Numbervalues for all numerics.If
dataconv.Marshalconverts the value to a string representation, then we can handle the values differently based onstr.isdigit()andstr.isdecimal()within the script.