Commit bec40ba5 authored by Russ Cox's avatar Russ Cox

json: preserve field name case by default

This matches the old JSON package behavior.
All lowercase names are not as standard as I believed,
and it seems less surprising to need to write

	type T struct { Field string "field" }

to get lower case (behavior after this CL) than it does to need
to write

	type T struct { Field string "Field" }

to preserve the case (behavior before this CL).

Also test and  fix unmarshal into non-nil interface
value or pointer.

Fixes #744.

R=r
CC=golang-dev
https://golang.org/cl/1013041
parent cc62bed0
...@@ -20,7 +20,7 @@ import ( ...@@ -20,7 +20,7 @@ import (
) )
// Unmarshal parses the JSON-encoded data and stores the result // Unmarshal parses the JSON-encoded data and stores the result
// in the value pointed at by v. // in the value pointed to by v.
// //
// Unmarshal traverses the value v recursively. // Unmarshal traverses the value v recursively.
// If an encountered value implements the Unmarshaler interface, // If an encountered value implements the Unmarshaler interface,
...@@ -247,6 +247,10 @@ func (d *decodeState) indirect(v reflect.Value, wantptr bool) (Unmarshaler, refl ...@@ -247,6 +247,10 @@ func (d *decodeState) indirect(v reflect.Value, wantptr bool) (Unmarshaler, refl
_, isUnmarshaler = v.Interface().(Unmarshaler) _, isUnmarshaler = v.Interface().(Unmarshaler)
} }
if iv, ok := v.(*reflect.InterfaceValue); ok && !iv.IsNil() {
v = iv.Elem()
continue
}
pv, ok := v.(*reflect.PtrValue) pv, ok := v.(*reflect.PtrValue)
if !ok { if !ok {
break break
...@@ -255,7 +259,9 @@ func (d *decodeState) indirect(v reflect.Value, wantptr bool) (Unmarshaler, refl ...@@ -255,7 +259,9 @@ func (d *decodeState) indirect(v reflect.Value, wantptr bool) (Unmarshaler, refl
if !isptrptr && wantptr && !isUnmarshaler { if !isptrptr && wantptr && !isUnmarshaler {
return nil, pv return nil, pv
} }
pv.PointTo(reflect.MakeZero(pv.Type().(*reflect.PtrType).Elem())) if pv.IsNil() {
pv.PointTo(reflect.MakeZero(pv.Type().(*reflect.PtrType).Elem()))
}
if isUnmarshaler { if isUnmarshaler {
// Using v.Interface().(Unmarshaler) // Using v.Interface().(Unmarshaler)
// here means that we have to use a pointer // here means that we have to use a pointer
...@@ -436,11 +442,12 @@ func (d *decodeState) object(v reflect.Value) { ...@@ -436,11 +442,12 @@ func (d *decodeState) object(v reflect.Value) {
d.error(errPhase) d.error(errPhase)
} }
// Figure out // Figure out field corresponding to key.
var subv reflect.Value var subv reflect.Value
if mv != nil { if mv != nil {
subv = reflect.MakeZero(mv.Type().(*reflect.MapType).Elem()) subv = reflect.MakeZero(mv.Type().(*reflect.MapType).Elem())
} else { } else {
// First try for field with that tag.
for i := 0; i < sv.NumField(); i++ { for i := 0; i < sv.NumField(); i++ {
f := sv.Type().(*reflect.StructType).Field(i) f := sv.Type().(*reflect.StructType).Field(i)
if f.Tag == key { if f.Tag == key {
...@@ -449,7 +456,12 @@ func (d *decodeState) object(v reflect.Value) { ...@@ -449,7 +456,12 @@ func (d *decodeState) object(v reflect.Value) {
} }
} }
if subv == nil { if subv == nil {
subv = sv.FieldByNameFunc(func(s string) bool { return matchName(key, s) }) // Second, exact match.
subv = sv.FieldByName(key)
if subv == nil {
// Third, case-insensitive match.
subv = sv.FieldByNameFunc(func(s string) bool { return matchName(key, s) })
}
} }
} }
......
...@@ -104,6 +104,32 @@ func TestUnmarshalMarshal(t *testing.T) { ...@@ -104,6 +104,32 @@ func TestUnmarshalMarshal(t *testing.T) {
} }
} }
type Xint struct {
X int
}
func TestUnmarshalInterface(t *testing.T) {
var xint Xint
var i interface{} = &xint
if err := Unmarshal([]byte(`{"X":1}`), &i); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if xint.X != 1 {
t.Fatalf("Did not write to xint")
}
}
func TestUnmarshalPtrPtr(t *testing.T) {
var xint Xint
pxint := &xint
if err := Unmarshal([]byte(`{"X":1}`), &pxint); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if xint.X != 1 {
t.Fatalf("Did not write to xint")
}
}
func noSpace(c int) int { func noSpace(c int) int {
if isSpace(c) { if isSpace(c) {
return -1 return -1
...@@ -243,185 +269,185 @@ var pallValue = All{ ...@@ -243,185 +269,185 @@ var pallValue = All{
} }
var allValueIndent = `{ var allValueIndent = `{
"bool": true, "Bool": true,
"int": 2, "Int": 2,
"int8": 3, "Int8": 3,
"int16": 4, "Int16": 4,
"int32": 5, "Int32": 5,
"int64": 6, "Int64": 6,
"uint": 7, "Uint": 7,
"uint8": 8, "Uint8": 8,
"uint16": 9, "Uint16": 9,
"uint32": 10, "Uint32": 10,
"uint64": 11, "Uint64": 11,
"uintptr": 12, "Uintptr": 12,
"float": 13.1, "Float": 13.1,
"float32": 14.1, "Float32": 14.1,
"float64": 15.1, "Float64": 15.1,
"bar": "foo", "bar": "foo",
"pbool": null, "PBool": null,
"pint": null, "PInt": null,
"pint8": null, "PInt8": null,
"pint16": null, "PInt16": null,
"pint32": null, "PInt32": null,
"pint64": null, "PInt64": null,
"puint": null, "PUint": null,
"puint8": null, "PUint8": null,
"puint16": null, "PUint16": null,
"puint32": null, "PUint32": null,
"puint64": null, "PUint64": null,
"puintptr": null, "PUintptr": null,
"pfloat": null, "PFloat": null,
"pfloat32": null, "PFloat32": null,
"pfloat64": null, "PFloat64": null,
"string": "16", "String": "16",
"pstring": null, "PString": null,
"map": { "Map": {
"17": { "17": {
"tag": "tag17" "Tag": "tag17"
}, },
"18": { "18": {
"tag": "tag18" "Tag": "tag18"
} }
}, },
"mapp": { "MapP": {
"19": { "19": {
"tag": "tag19" "Tag": "tag19"
}, },
"20": null "20": null
}, },
"pmap": null, "PMap": null,
"pmapp": null, "PMapP": null,
"emptymap": {}, "EmptyMap": {},
"nilmap": null, "NilMap": null,
"slice": [ "Slice": [
{ {
"tag": "tag20" "Tag": "tag20"
}, },
{ {
"tag": "tag21" "Tag": "tag21"
} }
], ],
"slicep": [ "SliceP": [
{ {
"tag": "tag22" "Tag": "tag22"
}, },
null, null,
{ {
"tag": "tag23" "Tag": "tag23"
} }
], ],
"pslice": null, "PSlice": null,
"pslicep": null, "PSliceP": null,
"emptyslice": [], "EmptySlice": [],
"nilslice": [], "NilSlice": [],
"stringslice": [ "StringSlice": [
"str24", "str24",
"str25", "str25",
"str26" "str26"
], ],
"byteslice": [ "ByteSlice": [
27, 27,
28, 28,
29 29
], ],
"small": { "Small": {
"tag": "tag30" "Tag": "tag30"
}, },
"psmall": { "PSmall": {
"tag": "tag31" "Tag": "tag31"
}, },
"ppsmall": null, "PPSmall": null,
"interface": 5.2, "Interface": 5.2,
"pinterface": null "PInterface": null
}` }`
var allValueCompact = strings.Map(noSpace, allValueIndent) var allValueCompact = strings.Map(noSpace, allValueIndent)
var pallValueIndent = `{ var pallValueIndent = `{
"bool": false, "Bool": false,
"int": 0, "Int": 0,
"int8": 0, "Int8": 0,
"int16": 0, "Int16": 0,
"int32": 0, "Int32": 0,
"int64": 0, "Int64": 0,
"uint": 0, "Uint": 0,
"uint8": 0, "Uint8": 0,
"uint16": 0, "Uint16": 0,
"uint32": 0, "Uint32": 0,
"uint64": 0, "Uint64": 0,
"uintptr": 0, "Uintptr": 0,
"float": 0, "Float": 0,
"float32": 0, "Float32": 0,
"float64": 0, "Float64": 0,
"bar": "", "bar": "",
"pbool": true, "PBool": true,
"pint": 2, "PInt": 2,
"pint8": 3, "PInt8": 3,
"pint16": 4, "PInt16": 4,
"pint32": 5, "PInt32": 5,
"pint64": 6, "PInt64": 6,
"puint": 7, "PUint": 7,
"puint8": 8, "PUint8": 8,
"puint16": 9, "PUint16": 9,
"puint32": 10, "PUint32": 10,
"puint64": 11, "PUint64": 11,
"puintptr": 12, "PUintptr": 12,
"pfloat": 13.1, "PFloat": 13.1,
"pfloat32": 14.1, "PFloat32": 14.1,
"pfloat64": 15.1, "PFloat64": 15.1,
"string": "", "String": "",
"pstring": "16", "PString": "16",
"map": null, "Map": null,
"mapp": null, "MapP": null,
"pmap": { "PMap": {
"17": { "17": {
"tag": "tag17" "Tag": "tag17"
}, },
"18": { "18": {
"tag": "tag18" "Tag": "tag18"
} }
}, },
"pmapp": { "PMapP": {
"19": { "19": {
"tag": "tag19" "Tag": "tag19"
}, },
"20": null "20": null
}, },
"emptymap": null, "EmptyMap": null,
"nilmap": null, "NilMap": null,
"slice": [], "Slice": [],
"slicep": [], "SliceP": [],
"pslice": [ "PSlice": [
{ {
"tag": "tag20" "Tag": "tag20"
}, },
{ {
"tag": "tag21" "Tag": "tag21"
} }
], ],
"pslicep": [ "PSliceP": [
{ {
"tag": "tag22" "Tag": "tag22"
}, },
null, null,
{ {
"tag": "tag23" "Tag": "tag23"
} }
], ],
"emptyslice": [], "EmptySlice": [],
"nilslice": [], "NilSlice": [],
"stringslice": [], "StringSlice": [],
"byteslice": [], "ByteSlice": [],
"small": { "Small": {
"tag": "" "Tag": ""
}, },
"psmall": null, "PSmall": null,
"ppsmall": { "PPSmall": {
"tag": "tag31" "Tag": "tag31"
}, },
"interface": null, "Interface": null,
"pinterface": 5.2 "PInterface": 5.2
}` }`
var pallValueCompact = strings.Map(noSpace, pallValueIndent) var pallValueCompact = strings.Map(noSpace, pallValueIndent)
...@@ -11,7 +11,6 @@ import ( ...@@ -11,7 +11,6 @@ import (
"runtime" "runtime"
"sort" "sort"
"strconv" "strconv"
"strings"
) )
// Marshal returns the JSON encoding of v. // Marshal returns the JSON encoding of v.
...@@ -40,7 +39,7 @@ import ( ...@@ -40,7 +39,7 @@ import (
// The map's key type must be string; the object keys are used directly // The map's key type must be string; the object keys are used directly
// as map keys. // as map keys.
// //
// Pointer values encode as the value pointed at. // Pointer values encode as the value pointed to.
// A nil pointer encodes as the null JSON object. // A nil pointer encodes as the null JSON object.
// //
// Interface values encode as the value contained in the interface. // Interface values encode as the value contained in the interface.
...@@ -202,7 +201,7 @@ func (e *encodeState) reflectValue(v reflect.Value) { ...@@ -202,7 +201,7 @@ func (e *encodeState) reflectValue(v reflect.Value) {
if f.Tag != "" { if f.Tag != "" {
e.string(f.Tag) e.string(f.Tag)
} else { } else {
e.string(strings.ToLower(f.Name)) e.string(f.Name)
} }
e.WriteByte(':') e.WriteByte(':')
e.reflectValue(v.Field(i)) e.reflectValue(v.Field(i))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment