Skip to content

Commit

Permalink
Add actions to parse select chunks of a JSON stream while scanning fo…
Browse files Browse the repository at this point in the history
…rward.
  • Loading branch information
peterwald committed Oct 12, 2015
1 parent 26d686e commit 663afef
Show file tree
Hide file tree
Showing 4 changed files with 529 additions and 2 deletions.
42 changes: 40 additions & 2 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ func (d *Decoder) Path() JsonPath {
}

// Token is equivalent to the Token() method on json.Decoder. The primary difference is that it distinguishes
// between strings that are keys and and strings that are values. String tokens that are object keys are returned as the
// KeyString type rather than as a bare string type.
// between strings that are keys and and strings that are values. String tokens that are object keys are returned as a
// KeyString rather than as a native string.
func (d *Decoder) Token() (json.Token, error) {
t, err := d.Decoder.Token()
if err != nil {
Expand Down Expand Up @@ -155,3 +155,41 @@ func (d *Decoder) Token() (json.Token, error) {

return t, err
}

func (d *Decoder) Scan(ext *PathActions) (bool, error) {

matched := false
rootPath := d.Path()
if rootPath.inferContext() == arrValue {
rootPath.incTop()
}

for {
_, err := d.Token()
if err != nil {
return matched, err
}

match:
path := d.Path()
relPath := JsonPath{}

// fmt.Printf("rootPath: %v path: %v rel: %v\n", rootPath, path, relPath)

if len(path) > len(rootPath) {
relPath = path[len(rootPath):]
} else {
return matched, nil
}

if node, ok := ext.node.match(relPath); ok {
if node.action != nil {
matched = true
node.action(d)
if d.context == arrValue && d.Decoder.More() {
goto match
}
}
}
}
}
12 changes: 12 additions & 0 deletions path.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ const (
arrValue
)

// AnyIndex can be used in a pattern to match any array index.
const AnyIndex = -2

// JsonPath is a slice of strings and/or integers. Each string specifies an JSON object key, and
// each integer specifies an index into a JSON array.
type JsonPath []interface{}
Expand Down Expand Up @@ -53,3 +56,12 @@ func (p *JsonPath) Equal(o JsonPath) bool {
}
return true
}

func (p *JsonPath) HasPrefix(o JsonPath) bool {
for i, v := range o {
if v != (*p)[i] {
return false
}
}
return true
}
56 changes: 56 additions & 0 deletions pathaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package jsonpath

type pathNode struct {
matchOn interface{} // string, or integer
childNodes []pathNode
action DecodeAction
}

func (n *pathNode) match(path JsonPath) (*pathNode, bool) {
var node *pathNode = n
for _, ps := range path {
found := false
for i, n := range node.childNodes {
if n.matchOn == ps {
node = &node.childNodes[i]
found = true
break
} else if _, ok := ps.(int); ok && n.matchOn == AnyIndex {
node = &node.childNodes[i]
found = true
break
}
}
if !found {
return nil, false
}
}
return node, true
}

type PathActions struct {
node pathNode
}

type DecodeAction func(d *Decoder)

// Action specifies an action to call on the Decoder when a particular path is encountered.
func (je *PathActions) Action(action DecodeAction, path ...interface{}) {

var node *pathNode = &je.node
for _, ps := range path {
found := false
for i, n := range node.childNodes {
if n.matchOn == ps {
node = &node.childNodes[i]
found = true
break
}
}
if !found {
node.childNodes = append(node.childNodes, pathNode{matchOn: ps})
node = &node.childNodes[len(node.childNodes)-1]
}
}
node.action = action
}
Loading

0 comments on commit 663afef

Please sign in to comment.