From 18a241d40bcc2f8014a5273f0d789f39efa57284 Mon Sep 17 00:00:00 2001 From: Oleg Shaldybin Date: Thu, 14 Sep 2017 16:20:27 -0700 Subject: [PATCH] Allow null booleans Make sure we do the same thing as stdlib with null booleans by not touching the original value and discarding the null. Another somewhat related change is nulling out null interface values in the original structure. This also matches stdlib behavior. --- feature_reflect_native.go | 8 +++++++- jsoniter_bool_test.go | 29 +++++++++++++++++++++++++++ jsoniter_interface_test.go | 40 +++++++++++++++++++++++++++++++++++++- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/feature_reflect_native.go b/feature_reflect_native.go index 6844760..9bd290b 100644 --- a/feature_reflect_native.go +++ b/feature_reflect_native.go @@ -331,7 +331,9 @@ type boolCodec struct { } func (codec *boolCodec) Decode(ptr unsafe.Pointer, iter *Iterator) { - *((*bool)(ptr)) = iter.ReadBool() + if !iter.ReadNil() { + *((*bool)(ptr)) = iter.ReadBool() + } } func (codec *boolCodec) Encode(ptr unsafe.Pointer, stream *Stream) { @@ -350,6 +352,10 @@ type emptyInterfaceCodec struct { } func (codec *emptyInterfaceCodec) Decode(ptr unsafe.Pointer, iter *Iterator) { + if iter.ReadNil() { + *((*interface{})(ptr)) = nil + return + } existing := *((*interface{})(ptr)) if existing != nil && reflect.TypeOf(existing).Kind() == reflect.Ptr { iter.ReadVal(existing) diff --git a/jsoniter_bool_test.go b/jsoniter_bool_test.go index 5f6a824..5a9aa75 100644 --- a/jsoniter_bool_test.go +++ b/jsoniter_bool_test.go @@ -82,3 +82,32 @@ func Test_decode_string_bool(t *testing.T) { err = Unmarshal([]byte(`{"Field":true}`), &obj) should.NotNil(err) } + +func Test_bool_can_be_null(t *testing.T) { + type TestData struct { + Field bool `json:"field"` + } + should := require.New(t) + + obj := TestData{} + data1 := []byte(`{"field": true}`) + err := Unmarshal(data1, &obj) + should.Equal(nil, err) + should.Equal(true, obj.Field) + + data2 := []byte(`{"field": null}`) + err = Unmarshal(data2, &obj) + should.Equal(nil, err) + // Same behavior as stdlib, not touching the existing value. + should.Equal(true, obj.Field) + + // Checking stdlib behavior as well + obj2 := TestData{} + err = json.Unmarshal(data1, &obj2) + should.Equal(nil, err) + should.Equal(true, obj2.Field) + + err = json.Unmarshal(data2, &obj2) + should.Equal(nil, err) + should.Equal(true, obj2.Field) +} diff --git a/jsoniter_interface_test.go b/jsoniter_interface_test.go index f9d368b..0e7ef88 100644 --- a/jsoniter_interface_test.go +++ b/jsoniter_interface_test.go @@ -3,9 +3,10 @@ package jsoniter import ( "encoding/json" "fmt" - "github.com/stretchr/testify/require" "testing" "unsafe" + + "github.com/stretchr/testify/require" ) func Test_write_array_of_interface(t *testing.T) { @@ -313,3 +314,40 @@ func Test_unmarshal_ptr_to_interface(t *testing.T) { should.Nil(err) should.Equal("&{value}", fmt.Sprintf("%v", obj)) } + +func Test_nil_out_null_interface(t *testing.T) { + type TestData struct { + Field interface{} `json:"field"` + } + should := require.New(t) + + var boolVar bool + obj := TestData{ + Field: &boolVar, + } + + data1 := []byte(`{"field": true}`) + + err := Unmarshal(data1, &obj) + should.Equal(nil, err) + should.Equal(true, *(obj.Field.(*bool))) + + data2 := []byte(`{"field": null}`) + + err = Unmarshal(data2, &obj) + should.Equal(nil, err) + should.Equal(nil, obj.Field) + + // Checking stdlib behavior matches. + obj2 := TestData{ + Field: &boolVar, + } + + err = json.Unmarshal(data1, &obj2) + should.Equal(nil, err) + should.Equal(true, *(obj2.Field.(*bool))) + + err = json.Unmarshal(data2, &obj2) + should.Equal(nil, err) + should.Equal(nil, obj2.Field) +}