From 1f58120d43fd36d041cfa3b747013e71389879f5 Mon Sep 17 00:00:00 2001 From: Oleg Shaldybin Date: Mon, 18 Sep 2017 10:52:01 -0700 Subject: [PATCH 1/2] Always skip unexported fields when encoding Skip creating encoders for unexported fields. They are not participating in JSON marshaling anyway. This allows using unexported fields of non-marshalable types in structs. As a side-effect of this change it's no longer possible to marshal unexported JSON fields by adding a custom type extenstion. It seems this is desired behavior since it matches standard library and jsoniter already disallows `json:"-"` fields from participating in custom extensions. Fixes #174. --- feature_reflect_extension.go | 3 ++ jsoniter_customize_test.go | 9 +++--- jsoniter_struct_encoder_test.go | 52 +++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 jsoniter_struct_encoder_test.go diff --git a/feature_reflect_extension.go b/feature_reflect_extension.go index 3dd3829..508aef2 100644 --- a/feature_reflect_extension.go +++ b/feature_reflect_extension.go @@ -227,6 +227,9 @@ func describeStruct(cfg *frozenConfig, typ reflect.Type) (*StructDescriptor, err bindings := []*Binding{} for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) + if unicode.IsLower([]rune(field.Name)[0]) { + continue + } tag := field.Tag.Get(cfg.getTagKey()) tagParts := strings.Split(tag, ",") if tag == "-" { diff --git a/jsoniter_customize_test.go b/jsoniter_customize_test.go index b4bf529..628f89e 100644 --- a/jsoniter_customize_test.go +++ b/jsoniter_customize_test.go @@ -2,11 +2,12 @@ package jsoniter import ( "encoding/json" - "github.com/stretchr/testify/require" "strconv" "testing" "time" "unsafe" + + "github.com/stretchr/testify/require" ) func Test_customize_type_decoder(t *testing.T) { @@ -82,7 +83,7 @@ func Test_customize_field_decoder(t *testing.T) { } type TestObject1 struct { - field1 string + Field1 string } type testExtension struct { @@ -93,7 +94,7 @@ func (extension *testExtension) UpdateStructDescriptor(structDescriptor *StructD if structDescriptor.Type.String() != "jsoniter.TestObject1" { return } - binding := structDescriptor.GetField("field1") + binding := structDescriptor.GetField("Field1") binding.Encoder = &funcEncoder{fun: func(ptr unsafe.Pointer, stream *Stream) { str := *((*string)(ptr)) val, _ := strconv.Atoi(str) @@ -112,7 +113,7 @@ func Test_customize_field_by_extension(t *testing.T) { obj := TestObject1{} err := UnmarshalFromString(`{"field-1": 100}`, &obj) should.Nil(err) - should.Equal("100", obj.field1) + should.Equal("100", obj.Field1) str, err := MarshalToString(obj) should.Nil(err) should.Equal(`{"field-1":100}`, str) diff --git a/jsoniter_struct_encoder_test.go b/jsoniter_struct_encoder_test.go new file mode 100644 index 0000000..0e3e541 --- /dev/null +++ b/jsoniter_struct_encoder_test.go @@ -0,0 +1,52 @@ +package jsoniter + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func Test_encode_unexported_field(t *testing.T) { + type TestData struct { + a int + b <-chan int + C int + d *time.Timer + } + + should := require.New(t) + + testChan := make(<-chan int, 10) + testTimer := time.NewTimer(10 * time.Second) + + obj := &TestData{ + a: 42, + b: testChan, + C: 21, + d: testTimer, + } + + jb, err := json.Marshal(obj) + should.NoError(err) + should.Equal([]byte(`{"C":21}`), jb) + + err = json.Unmarshal([]byte(`{"a": 444, "b":"bad", "C":55, "d":{"not": "a timer"}}`), obj) + should.NoError(err) + should.Equal(42, obj.a) + should.Equal(testChan, obj.b) + should.Equal(55, obj.C) + should.Equal(testTimer, obj.d) + + jb, err = Marshal(obj) + should.NoError(err) + should.Equal(jb, []byte(`{"C":55}`)) + + err = Unmarshal([]byte(`{"a": 444, "b":"bad", "C":256, "d":{"not":"a timer"}}`), obj) + should.NoError(err) + should.Equal(42, obj.a) + should.Equal(testChan, obj.b) + should.Equal(256, obj.C) + should.Equal(testTimer, obj.d) +} From faa3dcf46a17bebd43a437edad21d26156e0e76f Mon Sep 17 00:00:00 2001 From: Tao Wen Date: Tue, 19 Sep 2017 10:06:34 +0800 Subject: [PATCH 2/2] do not report error when field is unexported --- feature_reflect_extension.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/feature_reflect_extension.go b/feature_reflect_extension.go index 508aef2..74f4b8b 100644 --- a/feature_reflect_extension.go +++ b/feature_reflect_extension.go @@ -227,9 +227,6 @@ func describeStruct(cfg *frozenConfig, typ reflect.Type) (*StructDescriptor, err bindings := []*Binding{} for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) - if unicode.IsLower([]rune(field.Name)[0]) { - continue - } tag := field.Tag.Get(cfg.getTagKey()) tagParts := strings.Split(tag, ",") if tag == "-" { @@ -272,7 +269,7 @@ func describeStruct(cfg *frozenConfig, typ reflect.Type) (*StructDescriptor, err if decoder == nil { var err error decoder, err = decoderOfType(cfg, field.Type) - if err != nil { + if len(fieldNames) > 0 && err != nil { return nil, err } } @@ -280,11 +277,11 @@ func describeStruct(cfg *frozenConfig, typ reflect.Type) (*StructDescriptor, err if encoder == nil { var err error encoder, err = encoderOfType(cfg, field.Type) - if err != nil { + if len(fieldNames) > 0 && err != nil { return nil, err } // map is stored as pointer in the struct - if field.Type.Kind() == reflect.Map { + if encoder != nil && field.Type.Kind() == reflect.Map { encoder = &optionalEncoder{encoder} } }