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:
Augusto Roman 2016-03-08 12:41:35 -08:00 committed by Brad Fitzpatrick
parent acefcb732c
commit ffbd31e9f7
4 changed files with 124 additions and 41 deletions

View file

@ -61,10 +61,11 @@ import (
// If the JSON array is smaller than the Go array,
// the additional Go array elements are set to zero values.
//
// To unmarshal a JSON object into a string-keyed map, Unmarshal first
// establishes a map to 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.
// 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
// 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.
//
// If a JSON value is not appropriate for a given target type,
// or if a JSON number overflows the target type, Unmarshal
@ -549,6 +550,7 @@ func (d *decodeState) array(v reflect.Value) {
}
var nullLiteral = []byte("null")
var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem()
// object consumes an object from d.data[d.off-1:], decoding into the value v.
// the first byte ('{') of the object has been read already.
@ -577,12 +579,15 @@ func (d *decodeState) object(v reflect.Value) {
return
}
// Check type of target: struct or map[string]T
// Check type of target:
// struct or
// map[string]T or map[encoding.TextUnmarshaler]T
switch v.Kind() {
case reflect.Map:
// map must have string kind
// Map key must either have string kind or be an encoding.TextUnmarshaler.
t := v.Type()
if t.Key().Kind() != reflect.String {
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
@ -687,7 +692,18 @@ func (d *decodeState) object(v reflect.Value) {
// Write value back to map;
// if using struct, subv points into struct already.
if v.Kind() == reflect.Map {
kv := reflect.ValueOf(key).Convert(v.Type().Key())
kt := v.Type().Key()
var kv reflect.Value
switch {
case kt.Kind() == reflect.String:
kv = reflect.ValueOf(key).Convert(v.Type().Key())
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
}
v.SetMapIndex(kv, subv)
}