-
Notifications
You must be signed in to change notification settings - Fork 0
/
data.go
309 lines (273 loc) · 9.38 KB
/
data.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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
// Package unstructured provides ways of manipulating unstructured data such as JSON or YAML
package unstructured
import (
"encoding/json"
"fmt"
"reflect"
"github.com/ghodss/yaml"
"github.com/xeipuuv/gojsonpointer"
)
const (
DataString = "string"
DataNum = "number"
DataOb = "object"
DataList = "list"
DataNull = "null"
DataBool = "bool"
)
// Data represents some unstructured data
type Data struct {
data interface{}
}
// ParseYAML unmarshals yaml from an input string. Use this for generating a
// Data struct, whose contents you can examine using the following functions.
func ParseYAML(rawjson string) (Data, error) {
jsonbytes, err := yaml.YAMLToJSON([]byte(rawjson))
if err != nil {
return Data{}, err
}
return ParseJSON(string(jsonbytes))
}
// ParseJSON unmarshals json from an input string. Use this for generating a
// Data struct, whose contents you can examine using the following functions.
func ParseJSON(rawjson string) (Data, error) {
j := Data{}
err := json.Unmarshal([]byte(rawjson), &j.data)
if err != nil {
return Data{}, fmt.Errorf("parse error: %s", err.Error())
}
return j, nil
}
// IsOb returns true iff the data represented by this Data struct is an object
// or map.
func (j Data) IsOb() bool {
return reflect.TypeOf(j.data) == reflect.TypeOf(map[string]interface{}{})
}
// UnsafeObValue returns a golang map[string]interface{} represenation of the object
// represented by this Data struct. If the Data struct does not represent an
// object, this method panics. If in doubt, check with `IsOb()`
func (j Data) UnsafeObValue() map[string]interface{} {
return j.data.(map[string]interface{})
}
// ObValue returns a golang map[string]interface{} represenation of the object
// represented by this Data struct. If the Data struct does not represent an
// object, this method returns an error
func (j Data) ObValue() (map[string]interface{}, error) {
if !j.IsOb() {
return nil, fmt.Errorf("This is not an object, so we can't get the object value of it")
}
return j.UnsafeObValue(), nil
}
// HasKey returns true iff the object represented by this Data struct contains `key`
//
// Note: this will panic if the data represented by this Data struct is not an
// object. If in doubt, check with `IsOb()`
func (j Data) HasKey(key string) bool {
jmap := j.data.(map[string]interface{})
_, ok := jmap[key]
return ok
}
// HasPointer returns true iff the object represented by this Data struct
// contains the pointer `p`
//
// For more information on json pointers, see https://tools.ietf.org/html/rfc6901
func (j Data) HasPointer(p string) (bool, error) {
pointer, err := gojsonpointer.NewJsonPointer(p)
if err != nil {
return false, err
}
_, _, err = pointer.Get(j.data)
return err == nil, nil
}
// GetByPointer returns a Data struct containing the contents of the original
// data at the given pointer address `p`.
// For more information on json pointers, see https://tools.ietf.org/html/rfc6901
func (j Data) GetByPointer(p string) (data Data, err error) {
pointer, err := gojsonpointer.NewJsonPointer(p)
if err != nil {
return
}
json, _, err := pointer.Get(j.data)
data = Data{data: json}
return
}
// UnsafeGetField returns a Data struct containing the contents of the original data
// at the given `key`. If this method name feels too long, use `F(key)`.
//
// Note: this function panics if the given `key` does not exist. If in doubt,
// check with `HasKey()`.
func (j Data) UnsafeGetField(key string) Data {
jmap := j.data.(map[string]interface{})
val, ok := jmap[key]
if !ok {
panic("getting a non-existing field from a Data")
}
return Data{data: val}
}
// F is a shorthand for `UnsafeGetField`
func (j Data) F(key string) Data {
return j.UnsafeGetField(key)
}
// Keys returns a list of the keys on this Data object.
//
// If this is not a Data object, return an error
func (j Data) Keys() ([]string, error) {
if !j.IsOb() {
return nil, fmt.Errorf("This is not an object, so you can't get a list of its keys.")
}
jmap := j.data.(map[string]interface{})
var keys []string
for key := range jmap {
keys = append(keys, key)
}
return keys, nil
}
// SetField updates the field `fieldName` of this Data object.
// If the field `fieldName` does not exist on this object, create it.
//
// If this Data does not represent an object, return an error.
func (j Data) SetField(fieldName string, val interface{}) error {
if !j.IsOb() {
return fmt.Errorf("This is not an object, so you can't set a field on it.")
}
jmap := j.data.(map[string]interface{})
jmap[fieldName] = val
return nil
}
// RawValue returns the raw go value of the parsed data, without any type
// checking
func (j Data) RawValue() interface{} {
return j.data
}
// IsString returns true iff the data represented by this Data struct is a
// string.
func (j Data) IsString() bool {
return reflect.TypeOf(j.data) == reflect.TypeOf("")
}
// UnsafeStringValue returns the golang string representation of the string
// represented by this Data struct. If the Data struct does not represent a
// string, this method panics. If in doubt, check with `IsString()`
func (j Data) UnsafeStringValue() string {
return j.data.(string)
}
// StringValue returns the golang string representation of the string
// represented by this Data struct. If the Data struct does not represent a
// string, this method returns an error.
func (j Data) StringValue() (string, error) {
if !j.IsString() {
return "", fmt.Errorf("This is not a string, so we can't get the StringValue of it")
}
return j.UnsafeStringValue(), nil
}
// IsNum returns true iff the data represented by this Data struct is a number.
func (j Data) IsNum() bool {
return reflect.TypeOf(j.data) == reflect.TypeOf(64.4)
}
// UnsafeNumValue returns the golang float64 representation of the number represented
// by this Data struct. If the Data struct does not represent a number, this
// method panics. If in doubt, check with `IsNum()`
func (j Data) UnsafeNumValue() float64 {
return j.data.(float64)
}
// NumValue returns the golang float64 representation of the number represented
// by this Data struct. If the Data struct does not represent a number, this
// method returns an error.
func (j Data) NumValue() (float64, error) {
if !j.IsNum() {
return 0, fmt.Errorf("This is not a number, so we can't get the NumValue of it")
}
return j.UnsafeNumValue(), nil
}
// IsBool returns true iff the data represented by this Data struct is a boolean.
func (j Data) IsBool() bool {
return reflect.TypeOf(j.data) == reflect.TypeOf(true)
}
// UnsafeBoolValue returns the golang bool representation of the bool represented by
// this Data struct. If the Data struct does not represent a bool, this method
// panics. If in doubt, check with `IsBool()`
func (j Data) UnsafeBoolValue() bool {
return j.data.(bool)
}
// BoolValue returns the golang bool representation of the bool represented by
// this Data struct. If the Data struct does not represent a bool, this method
// returns an error.
func (j Data) BoolValue() (bool, error) {
if !j.IsBool() {
return false, fmt.Errorf("This is not a bool, so we can't get the bool value of it")
}
return j.UnsafeBoolValue(), nil
}
// IsList returns true iff the data represented by this Data struct is a list.
func (j Data) IsList() bool {
if j.data == nil {
return false
}
return reflect.TypeOf(j.data).Kind() == reflect.TypeOf([]interface{}{}).Kind()
}
// UnsafeListValue returns a golang slice of Data structs representing the
// unstructured list represented by this Data struct. If the Data struct does
// not represent a list, this method panics. If in doubt, check with `IsList()`
func (j Data) UnsafeListValue() (list []Data) {
list = []Data{}
for _, val := range j.data.([]interface{}) {
list = append(list, Data{data: val})
}
return
}
// ListValue returns a golang slice of Data structs representing the
// unstructured list represented by this Data struct. If the Data struct does
// not represent a list, this method returns an error.
func (j Data) ListValue() ([]Data, error) {
if !j.IsList() {
return nil, fmt.Errorf("This is not a list, so we can't get its ListValue")
}
return j.UnsafeListValue(), nil
}
// An ElementMatcher can be used with FindElem to find an element in an
// unstructured list.
type ElementMatcher func(Data) bool
// FindElem finds an element in a list, using a provided matcher
func (j Data) FindElem(match ElementMatcher) (Data, bool) {
if !j.IsList() {
return Data{}, false
}
for _, elem := range j.UnsafeListValue() {
if match(elem) {
return elem, true
}
}
return Data{}, false
}
// SetElem sets the element at a given index in this Data list to the given value.
// If this Data object does not represent a list, return an error
func (j Data) SetElem(index int, value interface{}) error {
if !j.IsList() {
return fmt.Errorf("This is not a list, so you can't set an element of it")
}
j.data.([]interface{})[index] = value
return nil
}
// IsNull returns true iff the data represented by this Data struct is null.
func (j Data) IsNull() bool {
return j.data == nil
}
// IsOfType returns true iff the Data struct represents data of type `typ`.
// Valid values of `typ` are listed as constants above.
func (j Data) IsOfType(typ string) bool {
switch typ {
case DataOb:
return j.IsOb()
case DataString:
return j.IsString()
case DataList:
return j.IsList()
case DataNum:
return j.IsNum()
case DataBool:
return j.IsBool()
case DataNull:
return j.IsNull()
default:
panic("that's not a Data type I recognise!")
}
}