...

Source file src/github.com/exponent-io/jsonpath/decoder.go

Documentation: github.com/exponent-io/jsonpath

     1  package jsonpath
     2  
     3  import (
     4  	"encoding/json"
     5  	"io"
     6  )
     7  
     8  // KeyString is returned from Decoder.Token to represent each key in a JSON object value.
     9  type KeyString string
    10  
    11  // Decoder extends the Go runtime's encoding/json.Decoder to support navigating in a stream of JSON tokens.
    12  type Decoder struct {
    13  	json.Decoder
    14  
    15  	path    JsonPath
    16  	context jsonContext
    17  }
    18  
    19  // NewDecoder creates a new instance of the extended JSON Decoder.
    20  func NewDecoder(r io.Reader) *Decoder {
    21  	return &Decoder{Decoder: *json.NewDecoder(r)}
    22  }
    23  
    24  // SeekTo causes the Decoder to move forward to a given path in the JSON structure.
    25  //
    26  // The path argument must consist of strings or integers. Each string specifies an JSON object key, and
    27  // each integer specifies an index into a JSON array.
    28  //
    29  // Consider the JSON structure
    30  //
    31  //  { "a": [0,"s",12e4,{"b":0,"v":35} ] }
    32  //
    33  // SeekTo("a",3,"v") will move to the value referenced by the "a" key in the current object,
    34  // followed by a move to the 4th value (index 3) in the array, followed by a move to the value at key "v".
    35  // In this example, a subsequent call to the decoder's Decode() would unmarshal the value 35.
    36  //
    37  // SeekTo returns a boolean value indicating whether a match was found.
    38  //
    39  // Decoder is intended to be used with a stream of tokens. As a result it navigates forward only.
    40  func (d *Decoder) SeekTo(path ...interface{}) (bool, error) {
    41  
    42  	if len(path) > 0 {
    43  		last := len(path) - 1
    44  		if i, ok := path[last].(int); ok {
    45  			path[last] = i - 1
    46  		}
    47  	}
    48  
    49  	for {
    50  		if len(path) == len(d.path) && d.path.Equal(path) {
    51  			return true, nil
    52  		}
    53  		_, err := d.Token()
    54  		if err == io.EOF {
    55  			return false, nil
    56  		} else if err != nil {
    57  			return false, err
    58  		}
    59  	}
    60  }
    61  
    62  // Decode reads the next JSON-encoded value from its input and stores it in the value pointed to by v. This is
    63  // equivalent to encoding/json.Decode().
    64  func (d *Decoder) Decode(v interface{}) error {
    65  	switch d.context {
    66  	case objValue:
    67  		d.context = objKey
    68  		break
    69  	case arrValue:
    70  		d.path.incTop()
    71  		break
    72  	}
    73  	return d.Decoder.Decode(v)
    74  }
    75  
    76  // Path returns a slice of string and/or int values representing the path from the root of the JSON object to the
    77  // position of the most-recently parsed token.
    78  func (d *Decoder) Path() JsonPath {
    79  	p := make(JsonPath, len(d.path))
    80  	copy(p, d.path)
    81  	return p
    82  }
    83  
    84  // Token is equivalent to the Token() method on json.Decoder. The primary difference is that it distinguishes
    85  // between strings that are keys and and strings that are values. String tokens that are object keys are returned as a
    86  // KeyString rather than as a native string.
    87  func (d *Decoder) Token() (json.Token, error) {
    88  	t, err := d.Decoder.Token()
    89  	if err != nil {
    90  		return t, err
    91  	}
    92  
    93  	if t == nil {
    94  		switch d.context {
    95  		case objValue:
    96  			d.context = objKey
    97  			break
    98  		case arrValue:
    99  			d.path.incTop()
   100  			break
   101  		}
   102  		return t, err
   103  	}
   104  
   105  	switch t := t.(type) {
   106  	case json.Delim:
   107  		switch t {
   108  		case json.Delim('{'):
   109  			if d.context == arrValue {
   110  				d.path.incTop()
   111  			}
   112  			d.path.push("")
   113  			d.context = objKey
   114  			break
   115  		case json.Delim('}'):
   116  			d.path.pop()
   117  			d.context = d.path.inferContext()
   118  			break
   119  		case json.Delim('['):
   120  			if d.context == arrValue {
   121  				d.path.incTop()
   122  			}
   123  			d.path.push(-1)
   124  			d.context = arrValue
   125  			break
   126  		case json.Delim(']'):
   127  			d.path.pop()
   128  			d.context = d.path.inferContext()
   129  			break
   130  		}
   131  	case float64, json.Number, bool:
   132  		switch d.context {
   133  		case objValue:
   134  			d.context = objKey
   135  			break
   136  		case arrValue:
   137  			d.path.incTop()
   138  			break
   139  		}
   140  		break
   141  	case string:
   142  		switch d.context {
   143  		case objKey:
   144  			d.path.nameTop(t)
   145  			d.context = objValue
   146  			return KeyString(t), err
   147  		case objValue:
   148  			d.context = objKey
   149  		case arrValue:
   150  			d.path.incTop()
   151  		}
   152  		break
   153  	}
   154  
   155  	return t, err
   156  }
   157  
   158  // Scan moves forward over the JSON stream consuming all the tokens at the current level (current object, current array)
   159  // invoking each matching PathAction along the way.
   160  //
   161  // Scan returns true if there are more contiguous values to scan (for example in an array).
   162  func (d *Decoder) Scan(ext *PathActions) (bool, error) {
   163  
   164  	rootPath := d.Path()
   165  
   166  	// If this is an array path, increment the root path in our local copy.
   167  	if rootPath.inferContext() == arrValue {
   168  		rootPath.incTop()
   169  	}
   170  
   171  	for {
   172  		// advance the token position
   173  		_, err := d.Token()
   174  		if err != nil {
   175  			return false, err
   176  		}
   177  
   178  	match:
   179  		var relPath JsonPath
   180  
   181  		// capture the new JSON path
   182  		path := d.Path()
   183  
   184  		if len(path) > len(rootPath) {
   185  			// capture the path relative to where the scan started
   186  			relPath = path[len(rootPath):]
   187  		} else {
   188  			// if the path is not longer than the root, then we are done with this scan
   189  			// return boolean flag indicating if there are more items to scan at the same level
   190  			return d.Decoder.More(), nil
   191  		}
   192  
   193  		// match the relative path against the path actions
   194  		if node := ext.node.match(relPath); node != nil {
   195  			if node.action != nil {
   196  				// we have a match so execute the action
   197  				err = node.action(d)
   198  				if err != nil {
   199  					return d.Decoder.More(), err
   200  				}
   201  				// The action may have advanced the decoder. If we are in an array, advancing it further would
   202  				// skip tokens. So, if we are scanning an array, jump to the top without advancing the token.
   203  				if d.path.inferContext() == arrValue && d.Decoder.More() {
   204  					goto match
   205  				}
   206  			}
   207  		}
   208  	}
   209  }
   210  

View as plain text