diff --git a/jsoniter.go b/jsoniter.go index 27ebc5a..67209bc 100644 --- a/jsoniter.go +++ b/jsoniter.go @@ -43,7 +43,7 @@ func ParseString(input string) *Iterator { func (iter *Iterator) skipWhitespaces() { c := iter.readByte() - for c == ' ' { + for c == ' ' || c == '\n' { c = iter.readByte() } iter.unreadByte() @@ -326,7 +326,9 @@ func (iter *Iterator) ReadArray() (ret bool) { } } case ']': return false - case ',': return true + case ',': + iter.skipWhitespaces() + return true default: iter.ReportError("ReadArray", "expect [ or , or ]") return @@ -397,18 +399,8 @@ func (iter *Iterator) readObjectField() (ret string) { func (iter *Iterator) ReadFloat64() (ret float64) { str := make([]byte, 0, 10) for c := iter.readByte(); iter.Error == nil; c = iter.readByte() { - switch { - case c == '+': - fallthrough - case c == '-': - fallthrough - case c == '.': - fallthrough - case c == 'e': - fallthrough - case c == 'E': - fallthrough - case c > '0' && c < '9': + switch c { + case '-', '+', '.', 'e', 'E', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': str = append(str, c) default: iter.unreadByte() @@ -430,3 +422,117 @@ func (iter *Iterator) ReadFloat64() (ret float64) { } return } + +func (iter *Iterator) Skip() { + c := iter.readByte() + if iter.Error != nil { + return + } + switch c { + case '"': + iter.skipString() + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + iter.skipNumber() + case '[': + iter.skipArray() + case '{': + iter.skipObject() + default: + iter.ReportError("Skip", fmt.Sprintf("do not know how to skip: %v", c)) + return + } +} + +func (iter *Iterator) skipString() { + for c := iter.readByte(); iter.Error == nil; c = iter.readByte() { + switch c { + case '"': + return // end of string found + case '\\': + iter.readByte() // " after \\ does not count + if iter.Error != nil { + return + } + } + } +} + +func (iter *Iterator) skipNumber() { + for c := iter.readByte(); iter.Error == nil; c = iter.readByte() { + switch c { + case '-', '+', '.', 'e', 'E', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + continue + default: + iter.unreadByte() + return + } + } +} + +func (iter *Iterator) skipArray() { + for { + iter.skipWhitespaces() + c := iter.readByte() + if iter.Error != nil { + return + } + if c == ']' { + return + } + iter.unreadByte() + iter.Skip() + iter.skipWhitespaces() + c = iter.readByte() + if iter.Error != nil { + return + } + switch c { + case ',': + iter.skipWhitespaces() + continue + case ']': + return + default: + iter.ReportError("skipArray", "expects , or ]") + return + } + } +} + +func (iter *Iterator) skipObject() { + for { + iter.skipWhitespaces() + c := iter.readByte() + if c != '"' { + iter.ReportError("skipObject", `expects "`) + return + } + iter.skipString() + iter.skipWhitespaces() + c = iter.readByte() + if iter.Error != nil { + return + } + if c != ':' { + iter.ReportError("skipObject", `expects :`) + return + } + iter.skipWhitespaces() + iter.Skip() + iter.skipWhitespaces() + c = iter.readByte() + if iter.Error != nil { + return + } + switch c { + case ',': + iter.skipWhitespaces() + continue + case '}': + return // end of object + default: + iter.ReportError("skipObject", "expects , or }") + return + } + } +} \ No newline at end of file diff --git a/jsoniter_skip_test.go b/jsoniter_skip_test.go new file mode 100644 index 0000000..9b68068 --- /dev/null +++ b/jsoniter_skip_test.go @@ -0,0 +1,138 @@ +package jsoniter + +import ( + "testing" + "encoding/json" +) + +func Test_skip_string(t *testing.T) { + iter := ParseString(`["a", "b"]`) + iter.ReadArray() + iter.Skip() + iter.ReadArray() + if iter.ReadString() != "b" { + t.FailNow() + } +} + +func Test_skip_string_with_escape(t *testing.T) { + iter := ParseString(`["a\"", "b"]`) + iter.ReadArray() + iter.Skip() + iter.ReadArray() + if iter.ReadString() != "b" { + t.FailNow() + } +} + +func Test_skip_number(t *testing.T) { + iter := ParseString(`[-0.12, "b"]`) + iter.ReadArray() + iter.Skip() + iter.ReadArray() + if iter.ReadString() != "b" { + t.FailNow() + } +} + +func Test_skip_array(t *testing.T) { + iter := ParseString(`[[1, [2, [3], 4]], "b"]`) + iter.ReadArray() + iter.Skip() + iter.ReadArray() + if iter.ReadString() != "b" { + t.FailNow() + } +} + +func Test_skip_object(t *testing.T) { + iter := ParseString(`[ {"a" : {"b": "c"}, "d": 102 }, "b"]`) + iter.ReadArray() + iter.Skip() + iter.ReadArray() + if iter.ReadString() != "b" { + t.FailNow() + } +} + +func Test_skip_nested(t *testing.T) { + iter := ParseString(`[ {"a" : [{"b": "c"}], "d": 102 }, "b"]`) + iter.ReadArray() + iter.Skip() + iter.ReadArray() + if iter.ReadString() != "b" { + t.FailNow() + } +} + +type TestResp struct { + Code uint64 +} + +func Benchmark_jsoniter_skip(b *testing.B) { + for n := 0; n < b.N; n++ { + result := TestResp{} + iter := ParseString(` +{ + "_shards":{ + "total" : 5, + "successful" : 5, + "failed" : 0 + }, + "code": 200, + "hits":{ + "total" : 1, + "hits" : [ + { + "_index" : "twitter", + "_type" : "tweet", + "_id" : "1", + "_source" : { + "user" : "kimchy", + "postDate" : "2009-11-15T14:12:12", + "message" : "trying out Elasticsearch" + } + } + ] + } +}`) + for field := iter.ReadObject(); field != ""; field = iter.ReadObject() { + switch field { + case "code": + result.Code = iter.ReadUint64() + default: + iter.Skip() + } + } + } +} + +func Benchmark_json_skip(b *testing.B) { + for n := 0; n < b.N; n++ { + result := TestResp{} + json.Unmarshal([]byte(` +{ + "_shards":{ + "total" : 5, + "successful" : 5, + "failed" : 0 + }, + "code": 200, + "hits":{ + "total" : 1, + "hits" : [ + { + "_index" : "twitter", + "_type" : "tweet", + "_id" : "1", + "_source" : { + "user" : "kimchy", + "postDate" : "2009-11-15T14:12:12", + "message" : "trying out Elasticsearch" + } + } + ] + } +}`), &result) + } +} \ No newline at end of file