├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── constants.go ├── errors.go ├── go.mod ├── go.sum ├── jsonic.go ├── jsonic_test.go └── test_data ├── test1.json ├── test2.json └── test3.json /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | test: 11 | name: Test 12 | strategy: 13 | matrix: 14 | go-version: [1.12.x, 1.13.x, 1.14.x, 1.15.x] 15 | platform: [ubuntu-latest, macos-latest] 16 | runs-on: ${{ matrix.platform }} 17 | steps: 18 | - name: Install Go 19 | uses: actions/setup-go@v2 20 | with: 21 | go-version: ${{ matrix.go-version }} 22 | id: go 23 | - name: Check out code into the Go module directory 24 | uses: actions/checkout@v2 25 | - name: Get dependencies 26 | run: | 27 | go get -v -t -d ./... 28 | if [ -f Gopkg.toml ]; then 29 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 30 | dep ensure 31 | fi 32 | - name: Run tests 33 | run: go test -v -coverprofile=profile.cov ./... 34 | - name: Send coverage 35 | uses: shogo82148/actions-goveralls@v1 36 | with: 37 | path-to-profile: profile.cov 38 | flag-name: ${{ matrix.platform }}-go-${{ matrix.go-version }} 39 | parallel: true 40 | 41 | finish: 42 | name: Finish 43 | runs-on: ubuntu-latest 44 | needs: [test] 45 | steps: 46 | - name: Sending coverage finished 47 | uses: shogo82148/actions-goveralls@v1 48 | with: 49 | parallel-finished: true 50 | 51 | build: 52 | name: Build 53 | runs-on: ubuntu-latest 54 | needs: [test] 55 | steps: 56 | - name: Set up Go 1.x 57 | uses: actions/setup-go@v2 58 | with: 59 | go-version: ^1.14 60 | id: go 61 | - name: Check out code into the Go module directory 62 | uses: actions/checkout@v2 63 | - name: Get dependencies 64 | run: | 65 | go get -v -t -d ./... 66 | if [ -f Gopkg.toml ]; then 67 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 68 | dep ensure 69 | fi 70 | - name: Build 71 | run: go build -v . 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Shubham Sinha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSONIC 2 | 3 | [![GoDoc](https://godoc.org/github.com/sinhashubham95/jsonic?status.svg)](https://pkg.go.dev/github.com/sinhashubham95/jsonic) 4 | [![Release](https://img.shields.io/github/v/release/sinhashubham95/jsonic?sort=semver)](https://github.com/sinhashubham95/jsonic/releases) 5 | [![Report](https://goreportcard.com/badge/github.com/sinhashubham95/jsonic)](https://goreportcard.com/report/github.com/sinhashubham95/jsonic) 6 | [![Coverage Status](https://coveralls.io/repos/github/sinhashubham95/jsonic/badge.svg?branch=master)](https://coveralls.io/github/sinhashubham95/jsonic?branch=master) 7 | [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go#json) 8 | 9 | `Jsonic` is the complete set of utilities to handle json data. There's no need to define structs anymore. It's completely safe to perform nested queries in the JSON. The strong typed methods part of this library will help you have the desired result without writing any extra code. 10 | 11 | ## Installation 12 | 13 | ```shell 14 | go get github.com/sinhashubham95/jsonic 15 | ``` 16 | 17 | ## Understanding the query path 18 | 19 | `Jsonic` uses a unique and simple way to query the elements in a json. It's easy but unique, so you need to understand the same for using `Jsonic`. 20 | 21 | Consider the following json. 22 | 23 | ```json 24 | { 25 | "a": { 26 | "x": "p", 27 | "arr": [ 28 | { 29 | "a": "b", 30 | "c.d": { 31 | "e": "f" 32 | } 33 | } 34 | ] 35 | }, 36 | "c": "d", 37 | "a.x": { 38 | "y": "q" 39 | }, 40 | "a.x.y": { 41 | "z": "r" 42 | } 43 | } 44 | ``` 45 | 46 | Though, practically such a JSON won't exist, but still `Jsonic` is intelligent enough to handle even this. Go through the below table carefully and it will help you understand the path schema. 47 | 48 | | Path | Result | Comments | 49 | | :------------: | :----------------------------------------------------: | ------------------------------------------------------------------------- | 50 | | {EMPTY_STRING} | entire json | empty string returns the entire json if no empty string exists in the key | 51 | | . | entire json | dot returns the entire json if no dot exists in the key | 52 | | a | {"x": "p", "arr": [{ "a": "b", "c.d": { "e": "f" } }]} | it returns the entire json tree of a | 53 | | a.x | p | multiple options here, but the first preference goes to the tree of a | 54 | | a.x.y | q | multiple options here, the first preference will be given to a.x | 55 | | a.x.y.z | r | there is only a single possibility here | 56 | | a.arr | [{ "a": "b", "c.d": { "e": "f" } }] | it returns the entire array denoting the json tree of a.arr | 57 | | a.arr[0] | { "a": "b", "c.d": { "e": "f" } } | it returns the first element of the array | 58 | | a.arr[0].a | b | it returns the element for key a of the first element of array | 59 | | a.arr[0].c.d.e | f | | 60 | 61 | As you would have understood, if there are multiple JSON trees satisfying the path, and the path looks something like this `a.b.c.d`, then the preferences will be in the following order - `a` > `a.b` > `a.b.c` > `a.b.c.d`. 62 | 63 | Consider another json. 64 | 65 | ```json 66 | { 67 | "": "a", 68 | ".": "b" 69 | } 70 | ``` 71 | Here the paths resolve in a different manner. 72 | 73 | | Path | Result | Comments | 74 | | :------------: | :----: | ------------------------------------------------------------------------- | 75 | | {EMPTY_STRING} | a | empty string returns the entire json if no empty string exists in the key | 76 | | . | b | empty string returns the entire json if no empty string exists in the key | 77 | 78 | ## How to Use? 79 | 80 | `Jsonic` allows you to process the JSON bytes. You can create a new instance of `Jsonic` for every JSON you have and you can get the child JSON trees using the set of utilities it provides. 81 | 82 | ### Create a New Instance 83 | 84 | This will create a new instance using the JSON bytes provided as it's data to be used on. 85 | 86 | ```go 87 | import ( 88 | "github.com/sinhashubham95/jsonic" 89 | ) 90 | 91 | func New() { 92 | json := "{\"naruto\": \"rocks\"}" 93 | j, err := jsonic.New([]byte(json)) 94 | // perform any sort of operations on the json using the instance created 95 | } 96 | ``` 97 | 98 | ### Create a child instance 99 | 100 | On the `Jsonic` created, you can provide a child path and get a new instance with the child JSON tree satisfying the path provided as it's data. 101 | 102 | ```go 103 | import ( 104 | "github.com/sinhashubham95/jsonic" 105 | ) 106 | 107 | func Child() { 108 | json := "{\"naruto\": \"rocks\"}" 109 | j, err := jsonic.New([]byte(json)) 110 | if err != nil { 111 | return 112 | } 113 | 114 | // create a child 115 | child, err := jsonic.Child("naruto") 116 | // now if you want to query on the child then use this child instance 117 | } 118 | ``` 119 | 120 | ### Get the data at the path 121 | 122 | On the `Jsonic` created, you can get the data at the path specified. 123 | 124 | ```go 125 | import ( 126 | "github.com/sinhashubham95/jsonic" 127 | ) 128 | 129 | func Get() { 130 | json := "{\"naruto\": \"rocks\"}" 131 | j, err := jsonic.New([]byte(json)) 132 | if err != nil { 133 | return 134 | } 135 | 136 | // get the data 137 | data, err := jsonic.Get("naruto") 138 | // this data will have type interface{} with value "rocks" 139 | } 140 | ``` 141 | 142 | ### Get typed data at the path 143 | 144 | Though using structs is not required with the wonderful set of utilities `Jsonic` provides, but even if you like to use that, it is very simple to get your result cast into the struct you want. 145 | 146 | ```go 147 | import ( 148 | "github.com/sinhashubham95/jsonic" 149 | ) 150 | 151 | type Detail struct { 152 | Name string `json:"name"` 153 | } 154 | 155 | func GetTyped() { 156 | json := "{\"characters\": [{\"name\": \"naruto\"}, {\"name\": \"boruto\"}]}" 157 | j, err := jsonic.New([]byte(json)) 158 | if err != nil { 159 | return 160 | } 161 | 162 | // get the data 163 | var data []Detail 164 | err := jsonic.GetTyped("characters", &data) 165 | // this data will contain 2 elements with names as naruto and boruto 166 | } 167 | ``` 168 | 169 | ### Other Typed Utilities 170 | 171 | Apart from the generic query methods mentioned above, `Jsonic` contains a bunch of others. 172 | 173 | ```go 174 | import ( 175 | "github.com/sinhashubham95/jsonic" 176 | ) 177 | 178 | func OtherGetters(j *Jsonic, path string) { 179 | // primitives 180 | i, err := j.GetInt(path) // int 181 | i64, err := j.GetInt64(path) // int64 182 | f, err := j.GetFloat(path) // float32 183 | f64, err := j.GetFloat64(path) // float64 184 | b, err := j.GetBool(path) // bool 185 | s, err := j.GetString(path) // string 186 | 187 | // arrays 188 | a, err := j.GetArray(path) // []interface{} 189 | iArr, err := j.GetIntArray(path) // []int 190 | i64Arr, err := j.GetInt64Array(path) // []int64 191 | fArr, err := j.GetFloatArray(path) // []float32 192 | f64Arr, err := j.GetFloat64Array(path) // []float64 193 | bArr, err := j.GetBoolArray(path) // []bool 194 | sArr, err := j.GetStringArray(path) // []string 195 | 196 | // maps 197 | m, err := j.GetMap(path) // map[string]interface{} 198 | iMap, err := j.GetIntMap(path) // map[string]int 199 | i64Map, err := j.GetInt64Map(path) // map[string]int64 200 | fMap, err := j.GetFloatMap(path) // map[string]float32 201 | f64Map, err := j.GetFloat64Map(path) // map[string]float64 202 | bMap, err := j.GetBoolMap(path) // map[string]bool 203 | sMap, err := j.GetStringMap(path) // map[string]string 204 | } 205 | ``` 206 | -------------------------------------------------------------------------------- /constants.go: -------------------------------------------------------------------------------- 1 | package jsonic 2 | 3 | const ( 4 | dot = "." 5 | empty = "" 6 | space = " " 7 | openBracket = "[" 8 | closeBracket = "]" 9 | ) 10 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package jsonic 2 | 3 | import "errors" 4 | 5 | // errors 6 | var ( 7 | ErrUnexpectedJSONData = errors.New("unexpected json data provided, neither array nor object") 8 | ErrIndexNotFound = errors.New("expected index for json array but found something else") 9 | ErrIndexOutOfBound = errors.New("index out of bounds of the json array") 10 | ErrNoDataFound = errors.New("no tree satisfies the path elements provided") 11 | ErrInvalidType = errors.New("data at the specified path does not match the expected type") 12 | ) 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sinhashubham95/jsonic 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 // indirect 7 | github.com/kr/text v0.2.0 // indirect 8 | github.com/stretchr/testify v1.6.1 9 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 10 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 7 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 8 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 9 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 10 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 11 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 12 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 16 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 17 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 18 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 19 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 20 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 21 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 22 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 23 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 24 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 25 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 26 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 27 | -------------------------------------------------------------------------------- /jsonic.go: -------------------------------------------------------------------------------- 1 | package jsonic 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | "strings" 7 | "sync" 8 | ) 9 | 10 | // Jsonic is the type to hold the JSON data 11 | type Jsonic struct { 12 | data interface{} 13 | mu *sync.RWMutex 14 | cache map[string]*Jsonic 15 | } 16 | 17 | type pathElement struct { 18 | nature int 19 | key string 20 | index int 21 | } 22 | 23 | // New is used to crete a new parser for the JSON data 24 | func New(data []byte) (*Jsonic, error) { 25 | var unmarshalled interface{} 26 | err := json.Unmarshal(data, &unmarshalled) 27 | if err != nil { 28 | // not a valid json 29 | return nil, err 30 | } 31 | return new(unmarshalled), nil 32 | } 33 | 34 | // Child returns the json tree at the path specified. 35 | // 36 | // It returns an error in case there is nothing that can be resolved 37 | // at the specified path. 38 | // 39 | // Path should be like this for example - a.[0].b, [0].a.b, etc. 40 | // The path elements should be separated with dots. 41 | // Now the path elements can either be the index in case of an array 42 | // with the index enclosed within square brackets or it can be 43 | // the key of the object. 44 | func (j *Jsonic) Child(path string) (*Jsonic, error) { 45 | if path == dot || path == empty { 46 | // this is a special case where we just need to check if the root 47 | // has a dot as key or empty as key 48 | return j.getDotOrEmptyChild(path), nil 49 | } 50 | return j.child(strings.Split(path, dot)) 51 | } 52 | 53 | // Get is used to get the data at the path specified. 54 | func (j *Jsonic) Get(path string) (interface{}, error) { 55 | child, err := j.Child(path) 56 | if err != nil { 57 | return nil, err 58 | } 59 | return child.data, nil 60 | } 61 | 62 | // GetTyped is used to get the data at the path specified in the value provided. 63 | // this value can be of any type, but preferably use a struct 64 | // using it with primitives will return an error 65 | // note that here a pointer should be used as value 66 | func (j *Jsonic) GetTyped(path string, val interface{}) error { 67 | child, err := j.Child(path) 68 | if err != nil { 69 | return err 70 | } 71 | return child.parseInto(val) 72 | } 73 | 74 | // GetInt is used to get the integer at the path specified. 75 | func (j *Jsonic) GetInt(path string) (int, error) { 76 | val, err := j.Get(path) 77 | if err != nil { 78 | return 0, err 79 | } 80 | if i, ok := val.(float64); ok { 81 | return int(i), nil 82 | } 83 | return 0, ErrInvalidType 84 | } 85 | 86 | // GetInt64 is used to get the integer at the path specified. 87 | func (j *Jsonic) GetInt64(path string) (int64, error) { 88 | val, err := j.Get(path) 89 | if err != nil { 90 | return 0, err 91 | } 92 | if i, ok := val.(float64); ok { 93 | return int64(i), nil 94 | } 95 | return 0, ErrInvalidType 96 | } 97 | 98 | // GetFloat is used to get the floating point number at the path specified. 99 | func (j *Jsonic) GetFloat(path string) (float32, error) { 100 | val, err := j.Get(path) 101 | if err != nil { 102 | return 0, err 103 | } 104 | if f, ok := val.(float64); ok { 105 | return float32(f), nil 106 | } 107 | return 0, ErrInvalidType 108 | } 109 | 110 | // GetFloat64 is used to get the floating point number at the path specified. 111 | func (j *Jsonic) GetFloat64(path string) (float64, error) { 112 | val, err := j.Get(path) 113 | if err != nil { 114 | return 0, err 115 | } 116 | if f, ok := val.(float64); ok { 117 | return f, nil 118 | } 119 | return 0, ErrInvalidType 120 | } 121 | 122 | // GetBool is used to get the integer at the path specified. 123 | func (j *Jsonic) GetBool(path string) (bool, error) { 124 | val, err := j.Get(path) 125 | if err != nil { 126 | return false, err 127 | } 128 | if b, ok := val.(bool); ok { 129 | return b, nil 130 | } 131 | return false, ErrInvalidType 132 | } 133 | 134 | // GetString is used to get the string at the path specified. 135 | func (j *Jsonic) GetString(path string) (string, error) { 136 | val, err := j.Get(path) 137 | if err != nil { 138 | return "", err 139 | } 140 | if s, ok := val.(string); ok { 141 | return s, nil 142 | } 143 | return "", ErrInvalidType 144 | } 145 | 146 | // GetArray is used to get the data array at the path specified. 147 | func (j *Jsonic) GetArray(path string) ([]interface{}, error) { 148 | val, err := j.Get(path) 149 | if err != nil { 150 | return nil, err 151 | } 152 | if a, ok := val.([]interface{}); ok { 153 | return a, nil 154 | } 155 | return nil, ErrInvalidType 156 | } 157 | 158 | // GetIntArray is used to get the integer array at the path specified. 159 | func (j *Jsonic) GetIntArray(path string) ([]int, error) { 160 | val, err := j.GetArray(path) 161 | if err != nil { 162 | return nil, err 163 | } 164 | iArr := make([]int, len(val)) 165 | for index, v := range val { 166 | if i, ok := v.(float64); ok { 167 | iArr[index] = int(i) 168 | } 169 | } 170 | return iArr, nil 171 | } 172 | 173 | // GetInt64Array is used to get the 64-bit integer array at the path specified. 174 | func (j *Jsonic) GetInt64Array(path string) ([]int64, error) { 175 | val, err := j.GetArray(path) 176 | if err != nil { 177 | return nil, err 178 | } 179 | iArr := make([]int64, len(val)) 180 | for index, v := range val { 181 | if i, ok := v.(float64); ok { 182 | iArr[index] = int64(i) 183 | } 184 | } 185 | return iArr, nil 186 | } 187 | 188 | // GetFloatArray is used to get the floating point number array at the path specified. 189 | func (j *Jsonic) GetFloatArray(path string) ([]float32, error) { 190 | val, err := j.GetArray(path) 191 | if err != nil { 192 | return nil, err 193 | } 194 | fArr := make([]float32, len(val)) 195 | for index, v := range val { 196 | if f, ok := v.(float64); ok { 197 | fArr[index] = float32(f) 198 | } 199 | } 200 | return fArr, nil 201 | } 202 | 203 | // GetFloat64Array is used to get the 64-bit floating point number array at the path specified. 204 | func (j *Jsonic) GetFloat64Array(path string) ([]float64, error) { 205 | val, err := j.GetArray(path) 206 | if err != nil { 207 | return nil, err 208 | } 209 | fArr := make([]float64, len(val)) 210 | for index, v := range val { 211 | if f, ok := v.(float64); ok { 212 | fArr[index] = f 213 | } 214 | } 215 | return fArr, nil 216 | } 217 | 218 | // GetBoolArray is used to get the boolean array at the path specified. 219 | func (j *Jsonic) GetBoolArray(path string) ([]bool, error) { 220 | val, err := j.GetArray(path) 221 | if err != nil { 222 | return nil, err 223 | } 224 | bArr := make([]bool, len(val)) 225 | for index, v := range val { 226 | if b, ok := v.(bool); ok { 227 | bArr[index] = b 228 | } 229 | } 230 | return bArr, nil 231 | } 232 | 233 | // GetStringArray is used to get the string array at the path specified. 234 | func (j *Jsonic) GetStringArray(path string) ([]string, error) { 235 | val, err := j.GetArray(path) 236 | if err != nil { 237 | return nil, err 238 | } 239 | sArr := make([]string, len(val)) 240 | for index, v := range val { 241 | if s, ok := v.(string); ok { 242 | sArr[index] = s 243 | } 244 | } 245 | return sArr, nil 246 | } 247 | 248 | // GetMap is used to get the data map at the path specified. 249 | func (j *Jsonic) GetMap(path string) (map[string]interface{}, error) { 250 | val, err := j.Get(path) 251 | if err != nil { 252 | return nil, err 253 | } 254 | if m, ok := val.(map[string]interface{}); ok { 255 | return m, nil 256 | } 257 | return nil, ErrInvalidType 258 | } 259 | 260 | // GetIntMap is used to get the integer map at the path specified. 261 | func (j *Jsonic) GetIntMap(path string) (map[string]int, error) { 262 | val, err := j.GetMap(path) 263 | if err != nil { 264 | return nil, err 265 | } 266 | iMap := make(map[string]int) 267 | for k, v := range val { 268 | if i, ok := v.(float64); ok { 269 | iMap[k] = int(i) 270 | } 271 | } 272 | return iMap, nil 273 | } 274 | 275 | // GetInt64Map is used to get the 64-bit integer map at the path specified. 276 | func (j *Jsonic) GetInt64Map(path string) (map[string]int64, error) { 277 | val, err := j.GetMap(path) 278 | if err != nil { 279 | return nil, err 280 | } 281 | iMap := make(map[string]int64) 282 | for k, v := range val { 283 | if i, ok := v.(float64); ok { 284 | iMap[k] = int64(i) 285 | } 286 | } 287 | return iMap, nil 288 | } 289 | 290 | // GetFloatMap is used to get the floating point number map at the path specified. 291 | func (j *Jsonic) GetFloatMap(path string) (map[string]float32, error) { 292 | val, err := j.GetMap(path) 293 | if err != nil { 294 | return nil, err 295 | } 296 | fMap := make(map[string]float32) 297 | for k, v := range val { 298 | if f, ok := v.(float64); ok { 299 | fMap[k] = float32(f) 300 | } 301 | } 302 | return fMap, nil 303 | } 304 | 305 | // GetFloat64Map is used to get the 64-bit floating point number map at the path specified. 306 | func (j *Jsonic) GetFloat64Map(path string) (map[string]float64, error) { 307 | val, err := j.GetMap(path) 308 | if err != nil { 309 | return nil, err 310 | } 311 | fMap := make(map[string]float64) 312 | for k, v := range val { 313 | if f, ok := v.(float64); ok { 314 | fMap[k] = f 315 | } 316 | } 317 | return fMap, nil 318 | } 319 | 320 | // GetBoolMap is used to get the boolean map at the path specified. 321 | func (j *Jsonic) GetBoolMap(path string) (map[string]bool, error) { 322 | val, err := j.GetMap(path) 323 | if err != nil { 324 | return nil, err 325 | } 326 | bMap := make(map[string]bool) 327 | for k, v := range val { 328 | if b, ok := v.(bool); ok { 329 | bMap[k] = b 330 | } 331 | } 332 | return bMap, nil 333 | } 334 | 335 | // GetStringMap is used to get the string map at the path specified. 336 | func (j *Jsonic) GetStringMap(path string) (map[string]string, error) { 337 | val, err := j.GetMap(path) 338 | if err != nil { 339 | return nil, err 340 | } 341 | sMap := make(map[string]string) 342 | for k, v := range val { 343 | if s, ok := v.(string); ok { 344 | sMap[k] = s 345 | } 346 | } 347 | return sMap, nil 348 | } 349 | 350 | func new(data interface{}) *Jsonic { 351 | return &Jsonic{ 352 | data: data, 353 | mu: &sync.RWMutex{}, 354 | cache: make(map[string]*Jsonic), 355 | } 356 | } 357 | 358 | func (j *Jsonic) getDotOrEmptyChild(path string) *Jsonic { 359 | if object, ok := j.data.(map[string]interface{}); ok { 360 | if cached := j.checkInCache(path); cached != nil { 361 | return cached 362 | } 363 | if data, ok := object[path]; ok { 364 | child := new(data) 365 | j.saveInCache(path, child) 366 | return child 367 | } 368 | } 369 | // in any other scenario we just return the root 370 | return j 371 | } 372 | 373 | func (j *Jsonic) childFromArray(array []interface{}, path []string) (*Jsonic, error) { 374 | // get the index, which should be there as the first path element 375 | index, err := getIndex(path[0]) 376 | if err != nil { 377 | // some problem with the index 378 | return nil, ErrIndexNotFound 379 | } 380 | if index < 0 || index >= len(array) { 381 | // index out of bound 382 | return nil, ErrIndexOutOfBound 383 | } 384 | // check in cache for the child 385 | if cached := j.checkInCache(strconv.Itoa(index)); cached != nil { 386 | return cached.child(path[1:]) 387 | } 388 | // create a child, and save it 389 | child := new(array[index]) 390 | j.saveInCache(strconv.Itoa(index), child) 391 | return child.child(path[1:]) 392 | } 393 | 394 | func (j *Jsonic) childFromObject(object map[string]interface{}, path []string) (*Jsonic, error) { 395 | current := "" 396 | // this loop is to handle the following scenario 397 | // say the path elements are as follows a, b and c 398 | // it might so happen that each of a, a.b and a.b.c are 399 | // present in the json data as keys, so we should give 400 | // each of them a fair chance. the only thing is we 401 | // are giving preference in the following order a > a.b > a.b.c 402 | for i, p := range path { 403 | current += p 404 | if cached := j.checkInCache(current); cached != nil { 405 | result, err := cached.child(path[i+1:]) 406 | if err == nil { 407 | // result found successfully 408 | return result, nil 409 | } 410 | } else if data, ok := object[current]; ok { 411 | child := new(data) 412 | j.saveInCache(current, child) 413 | result, err := child.child(path[i+1:]) 414 | if err == nil { 415 | // result found successfully 416 | return result, nil 417 | } 418 | } 419 | // nothing here, check further 420 | current += dot 421 | } 422 | return nil, ErrNoDataFound 423 | } 424 | 425 | func (j *Jsonic) child(path []string) (*Jsonic, error) { 426 | // first the base condition 427 | if len(path) == 0 { 428 | // we have reached the result 429 | return j, nil 430 | } 431 | // either data is array or object 432 | // we need to check that 433 | // and accordingly proceed 434 | if array, ok := j.data.([]interface{}); ok { 435 | return j.childFromArray(array, path) 436 | } 437 | if object, ok := j.data.(map[string]interface{}); ok { 438 | return j.childFromObject(object, path) 439 | } 440 | return nil, ErrUnexpectedJSONData 441 | } 442 | 443 | func (j *Jsonic) parseInto(val interface{}) error { 444 | b, err := json.Marshal(j.data) 445 | if err != nil { 446 | return err 447 | } 448 | return json.Unmarshal(b, val) 449 | } 450 | 451 | func getIndex(element string) (int, error) { 452 | // it should be enclosed within curly braces 453 | return strconv.Atoi(strings.TrimPrefix(strings.TrimSuffix(element, closeBracket), openBracket)) 454 | } 455 | 456 | func (j *Jsonic) checkInCache(path string) *Jsonic { 457 | j.mu.RLock() 458 | defer j.mu.RUnlock() 459 | return j.cache[path] 460 | } 461 | 462 | func (j *Jsonic) saveInCache(path string, child *Jsonic) { 463 | j.mu.Lock() 464 | defer j.mu.Unlock() 465 | j.cache[path] = child 466 | } 467 | -------------------------------------------------------------------------------- /jsonic_test.go: -------------------------------------------------------------------------------- 1 | package jsonic_test 2 | 3 | import ( 4 | "io/ioutil" 5 | "testing" 6 | 7 | "github.com/sinhashubham95/jsonic" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func readFromFile(path string, t *testing.T) []byte { 12 | data, err := ioutil.ReadFile(path) 13 | assert.NoError(t, err) 14 | return data 15 | } 16 | 17 | func TestNewSuccess(t *testing.T) { 18 | j, err := jsonic.New(readFromFile("test_data/test1.json", t)) 19 | assert.NotNil(t, j) 20 | assert.NoError(t, err) 21 | } 22 | 23 | func TestNewError(t *testing.T) { 24 | j, err := jsonic.New(nil) 25 | assert.Nil(t, j) 26 | assert.Error(t, err) 27 | assert.Equal(t, "unexpected end of JSON input", err.Error()) 28 | } 29 | 30 | func TestChild(t *testing.T) { 31 | j, err := jsonic.New(readFromFile("test_data/test1.json", t)) 32 | assert.NotNil(t, j) 33 | assert.NoError(t, err) 34 | 35 | // simple 36 | c1, err := j.Child("c") 37 | assert.NoError(t, err) 38 | assert.NotNil(t, c1) 39 | s, err := c1.GetString(".") 40 | assert.NoError(t, err) 41 | assert.Equal(t, "d", s) 42 | 43 | // same 44 | c2, err := j.Child(".") 45 | assert.NoError(t, err) 46 | assert.NotNil(t, c2) 47 | assert.Equal(t, j, c2) 48 | 49 | // not found 50 | c3, err := j.Child("a.m") 51 | assert.Error(t, err) 52 | assert.Nil(t, c3) 53 | assert.Equal(t, jsonic.ErrNoDataFound, err) 54 | 55 | // array 56 | c4, err := j.Child("a.arr") 57 | assert.NoError(t, err) 58 | assert.NotNil(t, c4) 59 | m, err := c4.GetMap("[0]") 60 | assert.NoError(t, err) 61 | assert.NotNil(t, m) 62 | assert.Equal(t, "b", m["a"]) 63 | 64 | // array nested query 65 | c5, err := j.Child("a.arr.[0].c.d") 66 | assert.NoError(t, err) 67 | assert.NotNil(t, c5) 68 | s, err = c5.GetString("e") 69 | assert.NoError(t, err) 70 | assert.Equal(t, "f", s) 71 | 72 | // array index not found 73 | c6, err := j.Child("a.arr.xyz") 74 | assert.Error(t, err) 75 | assert.Nil(t, c6) 76 | assert.Equal(t, jsonic.ErrNoDataFound, err) 77 | 78 | // array index out of bound 79 | c7, err := j.Child("a.arr.[1]") 80 | assert.Error(t, err) 81 | assert.Nil(t, c7) 82 | assert.Equal(t, jsonic.ErrNoDataFound, err) 83 | } 84 | 85 | func TestGet(t *testing.T) { 86 | j, err := jsonic.New(readFromFile("test_data/test1.json", t)) 87 | assert.NotNil(t, j) 88 | assert.NoError(t, err) 89 | 90 | // child exists 91 | v, err := j.Get("a.x") 92 | assert.NoError(t, err) 93 | assert.NotNil(t, v) 94 | assert.Equal(t, "p", v) 95 | 96 | // child not exists 97 | v, err = j.Get("a.y") 98 | assert.Error(t, err) 99 | assert.Nil(t, v) 100 | } 101 | 102 | func TestTyped(t *testing.T) { 103 | j, err := jsonic.New(readFromFile("test_data/test2.json", t)) 104 | assert.NotNil(t, j) 105 | assert.NoError(t, err) 106 | 107 | i, err := j.GetInt("z") 108 | assert.Equal(t, 0, i) 109 | assert.Error(t, err) 110 | assert.Equal(t, jsonic.ErrNoDataFound, err) 111 | 112 | i, err = j.GetInt("a") 113 | assert.Equal(t, 1, i) 114 | assert.NoError(t, err) 115 | 116 | i, err = j.GetInt("e") 117 | assert.Equal(t, 0, i) 118 | assert.Error(t, err) 119 | assert.Equal(t, jsonic.ErrInvalidType, err) 120 | 121 | i64, err := j.GetInt64("z") 122 | assert.Equal(t, int64(0), i64) 123 | assert.Error(t, err) 124 | assert.Equal(t, jsonic.ErrNoDataFound, err) 125 | 126 | i64, err = j.GetInt64("a") 127 | assert.Equal(t, int64(1), i64) 128 | assert.NoError(t, err) 129 | 130 | i64, err = j.GetInt64("e") 131 | assert.Equal(t, int64(0), i64) 132 | assert.Error(t, err) 133 | assert.Equal(t, jsonic.ErrInvalidType, err) 134 | 135 | f, err := j.GetFloat("z") 136 | assert.Equal(t, float32(0.0), f) 137 | assert.Error(t, err) 138 | assert.Equal(t, jsonic.ErrNoDataFound, err) 139 | 140 | f, err = j.GetFloat("b") 141 | assert.Equal(t, float32(2.2), f) 142 | assert.NoError(t, err) 143 | 144 | f, err = j.GetFloat("e") 145 | assert.Equal(t, float32(0.0), f) 146 | assert.Error(t, err) 147 | assert.Equal(t, jsonic.ErrInvalidType, err) 148 | 149 | f64, err := j.GetFloat64("z") 150 | assert.Equal(t, 0.0, f64) 151 | assert.Error(t, err) 152 | assert.Equal(t, jsonic.ErrNoDataFound, err) 153 | 154 | f64, err = j.GetFloat64("b") 155 | assert.Equal(t, 2.2, f64) 156 | assert.NoError(t, err) 157 | 158 | f64, err = j.GetFloat64("e") 159 | assert.Equal(t, 0.0, f64) 160 | assert.Error(t, err) 161 | assert.Equal(t, jsonic.ErrInvalidType, err) 162 | 163 | b, err := j.GetBool("z") 164 | assert.Equal(t, false, b) 165 | assert.Error(t, err) 166 | assert.Equal(t, jsonic.ErrNoDataFound, err) 167 | 168 | b, err = j.GetBool("c") 169 | assert.Equal(t, true, b) 170 | assert.NoError(t, err) 171 | 172 | b, err = j.GetBool("e") 173 | assert.Equal(t, false, b) 174 | assert.Error(t, err) 175 | assert.Equal(t, jsonic.ErrInvalidType, err) 176 | 177 | s, err := j.GetString("z") 178 | assert.Equal(t, "", s) 179 | assert.Error(t, err) 180 | assert.Equal(t, jsonic.ErrNoDataFound, err) 181 | 182 | s, err = j.GetString("d") 183 | assert.Equal(t, "naruto", s) 184 | assert.NoError(t, err) 185 | 186 | s, err = j.GetString("e") 187 | assert.Equal(t, "", s) 188 | assert.Error(t, err) 189 | assert.Equal(t, jsonic.ErrInvalidType, err) 190 | } 191 | 192 | func TestTypedArray(t *testing.T) { 193 | j, err := jsonic.New(readFromFile("test_data/test2.json", t)) 194 | assert.NotNil(t, j) 195 | assert.NoError(t, err) 196 | 197 | i, err := j.GetIntArray("z") 198 | assert.Nil(t, i) 199 | assert.Error(t, err) 200 | assert.Equal(t, jsonic.ErrNoDataFound, err) 201 | 202 | i, err = j.GetIntArray("e") 203 | assert.Equal(t, []int{1, 2}, i) 204 | assert.NoError(t, err) 205 | 206 | i, err = j.GetIntArray("a") 207 | assert.Nil(t, i) 208 | assert.Error(t, err) 209 | assert.Equal(t, jsonic.ErrInvalidType, err) 210 | 211 | i64, err := j.GetInt64Array("z") 212 | assert.Nil(t, i64) 213 | assert.Error(t, err) 214 | assert.Equal(t, jsonic.ErrNoDataFound, err) 215 | 216 | i64, err = j.GetInt64Array("e") 217 | assert.Equal(t, []int64{1, 2}, i64) 218 | assert.NoError(t, err) 219 | 220 | i64, err = j.GetInt64Array("a") 221 | assert.Nil(t, i64) 222 | assert.Error(t, err) 223 | assert.Equal(t, jsonic.ErrInvalidType, err) 224 | 225 | f, err := j.GetFloatArray("z") 226 | assert.Nil(t, f) 227 | assert.Error(t, err) 228 | assert.Equal(t, jsonic.ErrNoDataFound, err) 229 | 230 | f, err = j.GetFloatArray("f") 231 | assert.Equal(t, []float32{1.1, 2.2}, f) 232 | assert.NoError(t, err) 233 | 234 | f, err = j.GetFloatArray("a") 235 | assert.Nil(t, f) 236 | assert.Error(t, err) 237 | assert.Equal(t, jsonic.ErrInvalidType, err) 238 | 239 | f64, err := j.GetFloat64Array("z") 240 | assert.Nil(t, f64) 241 | assert.Error(t, err) 242 | assert.Equal(t, jsonic.ErrNoDataFound, err) 243 | 244 | f64, err = j.GetFloat64Array("f") 245 | assert.Equal(t, []float64{1.1, 2.2}, f64) 246 | assert.NoError(t, err) 247 | 248 | f64, err = j.GetFloat64Array("a") 249 | assert.Nil(t, f64) 250 | assert.Error(t, err) 251 | assert.Equal(t, jsonic.ErrInvalidType, err) 252 | 253 | b, err := j.GetBoolArray("z") 254 | assert.Nil(t, b) 255 | assert.Error(t, err) 256 | assert.Equal(t, jsonic.ErrNoDataFound, err) 257 | 258 | b, err = j.GetBoolArray("g") 259 | assert.Equal(t, []bool{true, false}, b) 260 | assert.NoError(t, err) 261 | 262 | b, err = j.GetBoolArray("a") 263 | assert.Nil(t, b) 264 | assert.Error(t, err) 265 | assert.Equal(t, jsonic.ErrInvalidType, err) 266 | 267 | s, err := j.GetStringArray("z") 268 | assert.Nil(t, s) 269 | assert.Error(t, err) 270 | assert.Equal(t, jsonic.ErrNoDataFound, err) 271 | 272 | s, err = j.GetStringArray("h") 273 | assert.Equal(t, []string{"naruto", "boruto"}, s) 274 | assert.NoError(t, err) 275 | 276 | s, err = j.GetStringArray("a") 277 | assert.Nil(t, s) 278 | assert.Error(t, err) 279 | assert.Equal(t, jsonic.ErrInvalidType, err) 280 | } 281 | 282 | func TestTypedMap(t *testing.T) { 283 | j, err := jsonic.New(readFromFile("test_data/test2.json", t)) 284 | assert.NotNil(t, j) 285 | assert.NoError(t, err) 286 | 287 | i, err := j.GetIntMap("z") 288 | assert.Nil(t, i) 289 | assert.Error(t, err) 290 | assert.Equal(t, jsonic.ErrNoDataFound, err) 291 | 292 | i, err = j.GetIntMap("i") 293 | assert.Equal(t, map[string]int{"naruto": 1}, i) 294 | assert.NoError(t, err) 295 | 296 | i, err = j.GetIntMap("a") 297 | assert.Nil(t, i) 298 | assert.Error(t, err) 299 | assert.Equal(t, jsonic.ErrInvalidType, err) 300 | 301 | i64, err := j.GetInt64Map("z") 302 | assert.Nil(t, i64) 303 | assert.Error(t, err) 304 | assert.Equal(t, jsonic.ErrNoDataFound, err) 305 | 306 | i64, err = j.GetInt64Map("i") 307 | assert.Equal(t, map[string]int64{"naruto": 1}, i64) 308 | assert.NoError(t, err) 309 | 310 | i64, err = j.GetInt64Map("a") 311 | assert.Nil(t, i64) 312 | assert.Error(t, err) 313 | assert.Equal(t, jsonic.ErrInvalidType, err) 314 | 315 | f, err := j.GetFloatMap("z") 316 | assert.Nil(t, f) 317 | assert.Error(t, err) 318 | assert.Equal(t, jsonic.ErrNoDataFound, err) 319 | 320 | f, err = j.GetFloatMap("j") 321 | assert.Equal(t, map[string]float32{"naruto": 1.1}, f) 322 | assert.NoError(t, err) 323 | 324 | f, err = j.GetFloatMap("a") 325 | assert.Nil(t, f) 326 | assert.Error(t, err) 327 | assert.Equal(t, jsonic.ErrInvalidType, err) 328 | 329 | f64, err := j.GetFloat64Map("z") 330 | assert.Nil(t, f64) 331 | assert.Error(t, err) 332 | assert.Equal(t, jsonic.ErrNoDataFound, err) 333 | 334 | f64, err = j.GetFloat64Map("j") 335 | assert.Equal(t, map[string]float64{"naruto": 1.1}, f64) 336 | assert.NoError(t, err) 337 | 338 | f64, err = j.GetFloat64Map("a") 339 | assert.Nil(t, f64) 340 | assert.Error(t, err) 341 | assert.Equal(t, jsonic.ErrInvalidType, err) 342 | 343 | b, err := j.GetBoolMap("z") 344 | assert.Nil(t, b) 345 | assert.Error(t, err) 346 | assert.Equal(t, jsonic.ErrNoDataFound, err) 347 | 348 | b, err = j.GetBoolMap("k") 349 | assert.Equal(t, map[string]bool{"naruto": true}, b) 350 | assert.NoError(t, err) 351 | 352 | b, err = j.GetBoolMap("a") 353 | assert.Nil(t, b) 354 | assert.Error(t, err) 355 | assert.Equal(t, jsonic.ErrInvalidType, err) 356 | 357 | s, err := j.GetStringMap("z") 358 | assert.Nil(t, s) 359 | assert.Error(t, err) 360 | assert.Equal(t, jsonic.ErrNoDataFound, err) 361 | 362 | s, err = j.GetStringMap("l") 363 | assert.Equal(t, map[string]string{"naruto": "rocks"}, s) 364 | assert.NoError(t, err) 365 | 366 | s, err = j.GetStringMap("a") 367 | assert.Nil(t, s) 368 | assert.Error(t, err) 369 | assert.Equal(t, jsonic.ErrInvalidType, err) 370 | } 371 | 372 | type TestTyped1 struct { 373 | X string `json:"x"` 374 | } 375 | 376 | type TestType2 struct { 377 | A string `json:"a"` 378 | CD struct { 379 | E string `json:"e"` 380 | } `json:"c.d"` 381 | } 382 | 383 | func TestGetTyped(t *testing.T) { 384 | j, err := jsonic.New(readFromFile("test_data/test1.json", t)) 385 | assert.NoError(t, err) 386 | assert.NotNil(t, j) 387 | 388 | // object 389 | var t1 TestTyped1 390 | err = j.GetTyped("a", &t1) 391 | assert.NoError(t, err) 392 | assert.Equal(t, "p", t1.X) 393 | 394 | // array 395 | var t2 []TestType2 396 | err = j.GetTyped("a.arr", &t2) 397 | assert.NoError(t, err) 398 | assert.NotNil(t, t2) 399 | assert.Equal(t, 1, len(t2)) 400 | assert.Equal(t, "b", t2[0].A) 401 | assert.Equal(t, "f", t2[0].CD.E) 402 | 403 | // not found 404 | var t3 TestTyped1 405 | err = j.GetTyped("z", &t3) 406 | assert.Error(t, err) 407 | assert.Equal(t, jsonic.ErrNoDataFound, err) 408 | 409 | // invalid type 410 | var t4 TestTyped1 411 | err = j.GetTyped("a.x.y", &t4) 412 | assert.Error(t, err) 413 | assert.Equal(t, err.Error(), "json: cannot unmarshal string into Go value of type jsonic_test.TestTyped1") 414 | } 415 | 416 | func TestDotOrEmptyChild(t *testing.T) { 417 | j, err := jsonic.New(readFromFile("test_data/test3.json", t)) 418 | assert.NoError(t, err) 419 | assert.NotNil(t, j) 420 | 421 | s, err := j.GetString(".") 422 | assert.NoError(t, err) 423 | assert.Equal(t, "a", s) 424 | 425 | s, err = j.GetString("") 426 | assert.NoError(t, err) 427 | assert.Equal(t, "b", s) 428 | 429 | s, err = j.GetString("") 430 | assert.NoError(t, err) 431 | assert.Equal(t, "b", s) 432 | } 433 | -------------------------------------------------------------------------------- /test_data/test1.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": { 3 | "x": "p", 4 | "arr": [ 5 | { 6 | "a": "b", 7 | "c.d": { 8 | "e": "f" 9 | } 10 | } 11 | ] 12 | }, 13 | "c": "d", 14 | "a.x": { 15 | "y": "q" 16 | }, 17 | "a.x.y": { 18 | "z": "r" 19 | } 20 | } -------------------------------------------------------------------------------- /test_data/test2.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": 1, 3 | "b": 2.2, 4 | "c": true, 5 | "d": "naruto", 6 | "e": [ 7 | 1, 8 | 2 9 | ], 10 | "f": [ 11 | 1.1, 12 | 2.2 13 | ], 14 | "g": [ 15 | true, 16 | false 17 | ], 18 | "h": [ 19 | "naruto", 20 | "boruto" 21 | ], 22 | "i": { 23 | "naruto": 1 24 | }, 25 | "j": { 26 | "naruto": 1.1 27 | }, 28 | "k": { 29 | "naruto": true 30 | }, 31 | "l": { 32 | "naruto": "rocks" 33 | } 34 | } -------------------------------------------------------------------------------- /test_data/test3.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "a", 3 | "": "b" 4 | } --------------------------------------------------------------------------------