Skip to content

Commit

Permalink
feat:(decoder) support skip mismatche-typed value (#325)
Browse files Browse the repository at this point in the history
* feat:(decoder) support skip mismatche-typed value

* change test cases

* refactor: add type check down into `CompilePrimitive()` to avoid repeat `null` check

* opt call skip()

* bench: add  option `--repeat_times`

* test: omit check primitive

* opt: inline primitive check into its OP

* implement on Go1.15

* fix: support skip json.Numer

* fix: OP_go_skip

* update README.md
  • Loading branch information
AsterDY committed Nov 4, 2022
1 parent 2866800 commit 02fe882
Show file tree
Hide file tree
Showing 11 changed files with 498 additions and 91 deletions.
21 changes: 18 additions & 3 deletions README.md
Expand Up @@ -181,16 +181,17 @@ ret, err := Encode(v, EscapeHTML) // ret == `{"\u0026\u0026":{"X":"\u003c\u003e"
### Compact Format
Sonic encodes primitive objects (struct/map...) as compact-format JSON by default, except marshaling `json.RawMessage` or `json.Marshaler`: sonic ensures validating their output JSON but **DONOT** compacting them for performance concerns. We provide the option `encoder.CompactMarshaler` to add compacting process.

### Print Syntax Error
### Print Error
If there invalid syntax in input JSON, sonic will return `decoder.SyntaxError`, which supports pretty-printing of error position
```go
import "github.com/bytedance/sonic"
import "github.com/bytedance/sonic/decoder"

var data interface{}
err := sonic.Unmarshal("[[[}]]", &data)
err := sonic.UnmarshalString("[[[}]]", &data)
if err != nil {
/*one line by default*/
println(e.Error())) // "Syntax error at index 3: invalid char\n\n\t[[[}]]\n\t...^..\n"
println(e.Error()) // "Syntax error at index 3: invalid char\n\n\t[[[}]]\n\t...^..\n"
/*pretty print*/
if e, ok := err.(decoder.SyntaxError); ok {
/*Syntax error at index 3: invalid char
Expand All @@ -199,10 +200,24 @@ if err != nil {
...^..
*/
print(e.Description())
}else if me, ok := err.(decoder.MismatchTypeError); ok {
print(e.Description()
}
}
```
If there a **mismatch-typed** value for a given key, sonic will report `decoder.MismatchTypeError` (if there are many, report the last one), but still skip wrong the value and keep decoding next JSON.
```go
import "github.com/bytedance/sonic"
import "github.com/bytedance/sonic/decoder"

var data = struct{
A int
B int
}{}
err := UnmarshalString(`{"A":"1","B":1}`, &data)
println(err.Error()) // Mismatch type int with value string "at index 5: mismatched type with value\n\n\t{\"A\":\"1\",\"B\":1}\n\t.....^.........\n"
fmt.Printf("%+v", data) // {A:0 B:1}
```
### Ast.Node
Sonic/ast.Node is a completely self-contained AST for JSON. It implements serialization and deserialization both and provides robust APIs for obtaining and modification of generic data.
#### Get/Index
Expand Down
14 changes: 10 additions & 4 deletions bench.py
Expand Up @@ -19,8 +19,7 @@
import subprocess
import argparse

repeat_time = 100
gbench_prefix = "SONIC_NO_ASYNC_GC=1 go test -benchmem -run=none -count=%d "%(repeat_time)
gbench_prefix = "SONIC_NO_ASYNC_GC=1 go test -benchmem -run=none "

def run(cmd):
print(cmd)
Expand Down Expand Up @@ -87,8 +86,8 @@ def compare(args):
run("%s %s ./... 2>&1 | tee %s" %(gbench_prefix, args, main))

# diff the result
benchstat = "go get golang.org/x/perf/cmd/benchstat && go install golang.org/x/perf/cmd/benchstat"
run( "%s && benchstat -sort=delta %s %s"%(benchstat, main, target))
# benchstat = "go get golang.org/x/perf/cmd/benchstat && go install golang.org/x/perf/cmd/benchstat"
run( "benchstat -sort=delta %s %s"%(main, target))
run("git checkout -- .")

# restore branch
Expand All @@ -105,6 +104,8 @@ def main():
help='Compare with the main benchmarking')
argparser.add_argument('-t', '--times', dest='times', required=False,
help='benchmark the times')
argparser.add_argument('-r', '--repeat_times', dest='count', required=False,
help='benchmark the count')
args = argparser.parse_args()

if args.filter:
Expand All @@ -114,6 +115,11 @@ def main():

if args.times:
gbench_args += " -benchtime=%s"%(args.times)

if args.count:
gbench_args += " -count=%s"%(args.count)
else:
gbench_args += " -count=10"

if args.compare:
target = compare(gbench_args)
Expand Down
24 changes: 23 additions & 1 deletion decode_test.go
Expand Up @@ -1116,6 +1116,7 @@ func TestMarshalEmbeds(t *testing.T) {

func TestUnmarshal(t *testing.T) {
for i, tt := range unmarshalTests {
t.Log(i, tt.in)
if !json.Valid([]byte(tt.in)) {
continue
}
Expand Down Expand Up @@ -2005,7 +2006,6 @@ var decodeTypeErrorTests = []struct {
dest interface{}
src string
}{
{new(string), `{"user": "name"}`}, // issue 4628.
{new(error), `{}`}, // issue 4222
{new(error), `[]`},
{new(error), `""`},
Expand All @@ -2025,6 +2025,28 @@ func TestUnmarshalTypeError(t *testing.T) {
}
}

var decodeMismatchErrorTests = []struct {
dest interface{}
src string
}{
{new(int), `{}`},
{new(string), `{}`},
{new(bool), `{}`},
{new([]byte), `{}`},
}

func TestMismatchTypeError(t *testing.T) {
for _, item := range decodeMismatchErrorTests {
err := Unmarshal([]byte(item.src), item.dest)
if _, ok := err.(*decoder.MismatchTypeError); !ok {
if _, ok = err.(decoder.SyntaxError); !ok {
t.Errorf("expected mismatch error for Unmarshal(%q, type %T): got %T",
item.src, item.dest, err)
}
}
}
}

var unmarshalSyntaxTests = []string{
"tru",
"fals",
Expand Down

0 comments on commit 02fe882

Please sign in to comment.