├── .errcheck.txt ├── .github ├── FUNDING.yml └── workflows │ └── test.yml ├── .golangci.yml ├── LICENSE ├── README.md ├── gabs.go ├── gabs_logo.png ├── gabs_test.go ├── go.mod └── migration.md /.errcheck.txt: -------------------------------------------------------------------------------- 1 | (*github.com/Jeffail/gabs/v2.Container).Array 2 | (*github.com/Jeffail/gabs/v2.Container).ArrayAppend 3 | (*github.com/Jeffail/gabs/v2.Container).ArrayAppend 4 | (*github.com/Jeffail/gabs/v2.Container).ArrayConcat 5 | (*github.com/Jeffail/gabs/v2.Container).ArrayConcatP 6 | (*github.com/Jeffail/gabs/v2.Container).ArrayP 7 | (*github.com/Jeffail/gabs/v2.Container).Set 8 | (*github.com/Jeffail/gabs/v2.Container).SetIndex 9 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: Jeffail 4 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Install Go 12 | uses: actions/setup-go@v3 13 | with: 14 | go-version: 1.16.x 15 | 16 | - name: Checkout code 17 | uses: actions/checkout@v3 18 | 19 | - name: Tidy 20 | run: go mod tidy && git diff-index --quiet HEAD || { >&2 echo "Stale go.{mod,sum} detected. This can be fixed with 'go mod tidy'."; exit 1; } 21 | 22 | - name: Test 23 | run: go test -count 100 ./... 24 | 25 | golangci-lint: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout code 29 | uses: actions/checkout@v3 30 | 31 | - name: Lint 32 | uses: golangci/golangci-lint-action@v3 33 | with: 34 | version: latest 35 | args: --timeout 10m 36 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 30s 3 | 4 | issues: 5 | max-issues-per-linter: 0 6 | max-same-issues: 0 7 | 8 | linters-settings: 9 | errcheck: 10 | exclude: .errcheck.txt 11 | gocritic: 12 | enabled-tags: 13 | - diagnostic 14 | - experimental 15 | - opinionated 16 | - performance 17 | - style 18 | 19 | linters: 20 | disable-all: true 21 | enable: 22 | # Default linters reported by golangci-lint help linters` in v1.39.0 23 | - gosimple 24 | - staticcheck 25 | - unused 26 | - errcheck 27 | - govet 28 | - ineffassign 29 | - typecheck 30 | # Extra linters: 31 | - wastedassign 32 | - stylecheck 33 | - gofmt 34 | - goimports 35 | - gocritic 36 | - revive 37 | - unconvert 38 | - durationcheck 39 | - depguard 40 | - gosec 41 | - bodyclose 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Ashley Jeffs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |  2 | 3 | [![pkg.go for Jeffail/gabs][godoc-badge]][godoc-url] 4 | 5 | Gabs is a small utility for dealing with dynamic or unknown JSON structures in Go. It's pretty much just a helpful wrapper for navigating hierarchies of `map[string]interface{}` objects provided by the `encoding/json` package. It does nothing spectacular apart from being fabulous. 6 | 7 | If you're migrating from version 1 check out [`migration.md`][migration-doc] for guidance. 8 | 9 | ## Use 10 | 11 | ### Import 12 | 13 | Using modules: 14 | 15 | ```go 16 | import ( 17 | "github.com/Jeffail/gabs/v2" 18 | ) 19 | ``` 20 | 21 | Without modules: 22 | 23 | ```go 24 | import ( 25 | "github.com/Jeffail/gabs" 26 | ) 27 | ``` 28 | 29 | ### Parsing and searching JSON 30 | 31 | ```go 32 | jsonParsed, err := gabs.ParseJSON([]byte(`{ 33 | "outer":{ 34 | "inner":{ 35 | "value1":10, 36 | "value2":22 37 | }, 38 | "alsoInner":{ 39 | "value1":20, 40 | "array1":[ 41 | 30, 40 42 | ] 43 | } 44 | } 45 | }`)) 46 | if err != nil { 47 | panic(err) 48 | } 49 | 50 | var value float64 51 | var ok bool 52 | 53 | value, ok = jsonParsed.Path("outer.inner.value1").Data().(float64) 54 | // value == 10.0, ok == true 55 | 56 | value, ok = jsonParsed.Search("outer", "inner", "value1").Data().(float64) 57 | // value == 10.0, ok == true 58 | 59 | value, ok = jsonParsed.Search("outer", "alsoInner", "array1", "1").Data().(float64) 60 | // value == 40.0, ok == true 61 | 62 | gObj, err := jsonParsed.JSONPointer("/outer/alsoInner/array1/1") 63 | if err != nil { 64 | panic(err) 65 | } 66 | value, ok = gObj.Data().(float64) 67 | // value == 40.0, ok == true 68 | 69 | value, ok = jsonParsed.Path("does.not.exist").Data().(float64) 70 | // value == 0.0, ok == false 71 | 72 | exists := jsonParsed.Exists("outer", "inner", "value1") 73 | // exists == true 74 | 75 | exists = jsonParsed.ExistsP("does.not.exist") 76 | // exists == false 77 | ``` 78 | 79 | ### Iterating objects 80 | 81 | ```go 82 | jsonParsed, err := gabs.ParseJSON([]byte(`{"object":{"first":1,"second":2,"third":3}}`)) 83 | if err != nil { 84 | panic(err) 85 | } 86 | 87 | // S is shorthand for Search 88 | for key, child := range jsonParsed.S("object").ChildrenMap() { 89 | fmt.Printf("key: %v, value: %v\n", key, child.Data().(float64)) 90 | } 91 | ``` 92 | 93 | ### Iterating arrays 94 | 95 | ```go 96 | jsonParsed, err := gabs.ParseJSON([]byte(`{"array":["first","second","third"]}`)) 97 | if err != nil { 98 | panic(err) 99 | } 100 | 101 | for _, child := range jsonParsed.S("array").Children() { 102 | fmt.Println(child.Data().(string)) 103 | } 104 | ``` 105 | 106 | Will print: 107 | 108 | ``` 109 | first 110 | second 111 | third 112 | ``` 113 | 114 | Children() will return all children of an array in order. This also works on objects, however, the children will be returned in a random order. 115 | 116 | ### Searching through arrays 117 | 118 | If your structure contains arrays you must target an index in your search. 119 | 120 | ```go 121 | jsonParsed, err := gabs.ParseJSON([]byte(`{"array":[{"value":1},{"value":2},{"value":3}]}`)) 122 | if err != nil { 123 | panic(err) 124 | } 125 | fmt.Println(jsonParsed.Path("array.1.value").String()) 126 | ``` 127 | 128 | Will print `2`. 129 | 130 | ### Generating JSON 131 | 132 | ```go 133 | jsonObj := gabs.New() 134 | // or gabs.Wrap(jsonObject) to work on an existing map[string]interface{} 135 | 136 | jsonObj.Set(10, "outer", "inner", "value") 137 | jsonObj.SetP(20, "outer.inner.value2") 138 | jsonObj.Set(30, "outer", "inner2", "value3") 139 | 140 | fmt.Println(jsonObj.String()) 141 | ``` 142 | 143 | Will print: 144 | 145 | ``` 146 | {"outer":{"inner":{"value":10,"value2":20},"inner2":{"value3":30}}} 147 | ``` 148 | 149 | To pretty-print: 150 | 151 | ```go 152 | fmt.Println(jsonObj.StringIndent("", " ")) 153 | ``` 154 | 155 | Will print: 156 | 157 | ``` 158 | { 159 | "outer": { 160 | "inner": { 161 | "value": 10, 162 | "value2": 20 163 | }, 164 | "inner2": { 165 | "value3": 30 166 | } 167 | } 168 | } 169 | ``` 170 | 171 | ### Generating Arrays 172 | 173 | ```go 174 | jsonObj := gabs.New() 175 | 176 | jsonObj.Array("foo", "array") 177 | // Or .ArrayP("foo.array") 178 | 179 | jsonObj.ArrayAppend(10, "foo", "array") 180 | jsonObj.ArrayAppend(20, "foo", "array") 181 | jsonObj.ArrayAppend(30, "foo", "array") 182 | 183 | fmt.Println(jsonObj.String()) 184 | ``` 185 | 186 | Will print: 187 | 188 | ``` 189 | {"foo":{"array":[10,20,30]}} 190 | ``` 191 | 192 | Working with arrays by index: 193 | 194 | ```go 195 | jsonObj := gabs.New() 196 | 197 | // Create an array with the length of 3 198 | jsonObj.ArrayOfSize(3, "foo") 199 | 200 | jsonObj.S("foo").SetIndex("test1", 0) 201 | jsonObj.S("foo").SetIndex("test2", 1) 202 | 203 | // Create an embedded array with the length of 3 204 | jsonObj.S("foo").ArrayOfSizeI(3, 2) 205 | 206 | jsonObj.S("foo").Index(2).SetIndex(1, 0) 207 | jsonObj.S("foo").Index(2).SetIndex(2, 1) 208 | jsonObj.S("foo").Index(2).SetIndex(3, 2) 209 | 210 | fmt.Println(jsonObj.String()) 211 | ``` 212 | 213 | Will print: 214 | 215 | ``` 216 | {"foo":["test1","test2",[1,2,3]]} 217 | ``` 218 | 219 | ### Converting back to JSON 220 | 221 | This is the easiest part: 222 | 223 | ```go 224 | jsonParsedObj, _ := gabs.ParseJSON([]byte(`{ 225 | "outer":{ 226 | "values":{ 227 | "first":10, 228 | "second":11 229 | } 230 | }, 231 | "outer2":"hello world" 232 | }`)) 233 | 234 | jsonOutput := jsonParsedObj.String() 235 | // Becomes `{"outer":{"values":{"first":10,"second":11}},"outer2":"hello world"}` 236 | ``` 237 | 238 | And to serialize a specific segment is as simple as: 239 | 240 | ```go 241 | jsonParsedObj := gabs.ParseJSON([]byte(`{ 242 | "outer":{ 243 | "values":{ 244 | "first":10, 245 | "second":11 246 | } 247 | }, 248 | "outer2":"hello world" 249 | }`)) 250 | 251 | jsonOutput := jsonParsedObj.Search("outer").String() 252 | // Becomes `{"values":{"first":10,"second":11}}` 253 | ``` 254 | 255 | ### Merge two containers 256 | 257 | You can merge a JSON structure into an existing one, where collisions will be converted into a JSON array. 258 | 259 | ```go 260 | jsonParsed1, _ := ParseJSON([]byte(`{"outer":{"value1":"one"}}`)) 261 | jsonParsed2, _ := ParseJSON([]byte(`{"outer":{"inner":{"value3":"three"}},"outer2":{"value2":"two"}}`)) 262 | 263 | jsonParsed1.Merge(jsonParsed2) 264 | // Becomes `{"outer":{"inner":{"value3":"three"},"value1":"one"},"outer2":{"value2":"two"}}` 265 | ``` 266 | 267 | Arrays are merged: 268 | 269 | ```go 270 | jsonParsed1, _ := ParseJSON([]byte(`{"array":["one"]}`)) 271 | jsonParsed2, _ := ParseJSON([]byte(`{"array":["two"]}`)) 272 | 273 | jsonParsed1.Merge(jsonParsed2) 274 | // Becomes `{"array":["one", "two"]}` 275 | ``` 276 | 277 | ### Parsing Numbers 278 | 279 | Gabs uses the `json` package under the bonnet, which by default will parse all number values into `float64`. If you need to parse `Int` values then you should use a [`json.Decoder`](https://golang.org/pkg/encoding/json/#Decoder): 280 | 281 | ```go 282 | sample := []byte(`{"test":{"int":10,"float":6.66}}`) 283 | dec := json.NewDecoder(bytes.NewReader(sample)) 284 | dec.UseNumber() 285 | 286 | val, err := gabs.ParseJSONDecoder(dec) 287 | if err != nil { 288 | t.Errorf("Failed to parse: %v", err) 289 | return 290 | } 291 | 292 | intValue, err := val.Path("test.int").Data().(json.Number).Int64() 293 | ``` 294 | 295 | [godoc-badge]: https://godoc.org/github.com/Jeffail/gabs?status.svg 296 | [godoc-url]: https://pkg.go.dev/github.com/Jeffail/gabs/v2 297 | [migration-doc]: ./migration.md 298 | -------------------------------------------------------------------------------- /gabs.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Ashley Jeffs 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // Package gabs implements a wrapper around creating and parsing unknown or 22 | // dynamic map structures resulting from JSON parsing. 23 | package gabs 24 | 25 | import ( 26 | "bytes" 27 | "encoding/json" 28 | "errors" 29 | "fmt" 30 | "io" 31 | "os" 32 | "strconv" 33 | "strings" 34 | ) 35 | 36 | //------------------------------------------------------------------------------ 37 | 38 | var ( 39 | // ErrOutOfBounds indicates an index was out of bounds. 40 | ErrOutOfBounds = errors.New("out of bounds") 41 | 42 | // ErrNotObjOrArray is returned when a target is not an object or array type 43 | // but needs to be for the intended operation. 44 | ErrNotObjOrArray = errors.New("not an object or array") 45 | 46 | // ErrNotObj is returned when a target is not an object but needs to be for 47 | // the intended operation. 48 | ErrNotObj = errors.New("not an object") 49 | 50 | // ErrInvalidQuery is returned when a seach query was not valid. 51 | ErrInvalidQuery = errors.New("invalid search query") 52 | 53 | // ErrNotArray is returned when a target is not an array but needs to be for 54 | // the intended operation. 55 | ErrNotArray = errors.New("not an array") 56 | 57 | // ErrPathCollision is returned when creating a path failed because an 58 | // element collided with an existing value. 59 | ErrPathCollision = errors.New("encountered value collision whilst building path") 60 | 61 | // ErrInvalidInputObj is returned when the input value was not a 62 | // map[string]interface{}. 63 | ErrInvalidInputObj = errors.New("invalid input object") 64 | 65 | // ErrInvalidInputText is returned when the input data could not be parsed. 66 | ErrInvalidInputText = errors.New("input text could not be parsed") 67 | 68 | // ErrNotFound is returned when a query leaf is not found. 69 | ErrNotFound = errors.New("field not found") 70 | 71 | // ErrInvalidPath is returned when the filepath was not valid. 72 | ErrInvalidPath = errors.New("invalid file path") 73 | 74 | // ErrInvalidBuffer is returned when the input buffer contained an invalid 75 | // JSON string. 76 | ErrInvalidBuffer = errors.New("input buffer contained invalid JSON") 77 | ) 78 | 79 | var ( 80 | r1 *strings.Replacer 81 | r2 *strings.Replacer 82 | ) 83 | 84 | func init() { 85 | r1 = strings.NewReplacer("~1", "/", "~0", "~") 86 | r2 = strings.NewReplacer("~1", ".", "~0", "~") 87 | } 88 | 89 | //------------------------------------------------------------------------------ 90 | 91 | // JSONPointerToSlice parses a JSON pointer path 92 | // (https://tools.ietf.org/html/rfc6901) and returns the path segments as a 93 | // slice. 94 | // 95 | // Because the characters '~' (%x7E) and '/' (%x2F) have special meanings in 96 | // gabs paths, '~' needs to be encoded as '~0' and '/' needs to be encoded as 97 | // '~1' when these characters appear in a reference key. 98 | func JSONPointerToSlice(path string) ([]string, error) { 99 | if path == "" { 100 | return nil, nil 101 | } 102 | if path[0] != '/' { 103 | return nil, errors.New("failed to resolve JSON pointer: path must begin with '/'") 104 | } 105 | if path == "/" { 106 | return []string{""}, nil 107 | } 108 | hierarchy := strings.Split(path, "/")[1:] 109 | for i, v := range hierarchy { 110 | hierarchy[i] = r1.Replace(v) 111 | } 112 | return hierarchy, nil 113 | } 114 | 115 | // DotPathToSlice returns a slice of path segments parsed out of a dot path. 116 | // 117 | // Because '.' (%x2E) is the segment separator, it must be encoded as '~1' 118 | // if it appears in the reference key. Likewise, '~' (%x7E) must be encoded 119 | // as '~0' since it is the escape character for encoding '.'. 120 | func DotPathToSlice(path string) []string { 121 | hierarchy := strings.Split(path, ".") 122 | for i, v := range hierarchy { 123 | hierarchy[i] = r2.Replace(v) 124 | } 125 | return hierarchy 126 | } 127 | 128 | //------------------------------------------------------------------------------ 129 | 130 | // Container references a specific element within a wrapped structure. 131 | type Container struct { 132 | object interface{} 133 | } 134 | 135 | // Data returns the underlying value of the target element in the wrapped 136 | // structure. 137 | func (g *Container) Data() interface{} { 138 | if g == nil { 139 | return nil 140 | } 141 | return g.object 142 | } 143 | 144 | //------------------------------------------------------------------------------ 145 | 146 | func (g *Container) searchStrict(allowWildcard bool, hierarchy ...string) (*Container, error) { 147 | object := g.Data() 148 | for target := 0; target < len(hierarchy); target++ { 149 | pathSeg := hierarchy[target] 150 | switch typedObj := object.(type) { 151 | case map[string]interface{}: 152 | var ok bool 153 | if object, ok = typedObj[pathSeg]; !ok { 154 | return nil, fmt.Errorf("failed to resolve path segment '%v': key '%v' was not found", target, pathSeg) 155 | } 156 | case []interface{}: 157 | if allowWildcard && pathSeg == "*" { 158 | var tmpArray []interface{} 159 | if (target + 1) >= len(hierarchy) { 160 | tmpArray = typedObj 161 | } else { 162 | tmpArray = make([]interface{}, 0, len(typedObj)) 163 | for _, val := range typedObj { 164 | if res := Wrap(val).Search(hierarchy[target+1:]...); res != nil { 165 | tmpArray = append(tmpArray, res.Data()) 166 | } 167 | } 168 | } 169 | 170 | if len(tmpArray) == 0 { 171 | return nil, nil 172 | } 173 | 174 | return &Container{tmpArray}, nil 175 | } 176 | index, err := strconv.Atoi(pathSeg) 177 | if err != nil { 178 | return nil, fmt.Errorf("failed to resolve path segment '%v': found array but segment value '%v' could not be parsed into array index: %v", target, pathSeg, err) 179 | } 180 | if index < 0 { 181 | return nil, fmt.Errorf("failed to resolve path segment '%v': found array but index '%v' is invalid", target, pathSeg) 182 | } 183 | if len(typedObj) <= index { 184 | return nil, fmt.Errorf("failed to resolve path segment '%v': found array but index '%v' exceeded target array size of '%v'", target, pathSeg, len(typedObj)) 185 | } 186 | object = typedObj[index] 187 | default: 188 | return nil, fmt.Errorf("failed to resolve path segment '%v': field '%v' was not found", target, pathSeg) 189 | } 190 | } 191 | return &Container{object}, nil 192 | } 193 | 194 | // Search attempts to find and return an object within the wrapped structure by 195 | // following a provided hierarchy of field names to locate the target. 196 | // 197 | // If the search encounters an array then the next hierarchy field name must be 198 | // either a an integer which is interpreted as the index of the target, or the 199 | // character '*', in which case all elements are searched with the remaining 200 | // search hierarchy and the results returned within an array. 201 | func (g *Container) Search(hierarchy ...string) *Container { 202 | c, _ := g.searchStrict(true, hierarchy...) 203 | return c 204 | } 205 | 206 | // Path searches the wrapped structure following a path in dot notation, 207 | // segments of this path are searched according to the same rules as Search. 208 | // 209 | // Because the characters '~' (%x7E) and '.' (%x2E) have special meanings in 210 | // gabs paths, '~' needs to be encoded as '~0' and '.' needs to be encoded as 211 | // '~1' when these characters appear in a reference key. 212 | func (g *Container) Path(path string) *Container { 213 | return g.Search(DotPathToSlice(path)...) 214 | } 215 | 216 | // JSONPointer parses a JSON pointer path (https://tools.ietf.org/html/rfc6901) 217 | // and either returns a *gabs.Container containing the result or an error if the 218 | // referenced item could not be found. 219 | // 220 | // Because the characters '~' (%x7E) and '/' (%x2F) have special meanings in 221 | // gabs paths, '~' needs to be encoded as '~0' and '/' needs to be encoded as 222 | // '~1' when these characters appear in a reference key. 223 | func (g *Container) JSONPointer(path string) (*Container, error) { 224 | hierarchy, err := JSONPointerToSlice(path) 225 | if err != nil { 226 | return nil, err 227 | } 228 | return g.searchStrict(false, hierarchy...) 229 | } 230 | 231 | // S is a shorthand alias for Search. 232 | func (g *Container) S(hierarchy ...string) *Container { 233 | return g.Search(hierarchy...) 234 | } 235 | 236 | // Exists checks whether a field exists within the hierarchy. 237 | func (g *Container) Exists(hierarchy ...string) bool { 238 | return g.Search(hierarchy...) != nil 239 | } 240 | 241 | // ExistsP checks whether a dot notation path exists. 242 | func (g *Container) ExistsP(path string) bool { 243 | return g.Exists(DotPathToSlice(path)...) 244 | } 245 | 246 | // Index attempts to find and return an element within a JSON array by an index. 247 | func (g *Container) Index(index int) *Container { 248 | if array, ok := g.Data().([]interface{}); ok { 249 | if index >= len(array) { 250 | return nil 251 | } 252 | return &Container{array[index]} 253 | } 254 | return nil 255 | } 256 | 257 | // Children returns a slice of all children of an array element. This also works 258 | // for objects, however, the children returned for an object will be in a random 259 | // order and you lose the names of the returned objects this way. If the 260 | // underlying container value isn't an array or map nil is returned. 261 | func (g *Container) Children() []*Container { 262 | if array, ok := g.Data().([]interface{}); ok { 263 | children := make([]*Container, len(array)) 264 | for i := 0; i < len(array); i++ { 265 | children[i] = &Container{array[i]} 266 | } 267 | return children 268 | } 269 | if mmap, ok := g.Data().(map[string]interface{}); ok { 270 | children := make([]*Container, 0, len(mmap)) 271 | for _, obj := range mmap { 272 | children = append(children, &Container{obj}) 273 | } 274 | return children 275 | } 276 | return nil 277 | } 278 | 279 | // ChildrenMap returns a map of all the children of an object element. IF the 280 | // underlying value isn't a object then an empty map is returned. 281 | func (g *Container) ChildrenMap() map[string]*Container { 282 | if mmap, ok := g.Data().(map[string]interface{}); ok { 283 | children := make(map[string]*Container, len(mmap)) 284 | for name, obj := range mmap { 285 | children[name] = &Container{obj} 286 | } 287 | return children 288 | } 289 | return map[string]*Container{} 290 | } 291 | 292 | //------------------------------------------------------------------------------ 293 | 294 | // Set attempts to set the value of a field located by a hierarchy of field 295 | // names. If the search encounters an array then the next hierarchy field name 296 | // is interpreted as an integer index of an existing element, or the character 297 | // '-', which indicates a new element appended to the end of the array. 298 | // 299 | // Any parts of the hierarchy that do not exist will be constructed as objects. 300 | // This includes parts that could be interpreted as array indexes. 301 | // 302 | // Returns a container of the new value or an error. 303 | func (g *Container) Set(value interface{}, hierarchy ...string) (*Container, error) { 304 | if g == nil { 305 | return nil, errors.New("failed to resolve path, container is nil") 306 | } 307 | if len(hierarchy) == 0 { 308 | g.object = value 309 | return g, nil 310 | } 311 | if g.object == nil { 312 | g.object = map[string]interface{}{} 313 | } 314 | object := g.object 315 | 316 | for target := 0; target < len(hierarchy); target++ { 317 | pathSeg := hierarchy[target] 318 | switch typedObj := object.(type) { 319 | case map[string]interface{}: 320 | if target == len(hierarchy)-1 { 321 | object = value 322 | typedObj[pathSeg] = object 323 | } else if object = typedObj[pathSeg]; object == nil { 324 | typedObj[pathSeg] = map[string]interface{}{} 325 | object = typedObj[pathSeg] 326 | } 327 | case []interface{}: 328 | if pathSeg == "-" { 329 | if target < 1 { 330 | return nil, errors.New("unable to append new array index at root of path") 331 | } 332 | if target == len(hierarchy)-1 { 333 | object = value 334 | } else { 335 | object = map[string]interface{}{} 336 | } 337 | typedObj = append(typedObj, object) 338 | if _, err := g.Set(typedObj, hierarchy[:target]...); err != nil { 339 | return nil, err 340 | } 341 | } else { 342 | index, err := strconv.Atoi(pathSeg) 343 | if err != nil { 344 | return nil, fmt.Errorf("failed to resolve path segment '%v': found array but segment value '%v' could not be parsed into array index: %v", target, pathSeg, err) 345 | } 346 | if index < 0 { 347 | return nil, fmt.Errorf("failed to resolve path segment '%v': found array but index '%v' is invalid", target, pathSeg) 348 | } 349 | if len(typedObj) <= index { 350 | return nil, fmt.Errorf("failed to resolve path segment '%v': found array but index '%v' exceeded target array size of '%v'", target, pathSeg, len(typedObj)) 351 | } 352 | if target == len(hierarchy)-1 { 353 | object = value 354 | typedObj[index] = object 355 | } else if object = typedObj[index]; object == nil { 356 | return nil, fmt.Errorf("failed to resolve path segment '%v': field '%v' was not found", target, pathSeg) 357 | } 358 | } 359 | default: 360 | return nil, ErrPathCollision 361 | } 362 | } 363 | return &Container{object}, nil 364 | } 365 | 366 | // SetP sets the value of a field at a path using dot notation, any parts 367 | // of the path that do not exist will be constructed, and if a collision occurs 368 | // with a non object type whilst iterating the path an error is returned. 369 | func (g *Container) SetP(value interface{}, path string) (*Container, error) { 370 | return g.Set(value, DotPathToSlice(path)...) 371 | } 372 | 373 | // SetIndex attempts to set a value of an array element based on an index. 374 | func (g *Container) SetIndex(value interface{}, index int) (*Container, error) { 375 | if array, ok := g.Data().([]interface{}); ok { 376 | if index >= len(array) { 377 | return nil, ErrOutOfBounds 378 | } 379 | array[index] = value 380 | return &Container{array[index]}, nil 381 | } 382 | return nil, ErrNotArray 383 | } 384 | 385 | // SetJSONPointer parses a JSON pointer path 386 | // (https://tools.ietf.org/html/rfc6901) and sets the leaf to a value. Returns 387 | // an error if the pointer could not be resolved due to missing fields. 388 | func (g *Container) SetJSONPointer(value interface{}, path string) (*Container, error) { 389 | hierarchy, err := JSONPointerToSlice(path) 390 | if err != nil { 391 | return nil, err 392 | } 393 | return g.Set(value, hierarchy...) 394 | } 395 | 396 | // Object creates a new JSON object at a target path. Returns an error if the 397 | // path contains a collision with a non object type. 398 | func (g *Container) Object(hierarchy ...string) (*Container, error) { 399 | return g.Set(map[string]interface{}{}, hierarchy...) 400 | } 401 | 402 | // ObjectP creates a new JSON object at a target path using dot notation. 403 | // Returns an error if the path contains a collision with a non object type. 404 | func (g *Container) ObjectP(path string) (*Container, error) { 405 | return g.Object(DotPathToSlice(path)...) 406 | } 407 | 408 | // ObjectI creates a new JSON object at an array index. Returns an error if the 409 | // object is not an array or the index is out of bounds. 410 | func (g *Container) ObjectI(index int) (*Container, error) { 411 | return g.SetIndex(map[string]interface{}{}, index) 412 | } 413 | 414 | // Array creates a new JSON array at a path. Returns an error if the path 415 | // contains a collision with a non object type. 416 | func (g *Container) Array(hierarchy ...string) (*Container, error) { 417 | return g.Set([]interface{}{}, hierarchy...) 418 | } 419 | 420 | // ArrayP creates a new JSON array at a path using dot notation. Returns an 421 | // error if the path contains a collision with a non object type. 422 | func (g *Container) ArrayP(path string) (*Container, error) { 423 | return g.Array(DotPathToSlice(path)...) 424 | } 425 | 426 | // ArrayI creates a new JSON array within an array at an index. Returns an error 427 | // if the element is not an array or the index is out of bounds. 428 | func (g *Container) ArrayI(index int) (*Container, error) { 429 | return g.SetIndex([]interface{}{}, index) 430 | } 431 | 432 | // ArrayOfSize creates a new JSON array of a particular size at a path. Returns 433 | // an error if the path contains a collision with a non object type. 434 | func (g *Container) ArrayOfSize(size int, hierarchy ...string) (*Container, error) { 435 | a := make([]interface{}, size) 436 | return g.Set(a, hierarchy...) 437 | } 438 | 439 | // ArrayOfSizeP creates a new JSON array of a particular size at a path using 440 | // dot notation. Returns an error if the path contains a collision with a non 441 | // object type. 442 | func (g *Container) ArrayOfSizeP(size int, path string) (*Container, error) { 443 | return g.ArrayOfSize(size, DotPathToSlice(path)...) 444 | } 445 | 446 | // ArrayOfSizeI create a new JSON array of a particular size within an array at 447 | // an index. Returns an error if the element is not an array or the index is out 448 | // of bounds. 449 | func (g *Container) ArrayOfSizeI(size, index int) (*Container, error) { 450 | a := make([]interface{}, size) 451 | return g.SetIndex(a, index) 452 | } 453 | 454 | // Delete an element at a path, an error is returned if the element does not 455 | // exist or is not an object. In order to remove an array element please use 456 | // ArrayRemove. 457 | func (g *Container) Delete(hierarchy ...string) error { 458 | if g == nil || g.object == nil { 459 | return ErrNotObj 460 | } 461 | if len(hierarchy) == 0 { 462 | return ErrInvalidQuery 463 | } 464 | 465 | object := g.object 466 | target := hierarchy[len(hierarchy)-1] 467 | if len(hierarchy) > 1 { 468 | object = g.Search(hierarchy[:len(hierarchy)-1]...).Data() 469 | } 470 | 471 | if obj, ok := object.(map[string]interface{}); ok { 472 | if _, ok = obj[target]; !ok { 473 | return ErrNotFound 474 | } 475 | delete(obj, target) 476 | return nil 477 | } 478 | if array, ok := object.([]interface{}); ok { 479 | if len(hierarchy) < 2 { 480 | return errors.New("unable to delete array index at root of path") 481 | } 482 | index, err := strconv.Atoi(target) 483 | if err != nil { 484 | return fmt.Errorf("failed to parse array index '%v': %v", target, err) 485 | } 486 | if index >= len(array) { 487 | return ErrOutOfBounds 488 | } 489 | if index < 0 { 490 | return ErrOutOfBounds 491 | } 492 | array = append(array[:index], array[index+1:]...) 493 | g.Set(array, hierarchy[:len(hierarchy)-1]...) 494 | return nil 495 | } 496 | return ErrNotObjOrArray 497 | } 498 | 499 | // DeleteP deletes an element at a path using dot notation, an error is returned 500 | // if the element does not exist. 501 | func (g *Container) DeleteP(path string) error { 502 | return g.Delete(DotPathToSlice(path)...) 503 | } 504 | 505 | // MergeFn merges two objects using a provided function to resolve collisions. 506 | // 507 | // The collision function receives two interface{} arguments, destination (the 508 | // original object) and source (the object being merged into the destination). 509 | // Which ever value is returned becomes the new value in the destination object 510 | // at the location of the collision. 511 | func (g *Container) MergeFn(source *Container, collisionFn func(destination, source interface{}) interface{}) error { 512 | var recursiveFnc func(map[string]interface{}, []string) error 513 | recursiveFnc = func(mmap map[string]interface{}, path []string) error { 514 | for key, value := range mmap { 515 | newPath := make([]string, len(path)) 516 | copy(newPath, path) 517 | newPath = append(newPath, key) 518 | if g.Exists(newPath...) { 519 | existingData := g.Search(newPath...).Data() 520 | switch t := value.(type) { 521 | case map[string]interface{}: 522 | switch existingVal := existingData.(type) { 523 | case map[string]interface{}: 524 | if err := recursiveFnc(t, newPath); err != nil { 525 | return err 526 | } 527 | default: 528 | if _, err := g.Set(collisionFn(existingVal, t), newPath...); err != nil { 529 | return err 530 | } 531 | } 532 | default: 533 | if _, err := g.Set(collisionFn(existingData, t), newPath...); err != nil { 534 | return err 535 | } 536 | } 537 | } else if _, err := g.Set(value, newPath...); err != nil { 538 | // path doesn't exist. So set the value 539 | return err 540 | } 541 | } 542 | return nil 543 | } 544 | if mmap, ok := source.Data().(map[string]interface{}); ok { 545 | return recursiveFnc(mmap, []string{}) 546 | } 547 | return nil 548 | } 549 | 550 | // Merge a source object into an existing destination object. When a collision 551 | // is found within the merged structures (both a source and destination object 552 | // contain the same non-object keys) the result will be an array containing both 553 | // values, where values that are already arrays will be expanded into the 554 | // resulting array. 555 | // 556 | // It is possible to merge structures will different collision behaviours with 557 | // MergeFn. 558 | func (g *Container) Merge(source *Container) error { 559 | return g.MergeFn(source, func(dest, source interface{}) interface{} { 560 | destArr, destIsArray := dest.([]interface{}) 561 | sourceArr, sourceIsArray := source.([]interface{}) 562 | if destIsArray { 563 | if sourceIsArray { 564 | return append(destArr, sourceArr...) 565 | } 566 | return append(destArr, source) 567 | } 568 | if sourceIsArray { 569 | return append(append([]interface{}{}, dest), sourceArr...) 570 | } 571 | return []interface{}{dest, source} 572 | }) 573 | } 574 | 575 | //------------------------------------------------------------------------------ 576 | 577 | /* 578 | Array modification/search - Keeping these options simple right now, no need for 579 | anything more complicated since you can just cast to []interface{}, modify and 580 | then reassign with Set. 581 | */ 582 | 583 | // ArrayAppend attempts to append a value onto a JSON array at a path. If the 584 | // target is not a JSON array then it will be converted into one, with its 585 | // original contents set to the first element of the array. 586 | func (g *Container) ArrayAppend(value interface{}, hierarchy ...string) error { 587 | if array, ok := g.Search(hierarchy...).Data().([]interface{}); ok { 588 | array = append(array, value) 589 | _, err := g.Set(array, hierarchy...) 590 | return err 591 | } 592 | 593 | newArray := []interface{}{} 594 | if d := g.Search(hierarchy...).Data(); d != nil { 595 | newArray = append(newArray, d) 596 | } 597 | newArray = append(newArray, value) 598 | 599 | _, err := g.Set(newArray, hierarchy...) 600 | return err 601 | } 602 | 603 | // ArrayAppendP attempts to append a value onto a JSON array at a path using dot 604 | // notation. If the target is not a JSON array then it will be converted into 605 | // one, with its original contents set to the first element of the array. 606 | func (g *Container) ArrayAppendP(value interface{}, path string) error { 607 | return g.ArrayAppend(value, DotPathToSlice(path)...) 608 | } 609 | 610 | // ArrayConcat attempts to append a value onto a JSON array at a path. If the 611 | // target is not a JSON array then it will be converted into one, with its 612 | // original contents set to the first element of the array. 613 | // 614 | // ArrayConcat differs from ArrayAppend in that it will expand a value type 615 | // []interface{} during the append operation, resulting in concatenation of each 616 | // element, rather than append as a single element of []interface{}. 617 | func (g *Container) ArrayConcat(value interface{}, hierarchy ...string) error { 618 | var array []interface{} 619 | if d := g.Search(hierarchy...).Data(); d != nil { 620 | if targetArray, ok := d.([]interface{}); !ok { 621 | // If the data exists, and it is not a slice of interface, 622 | // append it as the first element of our new array. 623 | array = append(array, d) 624 | } else { 625 | // If the data exists, and it is a slice of interface, 626 | // assign it to our variable. 627 | array = targetArray 628 | } 629 | } 630 | 631 | switch v := value.(type) { 632 | case []interface{}: 633 | // If we have been given a slice of interface, expand it when appending. 634 | array = append(array, v...) 635 | default: 636 | array = append(array, v) 637 | } 638 | 639 | _, err := g.Set(array, hierarchy...) 640 | 641 | return err 642 | } 643 | 644 | // ArrayConcatP attempts to append a value onto a JSON array at a path using dot 645 | // notation. If the target is not a JSON array then it will be converted into one, 646 | // with its original contents set to the first element of the array. 647 | // 648 | // ArrayConcatP differs from ArrayAppendP in that it will expand a value type 649 | // []interface{} during the append operation, resulting in concatenation of each 650 | // element, rather than append as a single element of []interface{}. 651 | func (g *Container) ArrayConcatP(value interface{}, path string) error { 652 | return g.ArrayConcat(value, DotPathToSlice(path)...) 653 | } 654 | 655 | // ArrayRemove attempts to remove an element identified by an index from a JSON 656 | // array at a path. 657 | func (g *Container) ArrayRemove(index int, hierarchy ...string) error { 658 | if index < 0 { 659 | return ErrOutOfBounds 660 | } 661 | array, ok := g.Search(hierarchy...).Data().([]interface{}) 662 | if !ok { 663 | return ErrNotArray 664 | } 665 | if index < len(array) { 666 | array = append(array[:index], array[index+1:]...) 667 | } else { 668 | return ErrOutOfBounds 669 | } 670 | _, err := g.Set(array, hierarchy...) 671 | return err 672 | } 673 | 674 | // ArrayRemoveP attempts to remove an element identified by an index from a JSON 675 | // array at a path using dot notation. 676 | func (g *Container) ArrayRemoveP(index int, path string) error { 677 | return g.ArrayRemove(index, DotPathToSlice(path)...) 678 | } 679 | 680 | // ArrayElement attempts to access an element by an index from a JSON array at a 681 | // path. 682 | func (g *Container) ArrayElement(index int, hierarchy ...string) (*Container, error) { 683 | if index < 0 { 684 | return nil, ErrOutOfBounds 685 | } 686 | array, ok := g.Search(hierarchy...).Data().([]interface{}) 687 | if !ok { 688 | return nil, ErrNotArray 689 | } 690 | if index < len(array) { 691 | return &Container{array[index]}, nil 692 | } 693 | return nil, ErrOutOfBounds 694 | } 695 | 696 | // ArrayElementP attempts to access an element by an index from a JSON array at 697 | // a path using dot notation. 698 | func (g *Container) ArrayElementP(index int, path string) (*Container, error) { 699 | return g.ArrayElement(index, DotPathToSlice(path)...) 700 | } 701 | 702 | // ArrayCount counts the number of elements in a JSON array at a path. 703 | func (g *Container) ArrayCount(hierarchy ...string) (int, error) { 704 | if array, ok := g.Search(hierarchy...).Data().([]interface{}); ok { 705 | return len(array), nil 706 | } 707 | return 0, ErrNotArray 708 | } 709 | 710 | // ArrayCountP counts the number of elements in a JSON array at a path using dot 711 | // notation. 712 | func (g *Container) ArrayCountP(path string) (int, error) { 713 | return g.ArrayCount(DotPathToSlice(path)...) 714 | } 715 | 716 | //------------------------------------------------------------------------------ 717 | 718 | func walkObject(path string, obj, flat map[string]interface{}, includeEmpty bool) { 719 | if includeEmpty && len(obj) == 0 { 720 | flat[path] = struct{}{} 721 | } 722 | for elePath, v := range obj { 723 | if len(path) > 0 { 724 | elePath = path + "." + elePath 725 | } 726 | switch t := v.(type) { 727 | case map[string]interface{}: 728 | walkObject(elePath, t, flat, includeEmpty) 729 | case []interface{}: 730 | walkArray(elePath, t, flat, includeEmpty) 731 | default: 732 | flat[elePath] = t 733 | } 734 | } 735 | } 736 | 737 | func walkArray(path string, arr []interface{}, flat map[string]interface{}, includeEmpty bool) { 738 | if includeEmpty && len(arr) == 0 { 739 | flat[path] = []struct{}{} 740 | } 741 | for i, ele := range arr { 742 | elePath := strconv.Itoa(i) 743 | if len(path) > 0 { 744 | elePath = path + "." + elePath 745 | } 746 | switch t := ele.(type) { 747 | case map[string]interface{}: 748 | walkObject(elePath, t, flat, includeEmpty) 749 | case []interface{}: 750 | walkArray(elePath, t, flat, includeEmpty) 751 | default: 752 | flat[elePath] = t 753 | } 754 | } 755 | } 756 | 757 | // Flatten a JSON array or object into an object of key/value pairs for each 758 | // field, where the key is the full path of the structured field in dot path 759 | // notation matching the spec for the method Path. 760 | // 761 | // E.g. the structure `{"foo":[{"bar":"1"},{"bar":"2"}]}` would flatten into the 762 | // object: `{"foo.0.bar":"1","foo.1.bar":"2"}`. `{"foo": [{"bar":[]},{"bar":{}}]}` 763 | // would flatten into the object `{}` 764 | // 765 | // Returns an error if the target is not a JSON object or array. 766 | func (g *Container) Flatten() (map[string]interface{}, error) { 767 | return g.flatten(false) 768 | } 769 | 770 | // FlattenIncludeEmpty a JSON array or object into an object of key/value pairs 771 | // for each field, just as Flatten, but includes empty arrays and objects, where 772 | // the key is the full path of the structured field in dot path notation matching 773 | // the spec for the method Path. 774 | // 775 | // E.g. the structure `{"foo": [{"bar":[]},{"bar":{}}]}` would flatten into the 776 | // object: `{"foo.0.bar":[],"foo.1.bar":{}}`. 777 | // 778 | // Returns an error if the target is not a JSON object or array. 779 | func (g *Container) FlattenIncludeEmpty() (map[string]interface{}, error) { 780 | return g.flatten(true) 781 | } 782 | 783 | func (g *Container) flatten(includeEmpty bool) (map[string]interface{}, error) { 784 | flattened := map[string]interface{}{} 785 | switch t := g.Data().(type) { 786 | case map[string]interface{}: 787 | walkObject("", t, flattened, includeEmpty) 788 | case []interface{}: 789 | walkArray("", t, flattened, includeEmpty) 790 | default: 791 | return nil, ErrNotObjOrArray 792 | } 793 | return flattened, nil 794 | } 795 | 796 | //------------------------------------------------------------------------------ 797 | 798 | // Bytes marshals an element to a JSON []byte blob. 799 | func (g *Container) Bytes() []byte { 800 | if data, err := json.Marshal(g.Data()); err == nil { 801 | return data 802 | } 803 | return []byte("null") 804 | } 805 | 806 | // BytesIndent marshals an element to a JSON []byte blob formatted with a prefix 807 | // and indent string. 808 | func (g *Container) BytesIndent(prefix, indent string) []byte { 809 | if g.object != nil { 810 | if data, err := json.MarshalIndent(g.Data(), prefix, indent); err == nil { 811 | return data 812 | } 813 | } 814 | return []byte("null") 815 | } 816 | 817 | // String marshals an element to a JSON formatted string. 818 | func (g *Container) String() string { 819 | return string(g.Bytes()) 820 | } 821 | 822 | // StringIndent marshals an element to a JSON string formatted with a prefix and 823 | // indent string. 824 | func (g *Container) StringIndent(prefix, indent string) string { 825 | return string(g.BytesIndent(prefix, indent)) 826 | } 827 | 828 | // EncodeOpt is a functional option for the EncodeJSON method. 829 | type EncodeOpt func(e *json.Encoder) 830 | 831 | // EncodeOptHTMLEscape sets the encoder to escape the JSON for html. 832 | func EncodeOptHTMLEscape(doEscape bool) EncodeOpt { 833 | return func(e *json.Encoder) { 834 | e.SetEscapeHTML(doEscape) 835 | } 836 | } 837 | 838 | // EncodeOptIndent sets the encoder to indent the JSON output. 839 | func EncodeOptIndent(prefix, indent string) EncodeOpt { 840 | return func(e *json.Encoder) { 841 | e.SetIndent(prefix, indent) 842 | } 843 | } 844 | 845 | // EncodeJSON marshals an element to a JSON formatted []byte using a variant 846 | // list of modifier functions for the encoder being used. Functions for 847 | // modifying the output are prefixed with EncodeOpt, e.g. EncodeOptHTMLEscape. 848 | func (g *Container) EncodeJSON(encodeOpts ...EncodeOpt) []byte { 849 | var b bytes.Buffer 850 | encoder := json.NewEncoder(&b) 851 | encoder.SetEscapeHTML(false) // Do not escape by default. 852 | for _, opt := range encodeOpts { 853 | opt(encoder) 854 | } 855 | if err := encoder.Encode(g.object); err != nil { 856 | return []byte("null") 857 | } 858 | result := b.Bytes() 859 | if len(result) > 0 { 860 | result = result[:len(result)-1] 861 | } 862 | return result 863 | } 864 | 865 | // New creates a new gabs JSON object. 866 | func New() *Container { 867 | return &Container{map[string]interface{}{}} 868 | } 869 | 870 | // Wrap an already unmarshalled JSON object (or a new map[string]interface{}) 871 | // into a *Container. 872 | func Wrap(root interface{}) *Container { 873 | return &Container{root} 874 | } 875 | 876 | // ParseJSON unmarshals a JSON byte slice into a *Container. 877 | func ParseJSON(sample []byte) (*Container, error) { 878 | var gabs Container 879 | 880 | if err := json.Unmarshal(sample, &gabs.object); err != nil { 881 | return nil, err 882 | } 883 | 884 | return &gabs, nil 885 | } 886 | 887 | // ParseJSONDecoder applies a json.Decoder to a *Container. 888 | func ParseJSONDecoder(decoder *json.Decoder) (*Container, error) { 889 | var gabs Container 890 | 891 | if err := decoder.Decode(&gabs.object); err != nil { 892 | return nil, err 893 | } 894 | 895 | return &gabs, nil 896 | } 897 | 898 | // ParseJSONFile reads a file and unmarshals the contents into a *Container. 899 | func ParseJSONFile(path string) (*Container, error) { 900 | if len(path) > 0 { 901 | cBytes, err := os.ReadFile(path) 902 | if err != nil { 903 | return nil, err 904 | } 905 | 906 | container, err := ParseJSON(cBytes) 907 | if err != nil { 908 | return nil, err 909 | } 910 | 911 | return container, nil 912 | } 913 | return nil, ErrInvalidPath 914 | } 915 | 916 | // ParseJSONBuffer reads a buffer and unmarshals the contents into a *Container. 917 | func ParseJSONBuffer(buffer io.Reader) (*Container, error) { 918 | var gabs Container 919 | jsonDecoder := json.NewDecoder(buffer) 920 | if err := jsonDecoder.Decode(&gabs.object); err != nil { 921 | return nil, err 922 | } 923 | 924 | return &gabs, nil 925 | } 926 | 927 | // MarshalJSON returns the JSON encoding of this container. This allows 928 | // structs which contain Container instances to be marshaled using 929 | // json.Marshal(). 930 | func (g *Container) MarshalJSON() ([]byte, error) { 931 | return json.Marshal(g.Data()) 932 | } 933 | 934 | //------------------------------------------------------------------------------ 935 | -------------------------------------------------------------------------------- /gabs_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jeffail/gabs/81fbfc23086ba576cb6ba8355a581e7c908f7553/gabs_logo.png -------------------------------------------------------------------------------- /gabs_test.go: -------------------------------------------------------------------------------- 1 | package gabs 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "reflect" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestBasic(t *testing.T) { 13 | sample := []byte(`{"test":{"value":10},"test2":20}`) 14 | 15 | val, err := ParseJSON(sample) 16 | if err != nil { 17 | t.Errorf("Failed to parse: %v", err) 18 | return 19 | } 20 | 21 | if result, ok := val.Search([]string{"test", "value"}...).Data().(float64); ok { 22 | if result != 10 { 23 | t.Errorf("Wrong value of result: %v", result) 24 | } 25 | } else { 26 | t.Errorf("Didn't find test.value") 27 | } 28 | 29 | if _, ok := val.Search("test2", "value").Data().(string); ok { 30 | t.Errorf("Somehow found a field that shouldn't exist") 31 | } 32 | 33 | if result, ok := val.Search("test2").Data().(float64); ok { 34 | if result != 20 { 35 | t.Errorf("Wrong value of result: %v", result) 36 | } 37 | } else { 38 | t.Errorf("Didn't find test2") 39 | } 40 | 41 | if result := val.Bytes(); !bytes.Equal(result, sample) { 42 | t.Errorf("Wrong []byte conversion: %s != %s", result, sample) 43 | } 44 | } 45 | 46 | func TestNilMethods(t *testing.T) { 47 | var n *Container 48 | if exp, act := "null", n.String(); exp != act { 49 | t.Errorf("Unexpected value: %v != %v", act, exp) 50 | } 51 | if exp, act := "null", string(n.Bytes()); exp != act { 52 | t.Errorf("Unexpected value: %v != %v", act, exp) 53 | } 54 | if n.Search("foo", "bar") != nil { 55 | t.Error("non nil result") 56 | } 57 | if n.Path("foo.bar") != nil { 58 | t.Error("non nil result") 59 | } 60 | if _, err := n.Array("foo"); err == nil { 61 | t.Error("expected error") 62 | } 63 | if err := n.ArrayAppend("foo", "bar"); err == nil { 64 | t.Error("expected error") 65 | } 66 | if err := n.ArrayRemove(1, "foo", "bar"); err == nil { 67 | t.Error("expected error") 68 | } 69 | if n.Exists("foo", "bar") { 70 | t.Error("expected false") 71 | } 72 | if n.Index(1) != nil { 73 | t.Error("non nil result") 74 | } 75 | if n.Children() != nil { 76 | t.Error("non nil result") 77 | } 78 | if len(n.ChildrenMap()) > 0 { 79 | t.Error("non nil result") 80 | } 81 | if err := n.Delete("foo"); err == nil { 82 | t.Error("expected error") 83 | } 84 | } 85 | 86 | var bigSample = []byte(`{ 87 | "a": { 88 | "nested1": { 89 | "value1": 5 90 | } 91 | }, 92 | "": { 93 | "can we access": "this?" 94 | }, 95 | "what/a/pain": "ouch1", 96 | "what~a~pain": "ouch2", 97 | "what~/a/~pain": "ouch3", 98 | "what.a.pain": "ouch4", 99 | "what~.a.~pain": "ouch5", 100 | "b": 10, 101 | "c": [ 102 | "first", 103 | "second", 104 | { 105 | "nested2": { 106 | "value2": 15 107 | } 108 | }, 109 | [ 110 | "fifth", 111 | "sixth" 112 | ], 113 | "fourth" 114 | ], 115 | "d": { 116 | "": { 117 | "what about": "this?" 118 | } 119 | } 120 | }`) 121 | 122 | func TestJSONPointer(t *testing.T) { 123 | type testCase struct { 124 | path string 125 | value string 126 | err string 127 | input string 128 | } 129 | tests := []testCase{ 130 | { 131 | path: "foo", 132 | err: "failed to resolve JSON pointer: path must begin with '/'", 133 | }, 134 | { 135 | path: "", 136 | value: `{"whole":{"document":"is this"}}`, 137 | input: `{"whole":{"document":"is this"}}`, 138 | }, 139 | { 140 | path: "/", 141 | value: `{"":{"b":"value b"},"a":"value a"}`, 142 | input: `{"":{"a":"value a","":{"b":"value b"}}}`, 143 | }, 144 | { 145 | path: "//", 146 | value: `{"b":"value b"}`, 147 | input: `{"":{"a":"value a","":{"b":"value b"}}}`, 148 | }, 149 | { 150 | path: "/a/doesnotexist", 151 | err: "failed to resolve path segment '1': key 'doesnotexist' was not found", 152 | }, 153 | { 154 | path: "/a", 155 | value: `{"nested1":{"value1":5}}`, 156 | }, 157 | { 158 | path: "/what~1a~1pain", 159 | value: `"ouch1"`, 160 | }, 161 | { 162 | path: "/what~0a~0pain", 163 | value: `"ouch2"`, 164 | }, 165 | { 166 | path: "/what~0~1a~1~0pain", 167 | value: `"ouch3"`, 168 | }, 169 | { 170 | path: "/what.a.pain", 171 | value: `"ouch4"`, 172 | }, 173 | { 174 | path: "/what~0.a.~0pain", 175 | value: `"ouch5"`, 176 | }, 177 | { 178 | path: "//can we access", 179 | value: `"this?"`, 180 | }, 181 | { 182 | path: "/d/", 183 | value: `{"what about":"this?"}`, 184 | }, 185 | { 186 | path: "/d//what about", 187 | value: `"this?"`, 188 | }, 189 | { 190 | path: "/c/1", 191 | value: `"second"`, 192 | }, 193 | { 194 | path: "/c/2/nested2/value2", 195 | value: `15`, 196 | }, 197 | { 198 | path: "/c/notindex/value2", 199 | err: `failed to resolve path segment '1': found array but segment value 'notindex' could not be parsed into array index: strconv.Atoi: parsing "notindex": invalid syntax`, 200 | }, 201 | { 202 | path: "/c/10/value2", 203 | err: `failed to resolve path segment '1': found array but index '10' exceeded target array size of '5'`, 204 | }, 205 | } 206 | 207 | for _, test := range tests { 208 | t.Run(test.path, func(tt *testing.T) { 209 | input := test.input 210 | if input == "" { 211 | input = string(bigSample) 212 | } 213 | root, err := ParseJSON([]byte(input)) 214 | if err != nil { 215 | t.Fatalf("Failed to parse: %v", err) 216 | } 217 | 218 | var result *Container 219 | result, err = root.JSONPointer(test.path) 220 | if len(test.err) > 0 { 221 | if err == nil { 222 | tt.Errorf("Expected error: %v", test.err) 223 | } else if exp, act := test.err, err.Error(); exp != act { 224 | tt.Errorf("Wrong error returned: %v != %v", act, exp) 225 | } 226 | return 227 | } else if err != nil { 228 | tt.Fatal(err) 229 | } 230 | if exp, act := test.value, result.String(); exp != act { 231 | tt.Errorf("Wrong result: %v != %v", act, exp) 232 | } 233 | }) 234 | } 235 | } 236 | 237 | func TestDotPath(t *testing.T) { 238 | type testCase struct { 239 | path string 240 | value string 241 | } 242 | tests := []testCase{ 243 | { 244 | path: "foo", 245 | value: "null", 246 | }, 247 | { 248 | path: "a.doesnotexist", 249 | value: "null", 250 | }, 251 | { 252 | path: "a", 253 | value: `{"nested1":{"value1":5}}`, 254 | }, 255 | { 256 | path: "what/a/pain", 257 | value: `"ouch1"`, 258 | }, 259 | { 260 | path: "what~0a~0pain", 261 | value: `"ouch2"`, 262 | }, 263 | { 264 | path: "what~0/a/~0pain", 265 | value: `"ouch3"`, 266 | }, 267 | { 268 | path: "what~1a~1pain", 269 | value: `"ouch4"`, 270 | }, 271 | { 272 | path: "what~0~1a~1~0pain", 273 | value: `"ouch5"`, 274 | }, 275 | { 276 | path: "", 277 | value: `{"can we access":"this?"}`, 278 | }, 279 | { 280 | path: ".can we access", 281 | value: `"this?"`, 282 | }, 283 | { 284 | path: "d.", 285 | value: `{"what about":"this?"}`, 286 | }, 287 | { 288 | path: "d..what about", 289 | value: `"this?"`, 290 | }, 291 | { 292 | path: "c.1", 293 | value: `"second"`, 294 | }, 295 | { 296 | path: "c.2.nested2.value2", 297 | value: `15`, 298 | }, 299 | { 300 | path: "c.notindex.value2", 301 | value: "null", 302 | }, 303 | { 304 | path: "c.10.value2", 305 | value: "null", 306 | }, 307 | } 308 | 309 | root, err := ParseJSON(bigSample) 310 | if err != nil { 311 | t.Fatalf("Failed to parse: %v", err) 312 | } 313 | 314 | for _, test := range tests { 315 | t.Run(test.path, func(tt *testing.T) { 316 | result := root.Path(test.path) 317 | if exp, act := test.value, result.String(); exp != act { 318 | tt.Errorf("Wrong result: %v != %v", act, exp) 319 | } 320 | }) 321 | } 322 | } 323 | 324 | func TestArrayWildcard(t *testing.T) { 325 | sample := []byte(`{"test":[{"value":10},{"value":20}]}`) 326 | 327 | val, err := ParseJSON(sample) 328 | if err != nil { 329 | t.Fatalf("Failed to parse: %v", err) 330 | } 331 | 332 | if act, ok := val.Search([]string{"test", "0", "value"}...).Data().(float64); ok { 333 | if exp := float64(10); !reflect.DeepEqual(act, exp) { 334 | t.Errorf("Wrong result: %v != %v", act, exp) 335 | } 336 | } else { 337 | t.Errorf("Didn't find test.0.value") 338 | } 339 | 340 | if act, ok := val.Search([]string{"test", "1", "value"}...).Data().(float64); ok { 341 | if exp := float64(20); !reflect.DeepEqual(act, exp) { 342 | t.Errorf("Wrong result: %v != %v", act, exp) 343 | } 344 | } else { 345 | t.Errorf("Didn't find test.1.value") 346 | } 347 | 348 | if act, ok := val.Search([]string{"test", "*", "value"}...).Data().([]interface{}); ok { 349 | if exp := []interface{}{float64(10), float64(20)}; !reflect.DeepEqual(act, exp) { 350 | t.Errorf("Wrong result: %v != %v", act, exp) 351 | } 352 | } else { 353 | t.Errorf("Didn't find test.*.value") 354 | } 355 | 356 | if act := val.Search([]string{"test", "*", "notmatched"}...); act != nil { 357 | t.Errorf("Expected nil result, received: %v", act) 358 | } 359 | 360 | if act, ok := val.Search([]string{"test", "*"}...).Data().([]interface{}); ok { 361 | if exp := []interface{}{map[string]interface{}{"value": float64(10)}, map[string]interface{}{"value": float64(20)}}; !reflect.DeepEqual(act, exp) { 362 | t.Errorf("Wrong result: %v != %v", act, exp) 363 | } 364 | } else { 365 | t.Errorf("Didn't find test.*.value") 366 | } 367 | } 368 | 369 | func TestArrayAppendWithSet(t *testing.T) { 370 | gObj := New() 371 | if _, err := gObj.Set([]interface{}{}, "foo"); err != nil { 372 | t.Fatal(err) 373 | } 374 | if _, err := gObj.Set(1, "foo", "-"); err != nil { 375 | t.Fatal(err) 376 | } 377 | if _, err := gObj.Set([]interface{}{}, "foo", "-", "baz"); err != nil { 378 | t.Fatal(err) 379 | } 380 | if _, err := gObj.Set(2, "foo", "1", "baz", "-"); err != nil { 381 | t.Fatal(err) 382 | } 383 | if _, err := gObj.Set(3, "foo", "1", "baz", "-"); err != nil { 384 | t.Fatal(err) 385 | } 386 | if _, err := gObj.Set(4, "foo", "-"); err != nil { 387 | t.Fatal(err) 388 | } 389 | 390 | exp := `{"foo":[1,{"baz":[2,3]},4]}` 391 | if act := gObj.String(); act != exp { 392 | t.Errorf("Unexpected value: %v != %v", act, exp) 393 | } 394 | } 395 | 396 | func TestExists(t *testing.T) { 397 | sample := []byte(`{"test":{"value":10,"nullvalue":null},"test2":20,"testnull":null}`) 398 | 399 | val, err := ParseJSON(sample) 400 | if err != nil { 401 | t.Errorf("Failed to parse: %v", err) 402 | return 403 | } 404 | 405 | paths := []struct { 406 | Path []string 407 | Exists bool 408 | }{ 409 | {[]string{"one", "two", "three"}, false}, 410 | {[]string{"test"}, true}, 411 | {[]string{"test", "value"}, true}, 412 | {[]string{"test", "nullvalue"}, true}, 413 | {[]string{"test2"}, true}, 414 | {[]string{"testnull"}, true}, 415 | {[]string{"test2", "value"}, false}, 416 | {[]string{"test", "value2"}, false}, 417 | {[]string{"test", "VALUE"}, false}, 418 | } 419 | 420 | for _, p := range paths { 421 | if exp, actual := p.Exists, val.Exists(p.Path...); exp != actual { 422 | t.Errorf("Wrong result from Exists: %v != %v, for path: %v", exp, actual, p.Path) 423 | } 424 | if exp, actual := p.Exists, val.ExistsP(strings.Join(p.Path, ".")); exp != actual { 425 | t.Errorf("Wrong result from ExistsP: %v != %v, for path: %v", exp, actual, p.Path) 426 | } 427 | } 428 | } 429 | 430 | func TestExistsWithArrays(t *testing.T) { 431 | sample := []byte(`{"foo":{"bar":{"baz":[10, 2, 3]}}}`) 432 | 433 | val, err := ParseJSON(sample) 434 | if err != nil { 435 | t.Errorf("Failed to parse: %v", err) 436 | return 437 | } 438 | 439 | if exp, actual := true, val.Exists("foo", "bar", "baz"); exp != actual { 440 | t.Errorf("Wrong result from array based Exists: %v != %v", exp, actual) 441 | } 442 | 443 | sample = []byte(`{"foo":{"bar":[{"baz":10},{"baz":2},{"baz":3}]}}`) 444 | 445 | if val, err = ParseJSON(sample); err != nil { 446 | t.Errorf("Failed to parse: %v", err) 447 | return 448 | } 449 | 450 | if exp, actual := true, val.Exists("foo", "bar", "0", "baz"); exp != actual { 451 | t.Errorf("Wrong result from array based Exists: %v != %v", exp, actual) 452 | } 453 | if exp, actual := false, val.Exists("foo", "bar", "1", "baz_NOPE"); exp != actual { 454 | t.Errorf("Wrong result from array based Exists: %v != %v", exp, actual) 455 | } 456 | 457 | sample = []byte(`{"foo":[{"bar":{"baz":10}},{"bar":{"baz":2}},{"bar":{"baz":3}}]}`) 458 | 459 | if val, err = ParseJSON(sample); err != nil { 460 | t.Errorf("Failed to parse: %v", err) 461 | return 462 | } 463 | 464 | if exp, actual := true, val.Exists("foo", "0", "bar", "baz"); exp != actual { 465 | t.Errorf("Wrong result from array based Exists: %v != %v", exp, actual) 466 | } 467 | if exp, actual := false, val.Exists("foo", "0", "bar", "baz_NOPE"); exp != actual { 468 | t.Errorf("Wrong result from array based Exists: %v != %v", exp, actual) 469 | } 470 | 471 | sample = 472 | []byte(`[{"foo":{"bar":{"baz":10}}},{"foo":{"bar":{"baz":2}}},{"foo":{"bar":{"baz":3}}}]`) 473 | 474 | if val, err = ParseJSON(sample); err != nil { 475 | t.Errorf("Failed to parse: %v", err) 476 | return 477 | } 478 | 479 | if exp, actual := true, val.Exists("0", "foo", "bar", "baz"); exp != actual { 480 | t.Errorf("Wrong result from array based Exists: %v != %v", exp, actual) 481 | } 482 | if exp, actual := false, val.Exists("0", "foo", "bar", "baz_NOPE"); exp != actual { 483 | t.Errorf("Wrong result from array based Exists: %v != %v", exp, actual) 484 | } 485 | } 486 | 487 | func TestBasicWithBuffer(t *testing.T) { 488 | sample := bytes.NewReader([]byte(`{"test":{"value":10},"test2":20}`)) 489 | 490 | _, err := ParseJSONBuffer(sample) 491 | if err != nil { 492 | t.Errorf("Failed to parse: %v", err) 493 | return 494 | } 495 | } 496 | 497 | func TestBasicWithDecoder(t *testing.T) { 498 | sample := []byte(`{"test":{"int":10, "float":6.66}}`) 499 | dec := json.NewDecoder(bytes.NewReader(sample)) 500 | dec.UseNumber() 501 | 502 | val, err := ParseJSONDecoder(dec) 503 | if err != nil { 504 | t.Errorf("Failed to parse: %v", err) 505 | return 506 | } 507 | 508 | checkNumber := func(path string, expectedVal json.Number) { 509 | data := val.Path(path).Data() 510 | asNumber, isNumber := data.(json.Number) 511 | if !isNumber { 512 | t.Error("Failed to parse using decoder UseNumber policy") 513 | } 514 | if expectedVal != asNumber { 515 | t.Errorf("Expected[%s] but got [%s]", expectedVal, asNumber) 516 | } 517 | } 518 | 519 | checkNumber("test.int", "10") 520 | checkNumber("test.float", "6.66") 521 | } 522 | 523 | func TestFailureWithDecoder(t *testing.T) { 524 | sample := []byte(`{"test":{" "invalidCrap":.66}}`) 525 | dec := json.NewDecoder(bytes.NewReader(sample)) 526 | 527 | _, err := ParseJSONDecoder(dec) 528 | if err == nil { 529 | t.Fatal("Expected parsing error") 530 | } 531 | } 532 | 533 | func TestDeletes(t *testing.T) { 534 | jsonParsed, _ := ParseJSON([]byte(`{ 535 | "outter":{ 536 | "inner":{ 537 | "value1":10, 538 | "value2":22, 539 | "value3":32 540 | }, 541 | "alsoInner":{ 542 | "value1":20, 543 | "value2":42, 544 | "value3":92 545 | }, 546 | "another":{ 547 | "value1":null, 548 | "value2":null, 549 | "value3":null 550 | } 551 | } 552 | }`)) 553 | 554 | if err := jsonParsed.Delete("outter", "inner", "value2"); err != nil { 555 | t.Error(err) 556 | } 557 | if err := jsonParsed.Delete("outter", "inner", "value4"); err == nil { 558 | t.Error("value4 should not have been found in outter.inner") 559 | } 560 | if err := jsonParsed.Delete("outter", "another", "value1"); err != nil { 561 | t.Error(err) 562 | } 563 | if err := jsonParsed.Delete("outter", "another", "value4"); err == nil { 564 | t.Error("value4 should not have been found in outter.another") 565 | } 566 | if err := jsonParsed.DeleteP("outter.alsoInner.value1"); err != nil { 567 | t.Error(err) 568 | } 569 | if err := jsonParsed.DeleteP("outter.alsoInner.value4"); err == nil { 570 | t.Error("value4 should not have been found in outter.alsoInner") 571 | } 572 | if err := jsonParsed.DeleteP("outter.another.value2"); err != nil { 573 | t.Error(err) 574 | } 575 | if err := jsonParsed.Delete("outter.another.value4"); err == nil { 576 | t.Error("value4 should not have been found in outter.another") 577 | } 578 | 579 | expected := `{"outter":{"alsoInner":{"value2":42,"value3":92},"another":{"value3":null},"inner":{"value1":10,"value3":32}}}` 580 | if actual := jsonParsed.String(); actual != expected { 581 | t.Errorf("Unexpected result from deletes: %v != %v", actual, expected) 582 | } 583 | } 584 | 585 | func TestDeletesWithArrays(t *testing.T) { 586 | rawJSON := `{ 587 | "outter":[ 588 | { 589 | "foo":{ 590 | "value1":10, 591 | "value2":22, 592 | "value3":32 593 | }, 594 | "bar": [ 595 | 20, 596 | 42, 597 | 92 598 | ] 599 | }, 600 | { 601 | "baz":{ 602 | "value1":null, 603 | "value2":null, 604 | "value3":null 605 | } 606 | } 607 | ] 608 | }` 609 | 610 | jsonParsed, err := ParseJSON([]byte(rawJSON)) 611 | if err != nil { 612 | t.Fatal(err) 613 | } 614 | if err = jsonParsed.Delete("outter", "1", "baz", "value1"); err != nil { 615 | t.Error(err) 616 | } 617 | 618 | expected := `{"outter":[{"bar":[20,42,92],"foo":{"value1":10,"value2":22,"value3":32}},{"baz":{"value2":null,"value3":null}}]}` 619 | if actual := jsonParsed.String(); actual != expected { 620 | t.Errorf("Unexpected result from array deletes: %v != %v", actual, expected) 621 | } 622 | 623 | jsonParsed, err = ParseJSON([]byte(rawJSON)) 624 | if err != nil { 625 | t.Fatal(err) 626 | } 627 | if err = jsonParsed.Delete("outter", "1", "baz"); err != nil { 628 | t.Error(err) 629 | } 630 | 631 | expected = `{"outter":[{"bar":[20,42,92],"foo":{"value1":10,"value2":22,"value3":32}},{}]}` 632 | if actual := jsonParsed.String(); actual != expected { 633 | t.Errorf("Unexpected result from array deletes: %v != %v", actual, expected) 634 | } 635 | 636 | jsonParsed, err = ParseJSON([]byte(rawJSON)) 637 | if err != nil { 638 | t.Fatal(err) 639 | } 640 | if err = jsonParsed.Delete("outter", "1"); err != nil { 641 | t.Error(err) 642 | } 643 | 644 | expected = `{"outter":[{"bar":[20,42,92],"foo":{"value1":10,"value2":22,"value3":32}}]}` 645 | if actual := jsonParsed.String(); actual != expected { 646 | t.Errorf("Unexpected result from array deletes: %v != %v", actual, expected) 647 | } 648 | 649 | jsonParsed, err = ParseJSON([]byte(rawJSON)) 650 | if err != nil { 651 | t.Fatal(err) 652 | } 653 | if err = jsonParsed.Delete("outter", "0", "bar", "0"); err != nil { 654 | t.Error(err) 655 | } 656 | 657 | expected = `{"outter":[{"bar":[42,92],"foo":{"value1":10,"value2":22,"value3":32}},{"baz":{"value1":null,"value2":null,"value3":null}}]}` 658 | if actual := jsonParsed.String(); actual != expected { 659 | t.Errorf("Unexpected result from array deletes: %v != %v", actual, expected) 660 | } 661 | 662 | jsonParsed, err = ParseJSON([]byte(rawJSON)) 663 | if err != nil { 664 | t.Fatal(err) 665 | } 666 | if err = jsonParsed.Delete("outter", "0", "bar", "1"); err != nil { 667 | t.Error(err) 668 | } 669 | 670 | expected = `{"outter":[{"bar":[20,92],"foo":{"value1":10,"value2":22,"value3":32}},{"baz":{"value1":null,"value2":null,"value3":null}}]}` 671 | if actual := jsonParsed.String(); actual != expected { 672 | t.Errorf("Unexpected result from array deletes: %v != %v", actual, expected) 673 | } 674 | 675 | jsonParsed, err = ParseJSON([]byte(rawJSON)) 676 | if err != nil { 677 | t.Fatal(err) 678 | } 679 | if err = jsonParsed.Delete("outter", "0", "bar", "2"); err != nil { 680 | t.Error(err) 681 | } 682 | 683 | expected = `{"outter":[{"bar":[20,42],"foo":{"value1":10,"value2":22,"value3":32}},{"baz":{"value1":null,"value2":null,"value3":null}}]}` 684 | if actual := jsonParsed.String(); actual != expected { 685 | t.Errorf("Unexpected result from array deletes: %v != %v", actual, expected) 686 | } 687 | } 688 | 689 | func TestExamples(t *testing.T) { 690 | jsonParsed, err := ParseJSON([]byte(`{ 691 | "outter":{ 692 | "inner":{ 693 | "value1":10, 694 | "value2":22 695 | }, 696 | "contains.dots.in.key":{ 697 | "value1":11 698 | }, 699 | "contains~tildes~in~key":{ 700 | "value1":12 701 | }, 702 | "alsoInner":{ 703 | "value1":20, 704 | "array1":[ 705 | 30, 40 706 | ] 707 | } 708 | } 709 | }`)) 710 | if err != nil { 711 | t.Errorf("Error: %v", err) 712 | return 713 | } 714 | 715 | var value float64 716 | var ok bool 717 | 718 | value, ok = jsonParsed.Path("outter.inner.value1").Data().(float64) 719 | if value != 10.0 || !ok { 720 | t.Errorf("wrong value: %v, %v", value, ok) 721 | } 722 | 723 | value, ok = jsonParsed.Path("outter.contains~1dots~1in~1key.value1").Data().(float64) 724 | if value != 11.0 || !ok { 725 | t.Errorf("wrong value: %v, %v", value, ok) 726 | } 727 | 728 | value, ok = jsonParsed.Path("outter.contains~0tildes~0in~0key.value1").Data().(float64) 729 | if value != 12.0 || !ok { 730 | t.Errorf("wrong value: %v, %v", value, ok) 731 | } 732 | 733 | value, ok = jsonParsed.Search("outter", "inner", "value1").Data().(float64) 734 | if value != 10.0 || !ok { 735 | t.Errorf("wrong value: %v, %v", value, ok) 736 | } 737 | 738 | gObj, err := jsonParsed.JSONPointer("/outter/alsoInner/array1/1") 739 | if err != nil { 740 | t.Fatal(err) 741 | } 742 | value, ok = gObj.Data().(float64) 743 | if value != 40.0 || !ok { 744 | t.Errorf("wrong value: %v, %v", value, ok) 745 | } 746 | 747 | value, ok = jsonParsed.Path("does.not.exist").Data().(float64) 748 | if value != 0.0 || ok { 749 | t.Errorf("wrong value: %v, %v", value, ok) 750 | } 751 | 752 | jsonParsed, _ = ParseJSON([]byte(`{"array":[ "first", "second", "third" ]}`)) 753 | 754 | expected := []string{"first", "second", "third"} 755 | 756 | children := jsonParsed.S("array").Children() 757 | for i, child := range children { 758 | if expected[i] != child.Data().(string) { 759 | t.Errorf("Child unexpected: %v != %v", expected[i], child.Data().(string)) 760 | } 761 | } 762 | } 763 | 764 | func TestSetAppendArray(t *testing.T) { 765 | content := []byte(`{ 766 | "nested": { 767 | "source": [ 768 | "foo", "bar" 769 | ] 770 | } 771 | }`) 772 | 773 | gObj, err := ParseJSON(content) 774 | if err != nil { 775 | t.Fatal(err) 776 | } 777 | if _, err = gObj.Set("baz", "nested", "source", "-"); err != nil { 778 | t.Fatal(err) 779 | } 780 | exp := `{"nested":{"source":["foo","bar","baz"]}}` 781 | if act := gObj.String(); act != exp { 782 | t.Errorf("Wrong result: %v != %v", act, exp) 783 | } 784 | } 785 | 786 | func TestExamples2(t *testing.T) { 787 | var err error 788 | 789 | jsonObj := New() 790 | 791 | _, err = jsonObj.Set(10, "outter", "inner", "value") 792 | if err != nil { 793 | t.Errorf("Error: %v", err) 794 | return 795 | } 796 | _, err = jsonObj.SetP(20, "outter.inner.value2") 797 | if err != nil { 798 | t.Errorf("Error: %v", err) 799 | return 800 | } 801 | _, err = jsonObj.Set(30, "outter", "inner2", "value3") 802 | if err != nil { 803 | t.Errorf("Error: %v", err) 804 | return 805 | } 806 | 807 | expected := `{"outter":{"inner":{"value":10,"value2":20},"inner2":{"value3":30}}}` 808 | if jsonObj.String() != expected { 809 | t.Errorf("Non matched output: %v != %v", expected, jsonObj.String()) 810 | } 811 | 812 | jsonObj = Wrap(map[string]interface{}{}) 813 | 814 | jsonObj.Array("array") 815 | 816 | jsonObj.ArrayAppend(10, "array") 817 | jsonObj.ArrayAppend(20, "array") 818 | jsonObj.ArrayAppend(30, "array") 819 | 820 | expected = `{ 821 | "array": [ 822 | 10, 823 | 20, 824 | 30 825 | ] 826 | }` 827 | result := jsonObj.StringIndent(" ", " ") 828 | if result != expected { 829 | t.Errorf("Non matched output: %v != %v", expected, result) 830 | } 831 | } 832 | 833 | func TestExamples3(t *testing.T) { 834 | jsonObj := New() 835 | 836 | jsonObj.ArrayP("foo.array") 837 | 838 | jsonObj.ArrayAppend(10, "foo", "array") 839 | jsonObj.ArrayAppend(20, "foo", "array") 840 | jsonObj.ArrayAppend(30, "foo", "array") 841 | 842 | result := jsonObj.String() 843 | expected := `{"foo":{"array":[10,20,30]}}` 844 | 845 | if result != expected { 846 | t.Errorf("Non matched output: %v != %v", result, expected) 847 | } 848 | } 849 | 850 | func TestArrayConcat(t *testing.T) { 851 | jsonObj := New() 852 | 853 | jsonObj.ArrayP("foo.array") 854 | 855 | jsonObj.ArrayConcat(10, "foo", "array") 856 | jsonObj.ArrayConcat([]interface{}{20, 30}, "foo", "array") 857 | 858 | result := jsonObj.String() 859 | expected := `{"foo":{"array":[10,20,30]}}` 860 | 861 | if result != expected { 862 | t.Errorf("Non matched output: %v != %v", result, expected) 863 | } 864 | 865 | jsonObj = New() 866 | 867 | jsonObj.ArrayP("foo.array") 868 | 869 | jsonObj.ArrayConcat([]interface{}{10, 20}, "foo", "array") 870 | jsonObj.ArrayConcat(30, "foo", "array") 871 | 872 | result = jsonObj.String() 873 | expected = `{"foo":{"array":[10,20,30]}}` 874 | 875 | if result != expected { 876 | t.Errorf("Non matched output: %v != %v", result, expected) 877 | } 878 | 879 | jsonObj = New() 880 | 881 | jsonObj.ArrayP("foo.array") 882 | 883 | jsonObj.ArrayConcat([]interface{}{10}, "foo", "array") 884 | jsonObj.ArrayConcat([]interface{}{20}, "foo", "array") 885 | jsonObj.ArrayConcat([]interface{}{30}, "foo", "array") 886 | 887 | result = jsonObj.String() 888 | expected = `{"foo":{"array":[10,20,30]}}` 889 | 890 | if result != expected { 891 | t.Errorf("Non matched output: %v != %v", result, expected) 892 | } 893 | } 894 | 895 | func TestArrayConcatP(t *testing.T) { 896 | jsonObj := New() 897 | 898 | jsonObj.ArrayP("foo.array") 899 | 900 | jsonObj.ArrayConcatP(10, "foo.array") 901 | jsonObj.ArrayConcatP([]interface{}{20, 30}, "foo.array") 902 | 903 | result := jsonObj.String() 904 | expected := `{"foo":{"array":[10,20,30]}}` 905 | 906 | if result != expected { 907 | t.Errorf("Non matched output: %v != %v", result, expected) 908 | } 909 | 910 | jsonObj = New() 911 | 912 | jsonObj.ArrayP("foo.array") 913 | 914 | jsonObj.ArrayConcatP([]interface{}{10, 20}, "foo.array") 915 | jsonObj.ArrayConcatP(30, "foo.array") 916 | 917 | result = jsonObj.String() 918 | expected = `{"foo":{"array":[10,20,30]}}` 919 | 920 | if result != expected { 921 | t.Errorf("Non matched output: %v != %v", result, expected) 922 | } 923 | 924 | jsonObj = New() 925 | 926 | jsonObj.ArrayP("foo.array") 927 | 928 | jsonObj.ArrayConcatP([]interface{}{10}, "foo.array") 929 | jsonObj.ArrayConcatP([]interface{}{20}, "foo.array") 930 | jsonObj.ArrayConcatP([]interface{}{30}, "foo.array") 931 | 932 | result = jsonObj.String() 933 | expected = `{"foo":{"array":[10,20,30]}}` 934 | 935 | if result != expected { 936 | t.Errorf("Non matched output: %v != %v", result, expected) 937 | } 938 | } 939 | 940 | func TestDotNotation(t *testing.T) { 941 | sample := []byte(`{"test":{"inner":{"value":10}},"test2":20}`) 942 | 943 | val, err := ParseJSON(sample) 944 | if err != nil { 945 | t.Errorf("Failed to parse: %v", err) 946 | return 947 | } 948 | 949 | if result, _ := val.Path("test.inner.value").Data().(float64); result != 10 { 950 | t.Errorf("Expected 10, received: %v", result) 951 | } 952 | } 953 | 954 | func TestModify(t *testing.T) { 955 | sample := []byte(`{"test":{"value":10},"test2":20}`) 956 | 957 | val, err := ParseJSON(sample) 958 | if err != nil { 959 | t.Errorf("Failed to parse: %v", err) 960 | return 961 | } 962 | 963 | if _, err := val.S("test").Set(45.0, "value"); err != nil { 964 | t.Errorf("Failed to set field") 965 | } 966 | 967 | if result, ok := val.Search([]string{"test", "value"}...).Data().(float64); ok { 968 | if result != 45 { 969 | t.Errorf("Wrong value of result: %v", result) 970 | } 971 | } else { 972 | t.Errorf("Didn't find test.value") 973 | } 974 | 975 | if out := val.String(); out != `{"test":{"value":45},"test2":20}` { 976 | t.Errorf("Incorrectly serialized: %v", out) 977 | } 978 | 979 | if out := val.Search("test").String(); out != `{"value":45}` { 980 | t.Errorf("Incorrectly serialized: %v", out) 981 | } 982 | } 983 | 984 | func TestChildren(t *testing.T) { 985 | json1, _ := ParseJSON([]byte(`{ 986 | "objectOne":{ 987 | }, 988 | "objectTwo":{ 989 | }, 990 | "objectThree":{ 991 | } 992 | }`)) 993 | 994 | objects := json1.Children() 995 | for _, object := range objects { 996 | object.Set("hello world", "child") 997 | } 998 | 999 | expected := `{"objectOne":{"child":"hello world"},"objectThree":{"child":"hello world"}` + 1000 | `,"objectTwo":{"child":"hello world"}}` 1001 | received := json1.String() 1002 | if expected != received { 1003 | t.Errorf("json1: expected %v, received %v", expected, received) 1004 | } 1005 | 1006 | json2, _ := ParseJSON([]byte(`{ 1007 | "values":[ 1008 | { 1009 | "objectOne":{ 1010 | } 1011 | }, 1012 | { 1013 | "objectTwo":{ 1014 | } 1015 | }, 1016 | { 1017 | "objectThree":{ 1018 | } 1019 | } 1020 | ] 1021 | }`)) 1022 | 1023 | json3, _ := ParseJSON([]byte(`{ 1024 | "values":[ 1025 | ] 1026 | }`)) 1027 | 1028 | numChildren1, _ := json2.ArrayCount("values") 1029 | numChildren2, _ := json3.ArrayCount("values") 1030 | if _, err := json3.ArrayCount("valuesNOTREAL"); err == nil { 1031 | t.Errorf("expected numChildren3 to fail") 1032 | } 1033 | 1034 | if numChildren1 != 3 || numChildren2 != 0 { 1035 | t.Errorf("CountElements, expected 3 and 0, received %v and %v", 1036 | numChildren1, numChildren2) 1037 | } 1038 | 1039 | objects = json2.S("values").Children() 1040 | for _, object := range objects { 1041 | object.Set("hello world", "child") 1042 | json3.ArrayAppend(object.Data(), "values") 1043 | } 1044 | 1045 | expected = `{"values":[{"child":"hello world","objectOne":{}},{"child":"hello world",` + 1046 | `"objectTwo":{}},{"child":"hello world","objectThree":{}}]}` 1047 | received = json2.String() 1048 | if expected != received { 1049 | t.Errorf("json2: expected %v, received %v", expected, received) 1050 | } 1051 | 1052 | received = json3.String() 1053 | if expected != received { 1054 | t.Errorf("json3: expected %v, received %v", expected, received) 1055 | } 1056 | } 1057 | 1058 | func TestChildrenMap(t *testing.T) { 1059 | json1, _ := ParseJSON([]byte(`{ 1060 | "objectOne":{"num":1}, 1061 | "objectTwo":{"num":2}, 1062 | "objectThree":{"num":3} 1063 | }`)) 1064 | 1065 | objectMap := json1.ChildrenMap() 1066 | if len(objectMap) != 3 { 1067 | t.Errorf("Wrong num of elements in objectMap: %v != %v", len(objectMap), 3) 1068 | return 1069 | } 1070 | 1071 | for key, val := range objectMap { 1072 | switch key { 1073 | case "objectOne": 1074 | if val := val.S("num").Data().(float64); val != 1 { 1075 | t.Errorf("%v != %v", val, 1) 1076 | } 1077 | case "objectTwo": 1078 | if val := val.S("num").Data().(float64); val != 2 { 1079 | t.Errorf("%v != %v", val, 2) 1080 | } 1081 | case "objectThree": 1082 | if val := val.S("num").Data().(float64); val != 3 { 1083 | t.Errorf("%v != %v", val, 3) 1084 | } 1085 | default: 1086 | t.Errorf("Unexpected key: %v", key) 1087 | } 1088 | } 1089 | 1090 | objectMap["objectOne"].Set(500, "num") 1091 | if val := json1.Path("objectOne.num").Data().(int); val != 500 { 1092 | t.Errorf("set objectOne failed: %v != %v", val, 500) 1093 | } 1094 | } 1095 | 1096 | func TestNestedAnonymousArrays(t *testing.T) { 1097 | json1, _ := ParseJSON([]byte(`{ 1098 | "array":[ 1099 | [ 1, 2, 3 ], 1100 | [ 4, 5, 6 ], 1101 | [ 7, 8, 9 ], 1102 | [{ "test" : 50 }] 1103 | ] 1104 | }`)) 1105 | 1106 | childTest := json1.S("array").Index(0).Children() 1107 | 1108 | if val := childTest[0].Data().(float64); val != 1 { 1109 | t.Errorf("child test: %v != %v", val, 1) 1110 | } 1111 | if val := childTest[1].Data().(float64); val != 2 { 1112 | t.Errorf("child test: %v != %v", val, 2) 1113 | } 1114 | if val := childTest[2].Data().(float64); val != 3 { 1115 | t.Errorf("child test: %v != %v", val, 3) 1116 | } 1117 | 1118 | if val := json1.Path("array").Index(1).Index(1).Data().(float64); val != 5 { 1119 | t.Errorf("nested child test: %v != %v", val, 5) 1120 | } 1121 | 1122 | if val := json1.Path("array").Index(3).Index(0).S("test").Data().(float64); val != 50 { 1123 | t.Errorf("nested child object test: %v != %v", val, 50) 1124 | } 1125 | 1126 | json1.Path("array").Index(3).Index(0).Set(200, "test") 1127 | 1128 | if val := json1.Path("array").Index(3).Index(0).S("test").Data().(int); val != 200 { 1129 | t.Errorf("set nested child object: %v != %v", val, 200) 1130 | } 1131 | } 1132 | 1133 | func TestArrays(t *testing.T) { 1134 | json1, _ := ParseJSON([]byte(`{ 1135 | "languages":{ 1136 | "english":{ 1137 | "places":0 1138 | }, 1139 | "french": { 1140 | "places": [ 1141 | "france", 1142 | "belgium" 1143 | ] 1144 | } 1145 | } 1146 | }`)) 1147 | 1148 | json2, _ := ParseJSON([]byte(`{ 1149 | "places":[ 1150 | "great_britain", 1151 | "united_states_of_america", 1152 | "the_world" 1153 | ] 1154 | }`)) 1155 | 1156 | if englishPlaces := json2.Search("places").Data(); englishPlaces != nil { 1157 | json1.Path("languages.english").Set(englishPlaces, "places") 1158 | } else { 1159 | t.Errorf("Didn't find places in json2") 1160 | } 1161 | 1162 | if englishPlaces := json1.Search("languages", "english", "places").Data(); englishPlaces != nil { 1163 | 1164 | englishArray, ok := englishPlaces.([]interface{}) 1165 | if !ok { 1166 | t.Errorf("places in json1 (%v) was not an array", englishPlaces) 1167 | } 1168 | 1169 | if len(englishArray) != 3 { 1170 | t.Errorf("wrong length of array: %v", len(englishArray)) 1171 | } 1172 | 1173 | } else { 1174 | t.Errorf("Didn't find places in json1") 1175 | } 1176 | 1177 | for i := 0; i < 3; i++ { 1178 | if err := json2.ArrayRemove(0, "places"); err != nil { 1179 | t.Errorf("Error removing element: %v", err) 1180 | } 1181 | } 1182 | 1183 | json2.ArrayAppend(map[string]interface{}{}, "places") 1184 | json2.ArrayAppend(map[string]interface{}{}, "places") 1185 | json2.ArrayAppend(map[string]interface{}{}, "places") 1186 | 1187 | // Using float64 for this test even though it's completely inappropriate because 1188 | // later on the API might do something clever with types, in which case all numbers 1189 | // will become float64. 1190 | for i := 0; i < 3; i++ { 1191 | obj, _ := json2.ArrayElement(i, "places") 1192 | obj2, _ := obj.Object(fmt.Sprintf("object%v", i)) 1193 | obj2.Set(float64(i), "index") 1194 | } 1195 | 1196 | children := json2.S("places").Children() 1197 | for i, obj := range children { 1198 | if id, ok := obj.S(fmt.Sprintf("object%v", i)).S("index").Data().(float64); ok { 1199 | if id != float64(i) { 1200 | t.Errorf("Wrong index somehow, expected %v, received %v", i, id) 1201 | } 1202 | } else { 1203 | t.Errorf("Failed to find element %v from %v", i, obj) 1204 | } 1205 | } 1206 | 1207 | if err := json2.ArrayRemove(1, "places"); err != nil { 1208 | t.Errorf("Error removing element: %v", err) 1209 | } 1210 | 1211 | expected := `{"places":[{"object0":{"index":0}},{"object2":{"index":2}}]}` 1212 | received := json2.String() 1213 | 1214 | if expected != received { 1215 | t.Errorf("Wrong output, expected: %v, received: %v", expected, received) 1216 | } 1217 | } 1218 | 1219 | func TestArraysTwo(t *testing.T) { 1220 | json1 := New() 1221 | 1222 | test1, err := json1.ArrayOfSize(4, "test1") 1223 | if err != nil { 1224 | t.Error(err) 1225 | } 1226 | 1227 | if _, err = test1.ArrayOfSizeI(2, 0); err != nil { 1228 | t.Error(err) 1229 | } 1230 | if _, err = test1.ArrayOfSizeI(2, 1); err != nil { 1231 | t.Error(err) 1232 | } 1233 | if _, err = test1.ArrayOfSizeI(2, 2); err != nil { 1234 | t.Error(err) 1235 | } 1236 | if _, err = test1.ArrayOfSizeI(2, 3); err != nil { 1237 | t.Error(err) 1238 | } 1239 | 1240 | if _, err = test1.ArrayOfSizeI(2, 4); err != ErrOutOfBounds { 1241 | t.Errorf("Index should have been out of bounds") 1242 | } 1243 | 1244 | if _, err = json1.S("test1").Index(0).SetIndex(10, 0); err != nil { 1245 | t.Error(err) 1246 | } 1247 | if _, err = json1.S("test1").Index(0).SetIndex(11, 1); err != nil { 1248 | t.Error(err) 1249 | } 1250 | 1251 | if _, err = json1.S("test1").Index(1).SetIndex(12, 0); err != nil { 1252 | t.Error(err) 1253 | } 1254 | if _, err = json1.S("test1").Index(1).SetIndex(13, 1); err != nil { 1255 | t.Error(err) 1256 | } 1257 | 1258 | if _, err = json1.S("test1").Index(2).SetIndex(14, 0); err != nil { 1259 | t.Error(err) 1260 | } 1261 | if _, err = json1.S("test1").Index(2).SetIndex(15, 1); err != nil { 1262 | t.Error(err) 1263 | } 1264 | 1265 | if _, err = json1.S("test1").Index(3).SetIndex(16, 0); err != nil { 1266 | t.Error(err) 1267 | } 1268 | if _, err = json1.S("test1").Index(3).SetIndex(17, 1); err != nil { 1269 | t.Error(err) 1270 | } 1271 | 1272 | if val := json1.S("test1").Index(0).Index(0).Data().(int); val != 10 { 1273 | t.Errorf("create array: %v != %v", val, 10) 1274 | } 1275 | if val := json1.S("test1").Index(0).Index(1).Data().(int); val != 11 { 1276 | t.Errorf("create array: %v != %v", val, 11) 1277 | } 1278 | 1279 | if val := json1.S("test1").Index(1).Index(0).Data().(int); val != 12 { 1280 | t.Errorf("create array: %v != %v", val, 12) 1281 | } 1282 | if val := json1.S("test1").Index(1).Index(1).Data().(int); val != 13 { 1283 | t.Errorf("create array: %v != %v", val, 13) 1284 | } 1285 | 1286 | if val := json1.S("test1").Index(2).Index(0).Data().(int); val != 14 { 1287 | t.Errorf("create array: %v != %v", val, 14) 1288 | } 1289 | if val := json1.S("test1").Index(2).Index(1).Data().(int); val != 15 { 1290 | t.Errorf("create array: %v != %v", val, 15) 1291 | } 1292 | 1293 | if val := json1.S("test1").Index(3).Index(0).Data().(int); val != 16 { 1294 | t.Errorf("create array: %v != %v", val, 16) 1295 | } 1296 | if val := json1.S("test1").Index(3).Index(1).Data().(int); val != 17 { 1297 | t.Errorf("create array: %v != %v", val, 17) 1298 | } 1299 | } 1300 | 1301 | func TestArraysThree(t *testing.T) { 1302 | json1 := New() 1303 | 1304 | test, err := json1.ArrayOfSizeP(1, "test1.test2") 1305 | if err != nil { 1306 | t.Fatal(err) 1307 | } 1308 | 1309 | test.SetIndex(10, 0) 1310 | if val := json1.S("test1", "test2").Index(0).Data().(int); val != 10 { 1311 | t.Error(err) 1312 | } 1313 | } 1314 | 1315 | func TestSetJSONPointer(t *testing.T) { 1316 | type testCase struct { 1317 | input string 1318 | pointer string 1319 | value interface{} 1320 | output string 1321 | } 1322 | tests := []testCase{ 1323 | { 1324 | input: `{"foo":{"bar":"baz"}}`, 1325 | pointer: "/foo/bar", 1326 | value: "qux", 1327 | output: `{"foo":{"bar":"qux"}}`, 1328 | }, 1329 | { 1330 | input: `{"foo":["bar","ignored","baz"]}`, 1331 | pointer: "/foo/2", 1332 | value: "qux", 1333 | output: `{"foo":["bar","ignored","qux"]}`, 1334 | }, 1335 | { 1336 | input: `{"foo":["bar","ignored",{"bar":"baz"}]}`, 1337 | pointer: "/foo/2/bar", 1338 | value: "qux", 1339 | output: `{"foo":["bar","ignored",{"bar":"qux"}]}`, 1340 | }, 1341 | } 1342 | 1343 | for _, test := range tests { 1344 | gObj, err := ParseJSON([]byte(test.input)) 1345 | if err != nil { 1346 | t.Errorf("Failed to parse '%v': %v", test.input, err) 1347 | continue 1348 | } 1349 | if _, err = gObj.SetJSONPointer(test.value, test.pointer); err != nil { 1350 | t.Error(err) 1351 | continue 1352 | } 1353 | if exp, act := test.output, gObj.String(); exp != act { 1354 | t.Errorf("Wrong result: %v != %v", act, exp) 1355 | } 1356 | } 1357 | } 1358 | 1359 | func TestArrayReplace(t *testing.T) { 1360 | json1 := New() 1361 | 1362 | json1.Set(1, "first") 1363 | json1.ArrayAppend(2, "first") 1364 | json1.ArrayAppend(3, "second") 1365 | 1366 | expected := `{"first":[1,2],"second":[3]}` 1367 | received := json1.String() 1368 | 1369 | if expected != received { 1370 | t.Errorf("Wrong output, expected: %v, received: %v", expected, received) 1371 | } 1372 | } 1373 | 1374 | func TestArraysRoot(t *testing.T) { 1375 | sample := []byte(`["test1"]`) 1376 | 1377 | val, err := ParseJSON(sample) 1378 | if err != nil { 1379 | t.Errorf("Failed to parse: %v", err) 1380 | return 1381 | } 1382 | 1383 | val.ArrayAppend("test2") 1384 | val.ArrayAppend("test3") 1385 | if obj, err := val.ObjectI(2); err != nil { 1386 | t.Error(err) 1387 | } else { 1388 | obj.Set("bar", "foo") 1389 | } 1390 | 1391 | if expected, actual := `["test1","test2",{"foo":"bar"}]`, val.String(); expected != actual { 1392 | t.Errorf("expected %v, received: %v", expected, actual) 1393 | } 1394 | } 1395 | 1396 | func TestLargeSample(t *testing.T) { 1397 | sample := []byte(`{ 1398 | "test":{ 1399 | "innerTest":{ 1400 | "value":10, 1401 | "value2":22, 1402 | "value3":{ 1403 | "moreValue":45 1404 | } 1405 | } 1406 | }, 1407 | "test2":20 1408 | }`) 1409 | 1410 | val, err := ParseJSON(sample) 1411 | if err != nil { 1412 | t.Errorf("Failed to parse: %v", err) 1413 | return 1414 | } 1415 | 1416 | if result, ok := val.Search("test", "innerTest", "value3", "moreValue").Data().(float64); ok { 1417 | if result != 45 { 1418 | t.Errorf("Wrong value of result: %v", result) 1419 | } 1420 | } else { 1421 | t.Errorf("Didn't find value") 1422 | } 1423 | } 1424 | 1425 | func TestShorthand(t *testing.T) { 1426 | container, _ := ParseJSON([]byte(`{ 1427 | "outter":{ 1428 | "inner":{ 1429 | "value":5, 1430 | "value2":10, 1431 | "value3":11 1432 | }, 1433 | "inner2":{ 1434 | } 1435 | }, 1436 | "outter2":{ 1437 | "inner":0 1438 | } 1439 | }`)) 1440 | 1441 | missingValue := container.S("outter").S("doesntexist").S("alsodoesntexist").S("inner").S("value").Data() 1442 | if missingValue != nil { 1443 | t.Errorf("missing value was actually found: %v\n", missingValue) 1444 | } 1445 | 1446 | realValue := container.S("outter").S("inner").S("value2").Data().(float64) 1447 | if realValue != 10 { 1448 | t.Errorf("real value was incorrect: %v\n", realValue) 1449 | } 1450 | 1451 | _, err := container.S("outter2").Set(container.S("outter").S("inner").Data(), "inner") 1452 | if err != nil { 1453 | t.Errorf("error setting outter2: %v\n", err) 1454 | } 1455 | 1456 | compare := `{"outter":{"inner":{"value":5,"value2":10,"value3":11},"inner2":{}}` + 1457 | `,"outter2":{"inner":{"value":5,"value2":10,"value3":11}}}` 1458 | out := container.String() 1459 | if out != compare { 1460 | t.Errorf("wrong serialized structure: %v\n", out) 1461 | } 1462 | 1463 | compare2 := `{"outter":{"inner":{"value":6,"value2":10,"value3":11},"inner2":{}}` + 1464 | `,"outter2":{"inner":{"value":6,"value2":10,"value3":11}}}` 1465 | 1466 | container.S("outter").S("inner").Set(6, "value") 1467 | out = container.String() 1468 | if out != compare2 { 1469 | t.Errorf("wrong serialized structure: %v\n", out) 1470 | } 1471 | } 1472 | 1473 | func TestInvalid(t *testing.T) { 1474 | invalidJSONSamples := []string{ 1475 | `{dfads"`, 1476 | ``, 1477 | // `""`, 1478 | // `"hello"`, 1479 | "{}\n{}", 1480 | } 1481 | 1482 | for _, sample := range invalidJSONSamples { 1483 | if _, err := ParseJSON([]byte(sample)); err == nil { 1484 | t.Errorf("parsing invalid JSON '%v' did not return error", sample) 1485 | } 1486 | } 1487 | 1488 | if _, err := ParseJSON(nil); err == nil { 1489 | t.Errorf("parsing nil did not return error") 1490 | } 1491 | 1492 | validObj, err := ParseJSON([]byte(`{}`)) 1493 | if err != nil { 1494 | t.Errorf("failed to parse '{}'") 1495 | } 1496 | 1497 | invalidStr := validObj.S("Doesn't exist").String() 1498 | if invalidStr != "null" { 1499 | t.Errorf("expected 'null', received: %v", invalidStr) 1500 | } 1501 | } 1502 | 1503 | func TestCreation(t *testing.T) { 1504 | container, _ := ParseJSON([]byte(`{}`)) 1505 | inner, err := container.ObjectP("test.inner") 1506 | if err != nil { 1507 | t.Errorf("Error: %v", err) 1508 | return 1509 | } 1510 | 1511 | inner.Set(10, "first") 1512 | inner.Set(20, "second") 1513 | 1514 | inner.Array("array") 1515 | inner.ArrayAppend("first element of the array", "array") 1516 | inner.ArrayAppend(2, "array") 1517 | inner.ArrayAppend("three", "array") 1518 | 1519 | expected := `{"test":{"inner":{"array":["first element of the array",2,"three"],` + 1520 | `"first":10,"second":20}}}` 1521 | actual := container.String() 1522 | if actual != expected { 1523 | t.Errorf("received incorrect output from json object: %v\n", actual) 1524 | } 1525 | } 1526 | 1527 | type outterJSON struct { 1528 | FirstInner innerJSON 1529 | SecondInner innerJSON 1530 | ThirdInner innerJSON 1531 | } 1532 | 1533 | type innerJSON struct { 1534 | NumberType float64 1535 | StringType string 1536 | } 1537 | 1538 | type jsonStructure struct { 1539 | FirstOutter outterJSON 1540 | SecondOutter outterJSON 1541 | } 1542 | 1543 | var jsonContent = []byte(`{ 1544 | "firstOutter":{ 1545 | "firstInner":{ 1546 | "numberType":11, 1547 | "stringType":"hello world, first first" 1548 | }, 1549 | "secondInner":{ 1550 | "numberType":12, 1551 | "stringType":"hello world, first second" 1552 | }, 1553 | "thirdInner":{ 1554 | "numberType":13, 1555 | "stringType":"hello world, first third" 1556 | } 1557 | }, 1558 | "secondOutter":{ 1559 | "firstInner":{ 1560 | "numberType":21, 1561 | "stringType":"hello world, second first" 1562 | }, 1563 | "secondInner":{ 1564 | "numberType":22, 1565 | "stringType":"hello world, second second" 1566 | }, 1567 | "thirdInner":{ 1568 | "numberType":23, 1569 | "stringType":"hello world, second third" 1570 | } 1571 | } 1572 | }`) 1573 | 1574 | /* 1575 | Simple use case, compares unmarshalling declared structs vs dynamically searching for 1576 | the equivalent hierarchy. Hopefully we won't see too great a performance drop from the 1577 | dynamic approach. 1578 | */ 1579 | 1580 | func BenchmarkStatic(b *testing.B) { 1581 | b.ReportAllocs() 1582 | for i := 0; i < b.N; i++ { 1583 | var jsonObj jsonStructure 1584 | if err := json.Unmarshal(jsonContent, &jsonObj); err != nil { 1585 | b.Errorf("Error: %v", err) 1586 | return 1587 | } 1588 | 1589 | if val := jsonObj.FirstOutter.SecondInner.NumberType; val != 12 { 1590 | b.Errorf("Wrong value of FirstOutter.SecondInner.NumberType: %v\n", val) 1591 | } 1592 | expected := "hello world, first second" 1593 | if val := jsonObj.FirstOutter.SecondInner.StringType; val != expected { 1594 | b.Errorf("Wrong value of FirstOutter.SecondInner.StringType: %v\n", val) 1595 | } 1596 | if val := jsonObj.SecondOutter.ThirdInner.NumberType; val != 23 { 1597 | b.Errorf("Wrong value of SecondOutter.ThirdInner.NumberType: %v\n", val) 1598 | } 1599 | expected = "hello world, second second" 1600 | if val := jsonObj.SecondOutter.SecondInner.StringType; val != expected { 1601 | b.Errorf("Wrong value of SecondOutter.SecondInner.StringType: %v\n", val) 1602 | } 1603 | } 1604 | } 1605 | 1606 | func BenchmarkDynamic(b *testing.B) { 1607 | b.ReportAllocs() 1608 | for i := 0; i < b.N; i++ { 1609 | jsonObj, err := ParseJSON(jsonContent) 1610 | if err != nil { 1611 | b.Errorf("Error parsing json: %v\n", err) 1612 | } 1613 | 1614 | FOSI := jsonObj.S("firstOutter", "secondInner") 1615 | SOSI := jsonObj.S("secondOutter", "secondInner") 1616 | SOTI := jsonObj.S("secondOutter", "thirdInner") 1617 | 1618 | if val := FOSI.S("numberType").Data().(float64); val != 12 { 1619 | b.Errorf("Wrong value of FirstOutter.SecondInner.NumberType: %v\n", val) 1620 | } 1621 | expected := "hello world, first second" 1622 | if val := FOSI.S("stringType").Data().(string); val != expected { 1623 | b.Errorf("Wrong value of FirstOutter.SecondInner.StringType: %v\n", val) 1624 | } 1625 | if val := SOTI.S("numberType").Data().(float64); val != 23 { 1626 | b.Errorf("Wrong value of SecondOutter.ThirdInner.NumberType: %v\n", val) 1627 | } 1628 | expected = "hello world, second second" 1629 | if val := SOSI.S("stringType").Data().(string); val != expected { 1630 | b.Errorf("Wrong value of SecondOutter.SecondInner.StringType: %v\n", val) 1631 | } 1632 | } 1633 | } 1634 | 1635 | func TestBadIndexes(t *testing.T) { 1636 | jsonObj, err := ParseJSON([]byte(`{"array":[1,2,3]}`)) 1637 | if err != nil { 1638 | t.Error(err) 1639 | } 1640 | if act := jsonObj.Index(0).Data(); act != nil { 1641 | t.Errorf("Unexpected value returned: %v != %v", nil, act) 1642 | } 1643 | if act := jsonObj.S("array").Index(4).Data(); act != nil { 1644 | t.Errorf("Unexpected value returned: %v != %v", nil, act) 1645 | } 1646 | } 1647 | 1648 | func TestNilSet(t *testing.T) { 1649 | obj := Container{nil} 1650 | if _, err := obj.Set("bar", "foo"); err != nil { 1651 | t.Error(err) 1652 | } 1653 | if _, err := obj.Set("new", "foo", "bar"); err != ErrPathCollision { 1654 | t.Errorf("Expected ErrPathCollision: %v, %s", err, obj.Data()) 1655 | } 1656 | if _, err := obj.SetIndex("new", 0); err != ErrNotArray { 1657 | t.Errorf("Expected ErrNotArray: %v, %s", err, obj.Data()) 1658 | } 1659 | } 1660 | 1661 | func TestLargeSampleWithHtmlEscape(t *testing.T) { 1662 | sample := []byte(`{ 1663 | "test": { 1664 | "innerTest": { 1665 | "value": 10, 1666 | "value2": "