Skip to content

Commit

Permalink
TOOLS-767 fix decoding 'null' JSON into bson.D
Browse files Browse the repository at this point in the history
  • Loading branch information
3rf committed Aug 18, 2015
1 parent d8c1c10 commit 57b66be
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 40 deletions.
49 changes: 9 additions & 40 deletions common/json/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -599,14 +599,18 @@ func (d *decodeState) object(v reflect.Value) {
if v.IsNil() {
v.Set(reflect.MakeMap(t))
}

case reflect.Struct:
// do nothing

case reflect.Slice:
// this is only a valid case if the output type is a bson.D
t := v.Type()
if t != orderedBSONType {
d.saveError(&UnmarshalTypeError{"object", v.Type()})
break
if t == orderedBSONType {
v.Set(reflect.ValueOf(d.bsonDInterface()))
return
}
case reflect.Struct:
fallthrough // can't unmarshal into a regular slice, goto "default" error case

default:
d.saveError(&UnmarshalTypeError{"object", v.Type()})
Expand All @@ -617,8 +621,6 @@ func (d *decodeState) object(v reflect.Value) {

var mapElem reflect.Value

i := 0
bsonDMode := false
for {
// Read opening " of string key or closing }.
op := d.scanWhile(scanSkipSpace)
Expand Down Expand Up @@ -651,14 +653,6 @@ func (d *decodeState) object(v reflect.Value) {
mapElem.Set(reflect.Zero(elemType))
}
subv = mapElem
} else if v.Kind() == reflect.Slice {
bsonDMode = true
elemType := interfaceType.Elem()
if !mapElem.IsValid() {
subv = reflect.New(elemType).Elem()
} else {
mapElem.Set(reflect.Zero(elemType))
}
} else {
var f *field
fields := cachedTypeFields(v.Type())
Expand Down Expand Up @@ -695,43 +689,19 @@ func (d *decodeState) object(v reflect.Value) {
d.error(errPhase)
}
// Read value.
var docElemValue interface{}
if destring {
d.value(reflect.ValueOf(&d.tempstr))
d.literalStore([]byte(d.tempstr), subv, true)
d.tempstr = "" // Zero scratch space for successive values.
} else {
if bsonDMode {
docElemValue = d.valueInterface()
subv.Set(reflect.ValueOf(docElemValue))
} else {
d.value(subv)
}
d.value(subv)
}

// 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())
v.SetMapIndex(kv, subv)
} else if v.Kind() == reflect.Slice {
kv := reflect.ValueOf(key).Convert(stringType)
newDocElem := &bson.DocElem{kv.String(), subv.Interface()}
// Slice construction/resizing code is from decodeState.array()
if i >= v.Cap() {
newcap := v.Cap() + v.Cap()/2
if newcap < 4 {
newcap = 4
}
newv := reflect.MakeSlice(v.Type(), v.Len(), newcap)
reflect.Copy(newv, v)
v.Set(newv)
}
if i >= v.Len() {
v.SetLen(i + 1)
}
v3 := v.Index(i)
v3.Set(reflect.ValueOf(newDocElem).Elem())
}

// Next token must be , or }.
Expand All @@ -742,7 +712,6 @@ func (d *decodeState) object(v reflect.Value) {
if op != scanObjectValue {
d.error(errPhase)
}
i++
}
}

Expand Down
33 changes: 33 additions & 0 deletions common/json/decode_d_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,24 @@ func TestDecodeBsonD(t *testing.T) {

})

Convey("A nested bson.D should be parsed", func() {
data := `{"a": 17, "b":{"foo":"bar", "baz":"boo"}, c:"wow" }`
out := struct {
A int `json:"a"`
B bson.D `json:"b"`
C string `json:"c"`
}{}
err := Unmarshal([]byte(data), &out)
So(err, ShouldBeNil)
So(out.A, ShouldEqual, 17)
So(out.C, ShouldEqual, "wow")
So(len(out.B), ShouldEqual, 2)
So(out.B[0].Name, ShouldEqual, "foo")
So(out.B[0].Value, ShouldEqual, "bar")
So(out.B[1].Name, ShouldEqual, "baz")
So(out.B[1].Value, ShouldEqual, "boo")
})

Convey("Objects nested within DocElems should still be parsed", func() {
data := `{"a":["x", "y","z"], "b":{"foo":"bar", "baz":"boo"}}`
out := bson.D{}
Expand All @@ -34,6 +52,21 @@ func TestDecodeBsonD(t *testing.T) {
So(out[0].Value, ShouldResemble, []interface{}{"x", "y", "z"})
So(out[1].Value, ShouldResemble, map[string]interface{}{"foo": "bar", "baz": "boo"})
})

Convey("null should be a valid value", func() {
data := `{"a":true, "b":null, "c": 5}`
out := bson.D{}
err := Unmarshal([]byte(data), &out)
So(err, ShouldBeNil)
So(len(out), ShouldEqual, 3)
So(out[0].Name, ShouldEqual, "a")
So(out[0].Value, ShouldEqual, true)
So(out[1].Name, ShouldEqual, "b")
So(out[1].Value, ShouldBeNil)
So(out[2].Name, ShouldEqual, "c")
So(out[2].Value, ShouldEqual, 5)
})

})
Convey("Unmarshalling to a non-bson.D slice types should fail", t, func() {
data := `{"a":["x", "y","z"], "b":{"foo":"bar", "baz":"boo"}}`
Expand Down

0 comments on commit 57b66be

Please sign in to comment.