diff --git a/src/encoding/json/decode.go b/src/encoding/json/decode.go index 434edf8ea45..2eda875bfde 100644 --- a/src/encoding/json/decode.go +++ b/src/encoding/json/decode.go @@ -62,10 +62,10 @@ import ( // the additional Go array elements are set to zero values. // // To unmarshal a JSON object into a map, Unmarshal first establishes a map to -// use, If the map is nil, Unmarshal allocates a new map. Otherwise Unmarshal +// use. If the map is nil, Unmarshal allocates a new map. Otherwise Unmarshal // reuses the existing map, keeping existing entries. Unmarshal then stores key- -// value pairs from the JSON object into the map. The map's key type must -// either be a string or implement encoding.TextUnmarshaler. +// value pairs from the JSON object into the map. The map's key type must +// either be a string, an integer, or implement encoding.TextUnmarshaler. // // If a JSON value is not appropriate for a given target type, // or if a JSON number overflows the target type, Unmarshal @@ -581,17 +581,24 @@ func (d *decodeState) object(v reflect.Value) { // Check type of target: // struct or - // map[string]T or map[encoding.TextUnmarshaler]T + // map[T1]T2 where T1 is string, an integer type, + // or an encoding.TextUnmarshaler switch v.Kind() { case reflect.Map: - // Map key must either have string kind or be an encoding.TextUnmarshaler. + // Map key must either have string kind, have an integer kind, + // or be an encoding.TextUnmarshaler. t := v.Type() - if t.Key().Kind() != reflect.String && - !reflect.PtrTo(t.Key()).Implements(textUnmarshalerType) { - d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)}) - d.off-- - d.next() // skip over { } in input - return + switch t.Key().Kind() { + case reflect.String, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + default: + if !reflect.PtrTo(t.Key()).Implements(textUnmarshalerType) { + d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)}) + d.off-- + d.next() // skip over { } in input + return + } } if v.IsNil() { v.Set(reflect.MakeMap(t)) @@ -696,13 +703,32 @@ func (d *decodeState) object(v reflect.Value) { var kv reflect.Value switch { case kt.Kind() == reflect.String: - kv = reflect.ValueOf(key).Convert(v.Type().Key()) + kv = reflect.ValueOf(key).Convert(kt) case reflect.PtrTo(kt).Implements(textUnmarshalerType): kv = reflect.New(v.Type().Key()) d.literalStore(item, kv, true) kv = kv.Elem() default: - panic("json: Unexpected key type") // should never occur + switch kt.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + s := string(key) + n, err := strconv.ParseInt(s, 10, 64) + if err != nil || reflect.Zero(kt).OverflowInt(n) { + d.saveError(&UnmarshalTypeError{"number " + s, kt, int64(start + 1)}) + return + } + kv = reflect.ValueOf(n).Convert(kt) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + s := string(key) + n, err := strconv.ParseUint(s, 10, 64) + if err != nil || reflect.Zero(kt).OverflowUint(n) { + d.saveError(&UnmarshalTypeError{"number " + s, kt, int64(start + 1)}) + return + } + kv = reflect.ValueOf(n).Convert(kt) + default: + panic("json: Unexpected key type") // should never occur + } } v.SetMapIndex(kv, subv) } diff --git a/src/encoding/json/decode_test.go b/src/encoding/json/decode_test.go index 30e46ca44f0..7c388c0c272 100644 --- a/src/encoding/json/decode_test.go +++ b/src/encoding/json/decode_test.go @@ -10,8 +10,10 @@ import ( "errors" "fmt" "image" + "math" "net" "reflect" + "strconv" "strings" "testing" "time" @@ -53,6 +55,8 @@ type tx struct { x int } +type u8 uint8 + // A type that can unmarshal itself. type unmarshaler struct { @@ -92,6 +96,29 @@ type ustructText struct { M unmarshalerText } +// u8marshal is an integer type that can marshal/unmarshal itself. +type u8marshal uint8 + +func (u8 u8marshal) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("u%d", u8)), nil +} + +var errMissingU8Prefix = errors.New("missing 'u' prefix") + +func (u8 *u8marshal) UnmarshalText(b []byte) error { + if !bytes.HasPrefix(b, []byte{'u'}) { + return errMissingU8Prefix + } + n, err := strconv.Atoi(string(b[1:])) + if err != nil { + return err + } + *u8 = u8marshal(n) + return nil +} + +var _ encoding.TextUnmarshaler = (*u8marshal)(nil) + var ( um0, um1 unmarshaler // target2 of unmarshaling ump = &um1 @@ -320,7 +347,69 @@ var unmarshalTests = []unmarshalTest{ {in: `["x:y"]`, ptr: &umslicepType, out: &umsliceXY}, {in: `{"M":"x:y"}`, ptr: umstructType, out: umstructXY}, - // Map keys can be encoding.TextUnmarshalers + // integer-keyed map test + { + in: `{"-1":"a","0":"b","1":"c"}`, + ptr: new(map[int]string), + out: map[int]string{-1: "a", 0: "b", 1: "c"}, + }, + { + in: `{"0":"a","10":"c","9":"b"}`, + ptr: new(map[u8]string), + out: map[u8]string{0: "a", 9: "b", 10: "c"}, + }, + { + in: `{"-9223372036854775808":"min","9223372036854775807":"max"}`, + ptr: new(map[int64]string), + out: map[int64]string{math.MinInt64: "min", math.MaxInt64: "max"}, + }, + { + in: `{"18446744073709551615":"max"}`, + ptr: new(map[uint64]string), + out: map[uint64]string{math.MaxUint64: "max"}, + }, + { + in: `{"0":false,"10":true}`, + ptr: new(map[uintptr]bool), + out: map[uintptr]bool{0: false, 10: true}, + }, + + // Check that MarshalText and UnmarshalText take precedence + // over default integer handling in map keys. + { + in: `{"u2":4}`, + ptr: new(map[u8marshal]int), + out: map[u8marshal]int{2: 4}, + }, + { + in: `{"2":4}`, + ptr: new(map[u8marshal]int), + err: errMissingU8Prefix, + }, + + // integer-keyed map errors + { + in: `{"abc":"abc"}`, + ptr: new(map[int]string), + err: &UnmarshalTypeError{"number abc", reflect.TypeOf(0), 2}, + }, + { + in: `{"256":"abc"}`, + ptr: new(map[uint8]string), + err: &UnmarshalTypeError{"number 256", reflect.TypeOf(uint8(0)), 2}, + }, + { + in: `{"128":"abc"}`, + ptr: new(map[int8]string), + err: &UnmarshalTypeError{"number 128", reflect.TypeOf(int8(0)), 2}, + }, + { + in: `{"-1":"abc"}`, + ptr: new(map[uint8]string), + err: &UnmarshalTypeError{"number -1", reflect.TypeOf(uint8(0)), 2}, + }, + + // Map keys can be encoding.TextUnmarshalers. {in: `{"x:y":true}`, ptr: &ummapType, out: ummapXY}, // If multiple values for the same key exists, only the most recent value is used. {in: `{"x:y":false,"x:y":true}`, ptr: &ummapType, out: ummapXY}, diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index d8c779869b3..8b967471ce7 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -117,9 +117,13 @@ import ( // an anonymous struct field in both current and earlier versions, give the field // a JSON tag of "-". // -// Map values encode as JSON objects. The map's key type must either be a string -// or implement encoding.TextMarshaler. The map keys are used as JSON object -// keys, subject to the UTF-8 coercion described for string values above. +// Map values encode as JSON objects. The map's key type must either be a +// string, an integer type, or implement encoding.TextMarshaler. The map keys +// are used as JSON object keys by applying the following rules, subject to the +// UTF-8 coercion described for string values above: +// - string keys are used directly +// - encoding.TextMarshalers are marshaled +// - integer keys are converted to strings // // Pointer values encode as the value pointed to. // A nil pointer encodes as the null JSON value. @@ -644,8 +648,14 @@ func (me *mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { } func newMapEncoder(t reflect.Type) encoderFunc { - if t.Key().Kind() != reflect.String && !t.Key().Implements(textMarshalerType) { - return unsupportedTypeEncoder + switch t.Key().Kind() { + case reflect.String, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + default: + if !t.Key().Implements(textMarshalerType) { + return unsupportedTypeEncoder + } } me := &mapEncoder{typeEncoder(t.Elem())} return me.encode @@ -806,9 +816,20 @@ func (w *reflectWithString) resolve() error { w.s = w.v.String() return nil } - buf, err := w.v.Interface().(encoding.TextMarshaler).MarshalText() - w.s = string(buf) - return err + if tm, ok := w.v.Interface().(encoding.TextMarshaler); ok { + buf, err := tm.MarshalText() + w.s = string(buf) + return err + } + switch w.v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + w.s = strconv.FormatInt(w.v.Int(), 10) + return nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + w.s = strconv.FormatUint(w.v.Uint(), 10) + return nil + } + panic("unexpected map key type") } // byString is a slice of reflectWithString where the reflect.Value is either