mirror of
https://github.com/golang/go
synced 2024-11-23 06:50:05 -07:00
encoding/json: marshal maps using reflect.Value.MapRange
Map serialization using reflect.Value.MapIndex cannot retrieve map keys that contain a NaN, resulting in a panic. Switch the implementation to use the reflect.Value.MapRange method instead, which iterates over all map entries regardless of whether they are directly retrievable. Note that according to RFC 8259, section 4, a JSON object should have unique names, but does not forbid the occurrence of duplicate names. Fixes #43207 Change-Id: If4bc55229b1f64b8ca4b0fed37549725efdace39 Reviewed-on: https://go-review.googlesource.com/c/go/+/278632 Trust: Meng Zhuo <mzh@golangcn.org> Trust: Joe Tsai <thebrokentoaster@gmail.com> Trust: Bryan C. Mills <bcmills@google.com> Run-TryBot: Meng Zhuo <mzh@golangcn.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com> Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
This commit is contained in:
parent
96a96a9058
commit
a7526bbf72
@ -794,23 +794,24 @@ func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
|
|||||||
e.WriteByte('{')
|
e.WriteByte('{')
|
||||||
|
|
||||||
// Extract and sort the keys.
|
// Extract and sort the keys.
|
||||||
keys := v.MapKeys()
|
sv := make([]reflectWithString, v.Len())
|
||||||
sv := make([]reflectWithString, len(keys))
|
mi := v.MapRange()
|
||||||
for i, v := range keys {
|
for i := 0; mi.Next(); i++ {
|
||||||
sv[i].v = v
|
sv[i].k = mi.Key()
|
||||||
|
sv[i].v = mi.Value()
|
||||||
if err := sv[i].resolve(); err != nil {
|
if err := sv[i].resolve(); err != nil {
|
||||||
e.error(fmt.Errorf("json: encoding error for type %q: %q", v.Type().String(), err.Error()))
|
e.error(fmt.Errorf("json: encoding error for type %q: %q", v.Type().String(), err.Error()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Slice(sv, func(i, j int) bool { return sv[i].s < sv[j].s })
|
sort.Slice(sv, func(i, j int) bool { return sv[i].ks < sv[j].ks })
|
||||||
|
|
||||||
for i, kv := range sv {
|
for i, kv := range sv {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
e.WriteByte(',')
|
e.WriteByte(',')
|
||||||
}
|
}
|
||||||
e.string(kv.s, opts.escapeHTML)
|
e.string(kv.ks, opts.escapeHTML)
|
||||||
e.WriteByte(':')
|
e.WriteByte(':')
|
||||||
me.elemEnc(e, v.MapIndex(kv.v), opts)
|
me.elemEnc(e, kv.v, opts)
|
||||||
}
|
}
|
||||||
e.WriteByte('}')
|
e.WriteByte('}')
|
||||||
e.ptrLevel--
|
e.ptrLevel--
|
||||||
@ -997,29 +998,30 @@ func typeByIndex(t reflect.Type, index []int) reflect.Type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type reflectWithString struct {
|
type reflectWithString struct {
|
||||||
v reflect.Value
|
k reflect.Value
|
||||||
s string
|
v reflect.Value
|
||||||
|
ks string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *reflectWithString) resolve() error {
|
func (w *reflectWithString) resolve() error {
|
||||||
if w.v.Kind() == reflect.String {
|
if w.k.Kind() == reflect.String {
|
||||||
w.s = w.v.String()
|
w.ks = w.k.String()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if tm, ok := w.v.Interface().(encoding.TextMarshaler); ok {
|
if tm, ok := w.k.Interface().(encoding.TextMarshaler); ok {
|
||||||
if w.v.Kind() == reflect.Ptr && w.v.IsNil() {
|
if w.k.Kind() == reflect.Ptr && w.k.IsNil() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
buf, err := tm.MarshalText()
|
buf, err := tm.MarshalText()
|
||||||
w.s = string(buf)
|
w.ks = string(buf)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
switch w.v.Kind() {
|
switch w.k.Kind() {
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
w.s = strconv.FormatInt(w.v.Int(), 10)
|
w.ks = strconv.FormatInt(w.k.Int(), 10)
|
||||||
return nil
|
return nil
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
w.s = strconv.FormatUint(w.v.Uint(), 10)
|
w.ks = strconv.FormatUint(w.k.Uint(), 10)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
panic("unexpected map key type")
|
panic("unexpected map key type")
|
||||||
|
@ -245,6 +245,22 @@ func TestUnsupportedValues(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issue 43207
|
||||||
|
func TestMarshalTextFloatMap(t *testing.T) {
|
||||||
|
m := map[textfloat]string{
|
||||||
|
textfloat(math.NaN()): "1",
|
||||||
|
textfloat(math.NaN()): "1",
|
||||||
|
}
|
||||||
|
got, err := Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Marshal() error: %v", err)
|
||||||
|
}
|
||||||
|
want := `{"TF:NaN":"1","TF:NaN":"1"}`
|
||||||
|
if string(got) != want {
|
||||||
|
t.Errorf("Marshal() = %s, want %s", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ref has Marshaler and Unmarshaler methods with pointer receiver.
|
// Ref has Marshaler and Unmarshaler methods with pointer receiver.
|
||||||
type Ref int
|
type Ref int
|
||||||
|
|
||||||
@ -854,6 +870,10 @@ func tenc(format string, a ...interface{}) ([]byte, error) {
|
|||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type textfloat float64
|
||||||
|
|
||||||
|
func (f textfloat) MarshalText() ([]byte, error) { return tenc(`TF:%0.2f`, f) }
|
||||||
|
|
||||||
// Issue 13783
|
// Issue 13783
|
||||||
func TestEncodeBytekind(t *testing.T) {
|
func TestEncodeBytekind(t *testing.T) {
|
||||||
testdata := []struct {
|
testdata := []struct {
|
||||||
@ -872,6 +892,7 @@ func TestEncodeBytekind(t *testing.T) {
|
|||||||
{[]jsonint{5, 4}, `[{"JI":5},{"JI":4}]`},
|
{[]jsonint{5, 4}, `[{"JI":5},{"JI":4}]`},
|
||||||
{[]textint{9, 3}, `["TI:9","TI:3"]`},
|
{[]textint{9, 3}, `["TI:9","TI:3"]`},
|
||||||
{[]int{9, 3}, `[9,3]`},
|
{[]int{9, 3}, `[9,3]`},
|
||||||
|
{[]textfloat{12, 3}, `["TF:12.00","TF:3.00"]`},
|
||||||
}
|
}
|
||||||
for _, d := range testdata {
|
for _, d := range testdata {
|
||||||
js, err := Marshal(d.data)
|
js, err := Marshal(d.data)
|
||||||
|
Loading…
Reference in New Issue
Block a user