/
json_parse.go
149 lines (129 loc) · 3.42 KB
/
json_parse.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package adapters
import (
"encoding/json"
"errors"
"strconv"
"strings"
"github.com/smartcontractkit/chainlink/core/store"
"github.com/smartcontractkit/chainlink/core/store/models"
"github.com/smartcontractkit/chainlink/core/utils"
simplejson "github.com/bitly/go-simplejson"
gjson "github.com/tidwall/gjson"
)
// JSONParse holds a path to the desired field in a JSON object,
// made up of an array of strings.
type JSONParse struct {
Path JSONPath `json:"path"`
}
// TaskType returns the type of Adapter.
func (jpa *JSONParse) TaskType() models.TaskType {
return TaskTypeJSONParse
}
// Perform returns the value associated to the desired field for a
// given JSON object.
//
// For example, if the JSON data looks like this:
// {
// "data": [
// {"last": "1111"},
// {"last": "2222"}
// ]
// }
//
// Then ["0","last"] would be the path, and "1111" would be the returned value
func (jpa *JSONParse) Perform(input models.RunInput, _ *store.Store) models.RunOutput {
var val string
var err error
if input.Result().Type == gjson.JSON {
// Handle case where JSON comes "pre-packaged" as gjson e.g. from bridge (external adapters)
val = input.Result().Raw
} else {
val, err = input.ResultString()
}
if err != nil {
return models.NewRunOutputError(err)
}
js, err := simplejson.NewJson([]byte(val))
if err != nil {
return models.NewRunOutputError(err)
}
last, err := dig(js, jpa.Path)
if err != nil {
return moldErrorOutput(js, jpa.Path, input)
}
return models.NewRunOutputCompleteWithResult(last.Interface())
}
func dig(js *simplejson.Json, path []string) (*simplejson.Json, error) {
var ok bool
for _, k := range path[:] {
if isArray(js, k) {
js, ok = arrayGet(js, k)
} else {
js, ok = js.CheckGet(k)
}
if !ok {
return js, errors.New("No value could be found for the key '" + k + "'")
}
}
return js, nil
}
// only error if any keys prior to the last one in the path are nonexistent.
// i.e. Path = ["errorIfNonExistent", "nullIfNonExistent"]
func moldErrorOutput(js *simplejson.Json, path []string, input models.RunInput) models.RunOutput {
if _, err := getEarlyPath(js, path); err != nil {
return models.NewRunOutputError(err)
}
return models.NewRunOutputCompleteWithResult(nil)
}
func getEarlyPath(js *simplejson.Json, path []string) (*simplejson.Json, error) {
var ok bool
for _, k := range path[:len(path)-1] {
if isArray(js, k) {
js, ok = arrayGet(js, k)
} else {
js, ok = js.CheckGet(k)
}
if !ok {
return js, errors.New("No value could be found for the key '" + k + "'")
}
}
return js, nil
}
func arrayGet(js *simplejson.Json, key string) (*simplejson.Json, bool) {
input, err := strconv.ParseInt(key, 10, 32)
if err != nil {
return js, false
}
a, err := js.Array()
if err != nil {
return js, false
}
index := int(input)
if index < 0 {
index = len(a) + index
}
if index >= len(a) || index < 0 {
return js, false
}
return js.GetIndex(index), true
}
func isArray(js *simplejson.Json, key string) bool {
if _, err := js.Array(); err != nil {
return false
}
return true
}
// JSONPath is a path to a value in a JSON object
type JSONPath []string
// UnmarshalJSON implements the Unmarshaler interface
func (jp *JSONPath) UnmarshalJSON(b []byte) error {
strs := []string{}
var err error
if utils.IsQuoted(b) {
strs = strings.Split(string(utils.RemoveQuotes(b)), ".")
} else {
err = json.Unmarshal(b, &strs)
}
*jp = JSONPath(strs)
return err
}