github.com/richardwilkes/toolbox@v1.121.0/formats/json/json.go (about) 1 // Copyright (c) 2016-2024 by Richard A. Wilkes. All rights reserved. 2 // 3 // This Source Code Form is subject to the terms of the Mozilla Public 4 // License, version 2.0. If a copy of the MPL was not distributed with 5 // this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 // 7 // This Source Code Form is "Incompatible With Secondary Licenses", as 8 // defined by the Mozilla Public License, version 2.0. 9 10 // Package json provides manipulation of JSON data. 11 package json 12 13 import ( 14 "bytes" 15 "encoding/json" 16 "io" 17 "strconv" 18 "strings" 19 ) 20 21 // Data provides conveniences for working with JSON data. 22 type Data struct { 23 obj any 24 } 25 26 // MustParse is the same as calling Parse, but without the error code on return. 27 func MustParse(data []byte) *Data { 28 result, err := Parse(data) 29 if err != nil { 30 result = &Data{} 31 } 32 return result 33 } 34 35 // Parse JSON data from bytes. If the data can't be loaded, a valid, empty Data will still be returned, along with an 36 // error. 37 func Parse(data []byte) (*Data, error) { 38 var obj any 39 decoder := json.NewDecoder(bytes.NewReader(data)) 40 decoder.UseNumber() 41 if err := decoder.Decode(&obj); err != nil { 42 return &Data{}, err 43 } 44 return &Data{obj: obj}, nil 45 } 46 47 // MustParseStream is the same as calling ParseStream, but without the error code on return. 48 func MustParseStream(in io.Reader) *Data { 49 result, err := ParseStream(in) 50 if err != nil { 51 result = &Data{} 52 } 53 return result 54 } 55 56 // ParseStream parses JSON data from the stream. If the data can't be loaded, a valid, empty Data will still be 57 // returned, along with an error. 58 func ParseStream(in io.Reader) (*Data, error) { 59 var obj any 60 decoder := json.NewDecoder(in) 61 decoder.UseNumber() 62 if err := decoder.Decode(&obj); err != nil { 63 return &Data{}, err 64 } 65 return &Data{obj: obj}, nil 66 } 67 68 // Raw returns the underlying data. 69 func (j *Data) Raw() any { 70 return j.obj 71 } 72 73 // Path searches the dot-separated path and returns the object at that point. If the search encounters an array and has 74 // not reached the end target, then it will iterate through the array for the target and return all results in a Data 75 // array. 76 func (j *Data) Path(path string) *Data { 77 return j.path(strings.Split(path, ".")...) 78 } 79 80 func (j *Data) path(path ...string) *Data { 81 if len(path) == 1 && path[0] == "" { 82 path = nil 83 } 84 obj := j.obj 85 for i := 0; i < len(path); i++ { 86 if m, ok := obj.(map[string]any); ok { 87 obj = m[path[i]] 88 } else { 89 var a []any 90 if a, ok = obj.([]any); ok { 91 t := make([]any, 0) 92 for _, one := range a { 93 tj := &Data{obj: one} 94 if result := tj.path(path[i:]...).obj; result != nil { 95 t = append(t, result) 96 } 97 } 98 if len(a) != 0 { 99 return &Data{obj: t} 100 } 101 } 102 return &Data{} 103 } 104 } 105 return &Data{obj} 106 } 107 108 // Exists returns true if the path exists in the data. 109 func (j *Data) Exists(path string) bool { 110 return j.Path(path).obj != nil 111 } 112 113 // IsArray returns true if this is a Data array. 114 func (j *Data) IsArray() bool { 115 _, ok := j.obj.([]any) 116 return ok 117 } 118 119 // IsMap returns true if this is a Data map. 120 func (j *Data) IsMap() bool { 121 _, ok := j.obj.(map[string]any) 122 return ok 123 } 124 125 // Keys returns the keys of a map, or an empty slice if this is not a map. 126 func (j *Data) Keys() []string { 127 if m, ok := j.obj.(map[string]any); ok { 128 keys := make([]string, 0, len(m)) 129 for key := range m { 130 keys = append(keys, key) 131 } 132 return keys 133 } 134 return make([]string, 0) 135 } 136 137 // Size returns the number of elements in an array or map, or 0 if this is neither type. 138 func (j *Data) Size() int { 139 if m, ok := j.obj.(map[string]any); ok { 140 return len(m) 141 } 142 if a, ok := j.obj.([]any); ok { 143 return len(a) 144 } 145 return 0 146 } 147 148 // Index returns the object at the specified index within an array, or nil if this isn't an array or the index isn't 149 // valid. 150 func (j *Data) Index(index int) *Data { 151 if a, ok := j.obj.([]any); ok { 152 if index >= 0 && index < len(a) { 153 return &Data{obj: a[index]} 154 } 155 } 156 return &Data{} 157 } 158 159 // Bytes converts the data into a Data []byte. 160 func (j *Data) Bytes() []byte { 161 if j.obj != nil { 162 if data, err := json.Marshal(j.obj); err == nil { 163 return data 164 } 165 } 166 return []byte("{}") 167 } 168 169 // String converts the data into a Data string. 170 func (j *Data) String() string { 171 return string(j.Bytes()) 172 } 173 174 // Str extracts a string from the path. Returns the empty string if the path isn't present or isn't a string type. 175 func (j *Data) Str(path string) string { 176 if str, ok := j.Path(path).obj.(string); ok { 177 return str 178 } 179 return "" 180 } 181 182 // Bool extracts a bool from the path. Returns false if the path isn't present or isn't a boolean type. 183 func (j *Data) Bool(path string) bool { 184 if b, ok := j.Path(path).obj.(bool); ok { 185 return b 186 } 187 return false 188 } 189 190 // BoolRelaxed extracts a bool from the path. Returns false if the path isn't present or can't be converted to a boolean 191 // type. 192 func (j *Data) BoolRelaxed(path string) bool { 193 if b, ok := j.Path(path).obj.(bool); ok { 194 return b 195 } 196 return strings.EqualFold(j.Str(path), "true") 197 } 198 199 // Float64 extracts an float64 from the path. Returns 0 if the path isn't present or isn't a numeric type. 200 func (j *Data) Float64(path string) float64 { 201 if n, ok := j.Path(path).obj.(json.Number); ok { 202 if f, err := n.Float64(); err == nil { 203 return f 204 } 205 } 206 return 0 207 } 208 209 // Float64Relaxed extracts an float64 from the path. Returns 0 if the path isn't present or can't be converted to a 210 // numeric type. 211 func (j *Data) Float64Relaxed(path string) float64 { 212 if n, ok := j.Path(path).obj.(json.Number); ok { 213 if f, err := n.Float64(); err == nil { 214 return f 215 } 216 } else { 217 if f, err := strconv.ParseFloat(j.Str(path), 64); err == nil { 218 return f 219 } 220 } 221 return 0 222 } 223 224 // Int64 extracts an int64 from the path. Returns 0 if the path isn't present or isn't a numeric type. 225 func (j *Data) Int64(path string) int64 { 226 if n, ok := j.Path(path).obj.(json.Number); ok { 227 if i, err := n.Int64(); err == nil { 228 return i 229 } 230 } 231 return 0 232 } 233 234 // Int64Relaxed extracts an int64 from the path. Returns 0 if the path isn't present or can't be converted to a numeric 235 // type. 236 func (j *Data) Int64Relaxed(path string) int64 { 237 if n, ok := j.Path(path).obj.(json.Number); ok { 238 if i, err := n.Int64(); err == nil { 239 return i 240 } 241 } else { 242 if i, err := strconv.ParseInt(j.Str(path), 10, 64); err == nil { 243 return i 244 } 245 } 246 return 0 247 } 248 249 // Unmarshal parses the data at the path and stores the result into value. 250 func (j *Data) Unmarshal(path string, value any) error { 251 return json.Unmarshal(j.Path(path).Bytes(), value) 252 } 253 254 // NewMap creates a map at the specified path. Any parts of the path that do not exist will be created. Returns true if 255 // successful, or false if a collision occurs with a non-object type while traversing the path. 256 func (j *Data) NewMap(path string) bool { 257 return j.set(path, make(map[string]any)) 258 } 259 260 // NewArray creates an array at the specified path. Any parts of the path that do not exist will be created. Returns 261 // true if successful, or false if a collision occurs with a non-object type while traversing the path. 262 func (j *Data) NewArray(path string) bool { 263 return j.set(path, make([]any, 0)) 264 } 265 266 // SetStr a string at the specified path. Any parts of the path that do not exist will be created. Returns true if 267 // successful, or false if a collision occurs with a non-object type while traversing the path. 268 func (j *Data) SetStr(path, value string) bool { 269 return j.set(path, value) 270 } 271 272 // SetBool a bool at the specified path. Any parts of the path that do not exist will be created. Returns true if 273 // successful, or false if a collision occurs with a non-object type while traversing the path. 274 func (j *Data) SetBool(path string, value bool) bool { 275 return j.set(path, value) 276 } 277 278 // SetFloat64 a float64 at the specified path. Any parts of the path that do not exist will be created. Returns true if 279 // successful, or false if a collision occurs with a non-object type while traversing the path. 280 func (j *Data) SetFloat64(path string, value float64) bool { 281 return j.set(path, value) 282 } 283 284 // SetInt64 an int64 at the specified path. Any parts of the path that do not exist will be created. Returns true if 285 // successful, or false if a collision occurs with a non-object type while traversing the path. 286 func (j *Data) SetInt64(path string, value int64) bool { 287 return j.set(path, value) 288 } 289 290 // Set a Data value at the specified path. Any parts of the path that do not exist will be created. Returns true if 291 // successful, or false if a collision occurs with a non-object type while traversing the path. 292 func (j *Data) Set(path string, value *Data) bool { 293 var v any 294 if value != nil { 295 v = value.obj 296 } 297 return j.set(path, v) 298 } 299 300 func (j *Data) set(path string, value any) bool { 301 paths := strings.Split(path, ".") 302 if len(paths) == 0 { 303 j.obj = value 304 } else { 305 if j.obj == nil { 306 j.obj = make(map[string]any) 307 } 308 obj := j.obj 309 for i := 0; i < len(paths); i++ { 310 if m, ok := obj.(map[string]any); ok { 311 if i == len(paths)-1 { 312 m[paths[i]] = value 313 } else if m[paths[i]] == nil { 314 m[paths[i]] = make(map[string]any) 315 } 316 obj = m[paths[i]] 317 } else { 318 return false 319 } 320 } 321 } 322 return true 323 } 324 325 // AppendMap appends a new map to an array at the specified path. The array must already exist. Returns true if 326 // successful. 327 func (j *Data) AppendMap(path string) bool { 328 return j.append(path, make(map[string]any)) 329 } 330 331 // AppendArray appends a new array to an array at the specified path. The array must already exist. Returns true if 332 // successful. 333 func (j *Data) AppendArray(path string) bool { 334 return j.append(path, make([]any, 0)) 335 } 336 337 // AppendStr appends a string to an array at the specified path. The array must already exist. Returns true if 338 // successful. 339 func (j *Data) AppendStr(path, value string) bool { 340 return j.append(path, value) 341 } 342 343 // AppendBool appends a bool to an array at the specified path. The array must already exist. Returns true if 344 // successful. 345 func (j *Data) AppendBool(path string, value bool) bool { 346 return j.append(path, value) 347 } 348 349 // AppendFloat64 appends a float64 to an array at the specified path. The array must already exist. Returns true if 350 // successful. 351 func (j *Data) AppendFloat64(path string, value float64) bool { 352 return j.append(path, value) 353 } 354 355 // AppendInt64 appends an int64 to an array at the specified path. The array must already exist. Returns true if 356 // successful. 357 func (j *Data) AppendInt64(path string, value int64) bool { 358 return j.append(path, value) 359 } 360 361 // Append a Data value to an array at the specified path. The array must already exist. Returns true if successful. 362 func (j *Data) Append(path string, value *Data) bool { 363 var v any 364 if value != nil { 365 v = value.obj 366 } 367 return j.append(path, v) 368 } 369 370 func (j *Data) append(path string, value any) bool { 371 if array, ok := j.Path(path).obj.([]any); ok { 372 return j.set(path, append(array, value)) 373 } 374 return false 375 } 376 377 // Delete a value at the specified path. Returns true if successful. 378 func (j *Data) Delete(path string) bool { 379 if j.obj == nil { 380 return false 381 } 382 paths := strings.Split(path, ".") 383 if len(paths) == 0 { 384 j.obj = nil 385 return true 386 } 387 obj := j.obj 388 for i := 0; i < len(paths); i++ { 389 if m, ok := obj.(map[string]any); ok { 390 if i == len(paths)-1 { 391 if _, ok = m[paths[i]]; ok { 392 delete(m, paths[i]) 393 return true 394 } 395 } 396 obj = m[paths[i]] 397 } 398 } 399 return false 400 }