aboutsummaryrefslogtreecommitdiff
path: root/src/encoding
diff options
context:
space:
mode:
authorJoe Tsai <joetsai@digital-static.net>2025-08-12 01:56:43 -0700
committerJoseph Tsai <joetsai@digital-static.net>2025-08-13 15:47:31 -0700
commitaf8870708bbaf15956a27cbab15582b4c666855e (patch)
treef60725fc5fcc77431142aff96c04fbe53f50c1a8 /src/encoding
parent0a75e5a07b858cbe6216c99fa12d582d063499d9 (diff)
downloadgo-af8870708bbaf15956a27cbab15582b4c666855e.tar.xz
encoding/json/v2: fix incorrect marshaling of NaN in float64 any
There is a fast-path optimization for marshaling an any type that should be semantically identical to when the optimization is not active (i.e., optimizeCommon is false). Unfortunately, the optimization accidentally allows NaN, which this change fixes. The source of this discrepency is that Encoder.WriteToken(Float(math.NaN())) emits a JSON string with "NaN", rather than report an error. The rationale for this behavior is because we needed to decide what to do with Float(math.NaN()), whether it would return an error, panic, or allow it. To keep the API simpler (no errors) and less sharp (no panics), we permitted NaN. The fact that WriteToken allowed it is a logical extension of that decision, but we could decide to disallow it at least within WriteToken. As things stand, it is already inconsistent between json/v2 and jsontext, where json/v2 rejects NaN by default in Marshal, but jsontext allows it in WriteToken. This only modifies code that is compiled under goexperiment.jsonv2. Fixes #74797 Change-Id: Ib0708cfbf93c2b059c0a85e4c4544c0604573448 Reviewed-on: https://go-review.googlesource.com/c/go/+/695276 Reviewed-by: Damien Neil <dneil@google.com> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'src/encoding')
-rw-r--r--src/encoding/json/v2/arshal_any.go20
-rw-r--r--src/encoding/json/v2/arshal_test.go5
2 files changed, 17 insertions, 8 deletions
diff --git a/src/encoding/json/v2/arshal_any.go b/src/encoding/json/v2/arshal_any.go
index c2b09bd918..97a77e9237 100644
--- a/src/encoding/json/v2/arshal_any.go
+++ b/src/encoding/json/v2/arshal_any.go
@@ -8,6 +8,7 @@ package json
import (
"cmp"
+ "math"
"reflect"
"strconv"
@@ -35,20 +36,23 @@ func marshalValueAny(enc *jsontext.Encoder, val any, mo *jsonopts.Struct) error
case string:
return enc.WriteToken(jsontext.String(val))
case float64:
+ if math.IsNaN(val) || math.IsInf(val, 0) {
+ break // use default logic below
+ }
return enc.WriteToken(jsontext.Float(val))
case map[string]any:
return marshalObjectAny(enc, val, mo)
case []any:
return marshalArrayAny(enc, val, mo)
- default:
- v := newAddressableValue(reflect.TypeOf(val))
- v.Set(reflect.ValueOf(val))
- marshal := lookupArshaler(v.Type()).marshal
- if mo.Marshalers != nil {
- marshal, _ = mo.Marshalers.(*Marshalers).lookup(marshal, v.Type())
- }
- return marshal(enc, v, mo)
}
+
+ v := newAddressableValue(reflect.TypeOf(val))
+ v.Set(reflect.ValueOf(val))
+ marshal := lookupArshaler(v.Type()).marshal
+ if mo.Marshalers != nil {
+ marshal, _ = mo.Marshalers.(*Marshalers).lookup(marshal, v.Type())
+ }
+ return marshal(enc, v, mo)
}
// unmarshalValueAny unmarshals a JSON value as a Go any.
diff --git a/src/encoding/json/v2/arshal_test.go b/src/encoding/json/v2/arshal_test.go
index 5f5f072e25..75093345a3 100644
--- a/src/encoding/json/v2/arshal_test.go
+++ b/src/encoding/json/v2/arshal_test.go
@@ -3217,6 +3217,11 @@ func TestMarshal(t *testing.T) {
in: struct{ X any }{[8]byte{}},
want: `{"X":"called"}`,
}, {
+ name: jsontest.Name("Interfaces/Any/Float/NaN"),
+ in: struct{ X any }{math.NaN()},
+ want: `{"X"`,
+ wantErr: EM(fmt.Errorf("unsupported value: %v", math.NaN())).withType(0, reflect.TypeFor[float64]()).withPos(`{"X":`, "/X"),
+ }, {
name: jsontest.Name("Interfaces/Any/Maps/Nil"),
in: struct{ X any }{map[string]any(nil)},
want: `{"X":{}}`,