From 05a9df474958724eb33836e13f6166fb3055ad88 Mon Sep 17 00:00:00 2001
From: Tao Wen <taowen@gmail.com>
Date: Fri, 2 Dec 2016 00:13:13 +0800
Subject: [PATCH] support skip

---
 jsoniter.go           | 134 +++++++++++++++++++++++++++++++++++-----
 jsoniter_skip_test.go | 138 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 258 insertions(+), 14 deletions(-)
 create mode 100644 jsoniter_skip_test.go

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