From 585ce46b3184c8030d25516a63ad9d8bdf708410 Mon Sep 17 00:00:00 2001 From: ebauer Date: Wed, 23 Jun 2021 14:22:24 +0200 Subject: [PATCH 1/7] add func decoder --- internal/decoder/compile.go | 10 ++++ internal/decoder/func.go | 108 ++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 internal/decoder/func.go diff --git a/internal/decoder/compile.go b/internal/decoder/compile.go index bd56687..08dd044 100644 --- a/internal/decoder/compile.go +++ b/internal/decoder/compile.go @@ -123,11 +123,15 @@ func compile(typ *runtime.Type, structName, fieldName string, structTypeToDecode return compileFloat32(structName, fieldName) case reflect.Float64: return compileFloat64(structName, fieldName) + case reflect.Func: + return compileFunc(typ, structName, fieldName) } return nil, &errors.UnmarshalTypeError{ Value: "object", Type: runtime.RType2Type(typ), Offset: 0, + Struct: structName, + Field: fieldName, } } @@ -178,6 +182,8 @@ ERROR: Value: "object", Type: runtime.RType2Type(typ), Offset: 0, + Struct: structName, + Field: fieldName, } } @@ -312,6 +318,10 @@ func compileInterface(typ *runtime.Type, structName, fieldName string) (Decoder, return newInterfaceDecoder(typ, structName, fieldName), nil } +func compileFunc(typ *runtime.Type, strutName, fieldName string) (Decoder, error) { + return newFuncDecoder(typ, strutName, fieldName), nil +} + func removeConflictFields(fieldMap map[string]*structFieldSet, conflictedMap map[string]struct{}, dec *structDecoder, field reflect.StructField) { for k, v := range dec.fieldMap { if _, exists := conflictedMap[k]; exists { diff --git a/internal/decoder/func.go b/internal/decoder/func.go new file mode 100644 index 0000000..54daaba --- /dev/null +++ b/internal/decoder/func.go @@ -0,0 +1,108 @@ +package decoder + +import ( + "bytes" + "unsafe" + + "github.com/goccy/go-json/internal/errors" + "github.com/goccy/go-json/internal/runtime" +) + +type funcDecoder struct { + typ *runtime.Type + structName string + fieldName string +} + +func newFuncDecoder(typ *runtime.Type, structName, fieldName string) *funcDecoder { + fnDecoder := &funcDecoder{typ, structName, fieldName} + return fnDecoder +} + +func (d *funcDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { + s.skipWhiteSpace() + start := s.cursor + if err := s.skipValue(depth); err != nil { + return err + } + src := s.buf[start:s.cursor] + if len(src) > 0 { + switch src[0] { + case '"': + return &errors.UnmarshalTypeError{ + Value: "string", + Type: runtime.RType2Type(d.typ), + Offset: s.totalOffset(), + } + case '[': + return &errors.UnmarshalTypeError{ + Value: "array", + Type: runtime.RType2Type(d.typ), + Offset: s.totalOffset(), + } + case '{': + return &errors.UnmarshalTypeError{ + Value: "object", + Type: runtime.RType2Type(d.typ), + Offset: s.totalOffset(), + } + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + return &errors.UnmarshalTypeError{ + Value: "number", + Type: runtime.RType2Type(d.typ), + Offset: s.totalOffset(), + } + case 'n': + if bytes.Equal(src, nullbytes) { + *(*unsafe.Pointer)(p) = nil + return nil + } + } + } + return errors.ErrNotAtBeginningOfValue(start) +} + +func (d *funcDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { + buf := ctx.Buf + cursor = skipWhiteSpace(buf, cursor) + start := cursor + end, err := skipValue(buf, cursor, depth) + if err != nil { + return 0, err + } + src := buf[start:end] + if len(src) > 0 { + switch src[0] { + case '"': + return 0, &errors.UnmarshalTypeError{ + Value: "string", + Type: runtime.RType2Type(d.typ), + Offset: start, + } + case '[': + return 0, &errors.UnmarshalTypeError{ + Value: "array", + Type: runtime.RType2Type(d.typ), + Offset: start, + } + case '{': + return 0, &errors.UnmarshalTypeError{ + Value: "object", + Type: runtime.RType2Type(d.typ), + Offset: start, + } + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + return 0, &errors.UnmarshalTypeError{ + Value: "number", + Type: runtime.RType2Type(d.typ), + Offset: start, + } + case 'n': + if bytes.Equal(src, nullbytes) { + *(*unsafe.Pointer)(p) = nil + return end, nil + } + } + } + return 0, errors.ErrNotAtBeginningOfValue(start) +} From d34d79600a0bed41323814da6c92d6dc193e9a8b Mon Sep 17 00:00:00 2001 From: ebauer Date: Wed, 23 Jun 2021 14:22:58 +0200 Subject: [PATCH 2/7] add func Unmarshal tests --- decode_test.go | 14 ++++++++++++-- helper_test.go | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/decode_test.go b/decode_test.go index fb00c36..f8e7a9f 100644 --- a/decode_test.go +++ b/decode_test.go @@ -152,6 +152,7 @@ func Test_Decoder(t *testing.T) { B string `json:"str"` C bool D *T + E func() } content := []byte(` { @@ -162,7 +163,8 @@ func Test_Decoder(t *testing.T) { "aa": 2, "bb": "world", "cc": true - } + }, + "e" : null }`) assertErr(t, json.Unmarshal(content, &v)) assertEq(t, "struct.A", 123, v.A) @@ -171,6 +173,7 @@ func Test_Decoder(t *testing.T) { assertEq(t, "struct.D.AA", 2, v.D.AA) assertEq(t, "struct.D.BB", "world", v.D.BB) assertEq(t, "struct.D.CC", true, v.D.CC) + assertEq(t, "struct.E", nil, v.E) t.Run("struct.field null", func(t *testing.T) { var v struct { A string @@ -179,8 +182,9 @@ func Test_Decoder(t *testing.T) { D map[string]interface{} E [2]string F interface{} + G func() } - assertErr(t, json.Unmarshal([]byte(`{"a":null,"b":null,"c":null,"d":null,"e":null,"f":null}`), &v)) + assertErr(t, json.Unmarshal([]byte(`{"a":null,"b":null,"c":null,"d":null,"e":null,"f":null,"g":null}`), &v)) assertEq(t, "string", v.A, "") assertNeq(t, "[]string", v.B, nil) assertEq(t, "[]string", len(v.B), 0) @@ -191,6 +195,7 @@ func Test_Decoder(t *testing.T) { assertNeq(t, "array", v.E, nil) assertEq(t, "array", len(v.E), 2) assertEq(t, "interface{}", v.F, nil) + assertEq(t, "func", v.G, nil) }) }) t.Run("interface", func(t *testing.T) { @@ -239,6 +244,11 @@ func Test_Decoder(t *testing.T) { assertEq(t, "interface", nil, v) }) }) + t.Run("func", func(t *testing.T) { + var v func() + assertErr(t, json.Unmarshal([]byte(`null`), &v)) + assertEq(t, "func", nil, v) + }) } func TestIssue98(t *testing.T) { diff --git a/helper_test.go b/helper_test.go index 53157ce..4175a9a 100644 --- a/helper_test.go +++ b/helper_test.go @@ -11,7 +11,7 @@ func assertErr(t *testing.T, err error) { func assertEq(t *testing.T, msg string, exp interface{}, act interface{}) { t.Helper() - if exp != act { + if exp != act && exp != nil && act != nil { t.Fatalf("failed to test for %s. exp=[%v] but act=[%v]", msg, exp, act) } } From a7d041a3d4c7def82a95296e747fc29101c99196 Mon Sep 17 00:00:00 2001 From: ebauer Date: Wed, 23 Jun 2021 15:22:40 +0200 Subject: [PATCH 3/7] gofmt -s --- internal/decoder/func.go | 216 +++++++++++++++++++-------------------- 1 file changed, 108 insertions(+), 108 deletions(-) diff --git a/internal/decoder/func.go b/internal/decoder/func.go index 54daaba..b7d2447 100644 --- a/internal/decoder/func.go +++ b/internal/decoder/func.go @@ -1,108 +1,108 @@ -package decoder - -import ( - "bytes" - "unsafe" - - "github.com/goccy/go-json/internal/errors" - "github.com/goccy/go-json/internal/runtime" -) - -type funcDecoder struct { - typ *runtime.Type - structName string - fieldName string -} - -func newFuncDecoder(typ *runtime.Type, structName, fieldName string) *funcDecoder { - fnDecoder := &funcDecoder{typ, structName, fieldName} - return fnDecoder -} - -func (d *funcDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { - s.skipWhiteSpace() - start := s.cursor - if err := s.skipValue(depth); err != nil { - return err - } - src := s.buf[start:s.cursor] - if len(src) > 0 { - switch src[0] { - case '"': - return &errors.UnmarshalTypeError{ - Value: "string", - Type: runtime.RType2Type(d.typ), - Offset: s.totalOffset(), - } - case '[': - return &errors.UnmarshalTypeError{ - Value: "array", - Type: runtime.RType2Type(d.typ), - Offset: s.totalOffset(), - } - case '{': - return &errors.UnmarshalTypeError{ - Value: "object", - Type: runtime.RType2Type(d.typ), - Offset: s.totalOffset(), - } - case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - return &errors.UnmarshalTypeError{ - Value: "number", - Type: runtime.RType2Type(d.typ), - Offset: s.totalOffset(), - } - case 'n': - if bytes.Equal(src, nullbytes) { - *(*unsafe.Pointer)(p) = nil - return nil - } - } - } - return errors.ErrNotAtBeginningOfValue(start) -} - -func (d *funcDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { - buf := ctx.Buf - cursor = skipWhiteSpace(buf, cursor) - start := cursor - end, err := skipValue(buf, cursor, depth) - if err != nil { - return 0, err - } - src := buf[start:end] - if len(src) > 0 { - switch src[0] { - case '"': - return 0, &errors.UnmarshalTypeError{ - Value: "string", - Type: runtime.RType2Type(d.typ), - Offset: start, - } - case '[': - return 0, &errors.UnmarshalTypeError{ - Value: "array", - Type: runtime.RType2Type(d.typ), - Offset: start, - } - case '{': - return 0, &errors.UnmarshalTypeError{ - Value: "object", - Type: runtime.RType2Type(d.typ), - Offset: start, - } - case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - return 0, &errors.UnmarshalTypeError{ - Value: "number", - Type: runtime.RType2Type(d.typ), - Offset: start, - } - case 'n': - if bytes.Equal(src, nullbytes) { - *(*unsafe.Pointer)(p) = nil - return end, nil - } - } - } - return 0, errors.ErrNotAtBeginningOfValue(start) -} +package decoder + +import ( + "bytes" + "unsafe" + + "github.com/goccy/go-json/internal/errors" + "github.com/goccy/go-json/internal/runtime" +) + +type funcDecoder struct { + typ *runtime.Type + structName string + fieldName string +} + +func newFuncDecoder(typ *runtime.Type, structName, fieldName string) *funcDecoder { + fnDecoder := &funcDecoder{typ, structName, fieldName} + return fnDecoder +} + +func (d *funcDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error { + s.skipWhiteSpace() + start := s.cursor + if err := s.skipValue(depth); err != nil { + return err + } + src := s.buf[start:s.cursor] + if len(src) > 0 { + switch src[0] { + case '"': + return &errors.UnmarshalTypeError{ + Value: "string", + Type: runtime.RType2Type(d.typ), + Offset: s.totalOffset(), + } + case '[': + return &errors.UnmarshalTypeError{ + Value: "array", + Type: runtime.RType2Type(d.typ), + Offset: s.totalOffset(), + } + case '{': + return &errors.UnmarshalTypeError{ + Value: "object", + Type: runtime.RType2Type(d.typ), + Offset: s.totalOffset(), + } + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + return &errors.UnmarshalTypeError{ + Value: "number", + Type: runtime.RType2Type(d.typ), + Offset: s.totalOffset(), + } + case 'n': + if bytes.Equal(src, nullbytes) { + *(*unsafe.Pointer)(p) = nil + return nil + } + } + } + return errors.ErrNotAtBeginningOfValue(start) +} + +func (d *funcDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) { + buf := ctx.Buf + cursor = skipWhiteSpace(buf, cursor) + start := cursor + end, err := skipValue(buf, cursor, depth) + if err != nil { + return 0, err + } + src := buf[start:end] + if len(src) > 0 { + switch src[0] { + case '"': + return 0, &errors.UnmarshalTypeError{ + Value: "string", + Type: runtime.RType2Type(d.typ), + Offset: start, + } + case '[': + return 0, &errors.UnmarshalTypeError{ + Value: "array", + Type: runtime.RType2Type(d.typ), + Offset: start, + } + case '{': + return 0, &errors.UnmarshalTypeError{ + Value: "object", + Type: runtime.RType2Type(d.typ), + Offset: start, + } + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + return 0, &errors.UnmarshalTypeError{ + Value: "number", + Type: runtime.RType2Type(d.typ), + Offset: start, + } + case 'n': + if bytes.Equal(src, nullbytes) { + *(*unsafe.Pointer)(p) = nil + return end, nil + } + } + } + return 0, errors.ErrNotAtBeginningOfValue(start) +} From 5f0b34250c272a881ff65bff535693263540b9e3 Mon Sep 17 00:00:00 2001 From: ebauer Date: Wed, 23 Jun 2021 15:51:42 +0200 Subject: [PATCH 4/7] funcDecoder: handle cases of value being true or false --- internal/decoder/func.go | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/internal/decoder/func.go b/internal/decoder/func.go index b7d2447..75afe75 100644 --- a/internal/decoder/func.go +++ b/internal/decoder/func.go @@ -53,9 +53,26 @@ func (d *funcDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) err Offset: s.totalOffset(), } case 'n': - if bytes.Equal(src, nullbytes) { - *(*unsafe.Pointer)(p) = nil - return nil + if err := nullBytes(s); err != nil { + return err + } + *(*unsafe.Pointer)(p) = nil + return nil + case 't': + if err := trueBytes(s); err == nil { + return &errors.UnmarshalTypeError{ + Value: "boolean", + Type: runtime.RType2Type(d.typ), + Offset: s.totalOffset(), + } + } + case 'f': + if err := falseBytes(s); err == nil { + return &errors.UnmarshalTypeError{ + Value: "boolean", + Type: runtime.RType2Type(d.typ), + Offset: s.totalOffset(), + } } } } @@ -102,6 +119,22 @@ func (d *funcDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe. *(*unsafe.Pointer)(p) = nil return end, nil } + case 't': + if err := validateTrue(buf, start); err == nil { + return 0, &errors.UnmarshalTypeError{ + Value: "boolean", + Type: runtime.RType2Type(d.typ), + Offset: start, + } + } + case 'f': + if err := validateFalse(buf, start); err == nil { + return 0, &errors.UnmarshalTypeError{ + Value: "boolean", + Type: runtime.RType2Type(d.typ), + Offset: start, + } + } } } return 0, errors.ErrNotAtBeginningOfValue(start) From 4fe8e6f172fe12c72d6c07ba9c7df7bb378bb1b4 Mon Sep 17 00:00:00 2001 From: ebauer Date: Wed, 23 Jun 2021 16:00:41 +0200 Subject: [PATCH 5/7] change assertEq check --- helper_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper_test.go b/helper_test.go index 4175a9a..7cc717f 100644 --- a/helper_test.go +++ b/helper_test.go @@ -11,7 +11,7 @@ func assertErr(t *testing.T, err error) { func assertEq(t *testing.T, msg string, exp interface{}, act interface{}) { t.Helper() - if exp != act && exp != nil && act != nil { + if exp != act && !(exp == nil && act == nil) { t.Fatalf("failed to test for %s. exp=[%v] but act=[%v]", msg, exp, act) } } From 6a81ba12dd6057f6e09cf38fab499f6f9afd8a19 Mon Sep 17 00:00:00 2001 From: Kiraub Date: Wed, 23 Jun 2021 19:09:20 +0200 Subject: [PATCH 6/7] reverted helper --- helper_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper_test.go b/helper_test.go index 7cc717f..53157ce 100644 --- a/helper_test.go +++ b/helper_test.go @@ -11,7 +11,7 @@ func assertErr(t *testing.T, err error) { func assertEq(t *testing.T, msg string, exp interface{}, act interface{}) { t.Helper() - if exp != act && !(exp == nil && act == nil) { + if exp != act { t.Fatalf("failed to test for %s. exp=[%v] but act=[%v]", msg, exp, act) } } From 9d9e5cd11ac69d0d48292fdffa718e5c1ad0bbab Mon Sep 17 00:00:00 2001 From: Kiraub Date: Wed, 23 Jun 2021 19:10:00 +0200 Subject: [PATCH 7/7] inline nilfunc to nil comparison --- decode_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/decode_test.go b/decode_test.go index f8e7a9f..a0d4eda 100644 --- a/decode_test.go +++ b/decode_test.go @@ -173,7 +173,7 @@ func Test_Decoder(t *testing.T) { assertEq(t, "struct.D.AA", 2, v.D.AA) assertEq(t, "struct.D.BB", "world", v.D.BB) assertEq(t, "struct.D.CC", true, v.D.CC) - assertEq(t, "struct.E", nil, v.E) + assertEq(t, "struct.E", true, v.E == nil) t.Run("struct.field null", func(t *testing.T) { var v struct { A string @@ -195,7 +195,7 @@ func Test_Decoder(t *testing.T) { assertNeq(t, "array", v.E, nil) assertEq(t, "array", len(v.E), 2) assertEq(t, "interface{}", v.F, nil) - assertEq(t, "func", v.G, nil) + assertEq(t, "nilfunc", true, v.G == nil) }) }) t.Run("interface", func(t *testing.T) { @@ -247,7 +247,7 @@ func Test_Decoder(t *testing.T) { t.Run("func", func(t *testing.T) { var v func() assertErr(t, json.Unmarshal([]byte(`null`), &v)) - assertEq(t, "func", nil, v) + assertEq(t, "nilfunc", true, v == nil) }) }