mirror of
https://github.com/golang/go.git
synced 2025-11-08 12:41:02 +00:00
encoding/json: allow non-string type keys for (un-)marshal
This CL allows JSON-encoding & -decoding maps whose keys are types that implement encoding.TextMarshaler / TextUnmarshaler. During encode, the map keys are marshaled upfront so that they can be sorted. Fixes #12146 Change-Id: I43809750a7ad82a3603662f095c7baf75fd172da Reviewed-on: https://go-review.googlesource.com/20356 Run-TryBot: Caleb Spare <cespare@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
acefcb732c
commit
ffbd31e9f7
4 changed files with 124 additions and 41 deletions
|
|
@ -116,8 +116,8 @@ 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 be string; the map keys are used as JSON object
|
||||
// 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.
|
||||
//
|
||||
// Pointer values encode as the value pointed to.
|
||||
|
|
@ -611,21 +611,31 @@ func (me *mapEncoder) encode(e *encodeState, v reflect.Value, _ bool) {
|
|||
return
|
||||
}
|
||||
e.WriteByte('{')
|
||||
var sv stringValues = v.MapKeys()
|
||||
sort.Sort(sv)
|
||||
for i, k := range sv {
|
||||
|
||||
// Extract and sort the keys.
|
||||
keys := v.MapKeys()
|
||||
sv := make([]reflectWithString, len(keys))
|
||||
for i, v := range keys {
|
||||
sv[i].v = v
|
||||
if err := sv[i].resolve(); err != nil {
|
||||
e.error(&MarshalerError{v.Type(), err})
|
||||
}
|
||||
}
|
||||
sort.Sort(byString(sv))
|
||||
|
||||
for i, kv := range sv {
|
||||
if i > 0 {
|
||||
e.WriteByte(',')
|
||||
}
|
||||
e.string(k.String())
|
||||
e.string(kv.s)
|
||||
e.WriteByte(':')
|
||||
me.elemEnc(e, v.MapIndex(k), false)
|
||||
me.elemEnc(e, v.MapIndex(kv.v), false)
|
||||
}
|
||||
e.WriteByte('}')
|
||||
}
|
||||
|
||||
func newMapEncoder(t reflect.Type) encoderFunc {
|
||||
if t.Key().Kind() != reflect.String {
|
||||
if t.Key().Kind() != reflect.String && !t.Key().Implements(textMarshalerType) {
|
||||
return unsupportedTypeEncoder
|
||||
}
|
||||
me := &mapEncoder{typeEncoder(t.Elem())}
|
||||
|
|
@ -775,14 +785,29 @@ func typeByIndex(t reflect.Type, index []int) reflect.Type {
|
|||
return t
|
||||
}
|
||||
|
||||
// stringValues is a slice of reflect.Value holding *reflect.StringValue.
|
||||
// It implements the methods to sort by string.
|
||||
type stringValues []reflect.Value
|
||||
type reflectWithString struct {
|
||||
v reflect.Value
|
||||
s string
|
||||
}
|
||||
|
||||
func (sv stringValues) Len() int { return len(sv) }
|
||||
func (sv stringValues) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] }
|
||||
func (sv stringValues) Less(i, j int) bool { return sv.get(i) < sv.get(j) }
|
||||
func (sv stringValues) get(i int) string { return sv[i].String() }
|
||||
func (w *reflectWithString) resolve() error {
|
||||
if w.v.Kind() == reflect.String {
|
||||
w.s = w.v.String()
|
||||
return nil
|
||||
}
|
||||
buf, err := w.v.Interface().(encoding.TextMarshaler).MarshalText()
|
||||
w.s = string(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// byString is a slice of reflectWithString where the reflect.Value is either
|
||||
// a string or an encoding.TextMarshaler.
|
||||
// It implements the methods to sort by string.
|
||||
type byString []reflectWithString
|
||||
|
||||
func (sv byString) Len() int { return len(sv) }
|
||||
func (sv byString) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] }
|
||||
func (sv byString) Less(i, j int) bool { return sv[i].s < sv[j].s }
|
||||
|
||||
// NOTE: keep in sync with stringBytes below.
|
||||
func (e *encodeState) string(s string) int {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue