encoding/json: support maps with integer keys

This change makes encoding and decoding support integer types in map
keys, converting to/from JSON string keys.

JSON object keys are still sorted lexically, even though the keys may be
integer strings.

For backwards-compatibility, the existing Text(Un)Marshaler support for
map keys (added in CL 20356) does not take precedence over the default
encoding for string types. There is no such concern for integer types,
so integer map key encoding is only used as a fallback if the map key
type is not a Text(Un)Marshaler.

Fixes #12529.

Change-Id: I7e68c34f9cd19704b1d233a9862da15fabf0908a
Reviewed-on: https://go-review.googlesource.com/22060
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
Caleb Spare 2016-04-13 16:51:25 -07:00 committed by Brad Fitzpatrick
parent 9af83462c6
commit f05c3aa24d
3 changed files with 158 additions and 22 deletions

View file

@ -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