├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── gson_test.go ├── read.go └── write.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | 5 | test: 6 | 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | 11 | - uses: actions/setup-go@v2 12 | with: 13 | go-version: 1.19 14 | 15 | - uses: actions/checkout@v2 16 | 17 | - run: | 18 | go run github.com/ysmood/golangci-lint@latest 19 | go test -coverprofile=coverage.out 20 | go run github.com/ysmood/got/cmd/check-cov@latest 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .golangci.yml 2 | *.out 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright 2020 Yad Smood 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/ysmood/gson.svg)](https://pkg.go.dev/github.com/ysmood/gson) 4 | 5 | The tests is the doc. 6 | 7 | A tiny JSON lib to read and alter a JSON value. The data structure is lazy, it's parse-on-read so that you can replace the parser with a faster one if performance is critical, use method `JSON.Raw` to do it. 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ysmood/gson 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /gson_test.go: -------------------------------------------------------------------------------- 1 | package gson_test 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "reflect" 8 | "testing" 9 | 10 | "github.com/ysmood/gson" 11 | ) 12 | 13 | func ExampleJSON() { 14 | obj := gson.NewFrom(`{"a": {"b": [1, 2]}}`) 15 | 16 | fmt.Println(obj.Get("a.b.0").Int()) 17 | 18 | obj.Set("a.b.1", "ok").Set("c", 2) 19 | obj.Del("c") 20 | fmt.Println(">", obj.JSON("> ", " ")) 21 | 22 | // Output: 23 | // 1 24 | // > { 25 | // > "a": { 26 | // > "b": [ 27 | // > 1, 28 | // > "ok" 29 | // > ] 30 | // > } 31 | // > } 32 | } 33 | 34 | func Test(t *testing.T) { 35 | eq := genEq(t) 36 | 37 | eq(gson.NewFrom("true").Bool(), true) 38 | eq(gson.New([]byte("10")).Int(), 10) 39 | eq(gson.New(10).Int(), 10) 40 | eq(gson.New(gson.New(10)).Int(), 10) 41 | eq(gson.JSON{}.Int(), 0) 42 | eq(gson.JSON{}.JSON("", ""), "null") 43 | eq(gson.New(nil).Num(), 0.0) 44 | eq(gson.New(nil).Bool(), false) 45 | 46 | buf := bytes.NewBufferString("10") 47 | fromBuf := gson.New(buf) 48 | eq(fromBuf.Int(), 10) 49 | eq(fromBuf.Int(), 10) 50 | 51 | b, _ := n(`"ok"`).MarshalJSON() 52 | eq(string(b), `"ok"`) 53 | 54 | eq(gson.JSON{}.Raw(), nil) 55 | eq(gson.New([]byte(`ok"`)).Raw(), []byte(`ok"`)) 56 | eq(n(`"ok"`).Str(), "ok") 57 | eq(n(`1`).Str(), "1") 58 | eq(n(`1.2`).Num(), 1.2) 59 | eq(n(`"ok"`).Num(), 0.0) 60 | eq(n(`1`).Int(), 1) 61 | eq(n(`"ok"`).Int(), 0) 62 | eq(n(`true`).Bool(), true) 63 | eq(n(`1`).Bool(), false) 64 | eq(n(`null`).Nil(), true) 65 | 66 | eq(n(`1`).Join(""), "") 67 | 68 | j := n(`{ 69 | "a": { 70 | "b": 1 71 | }, 72 | "c": ["x", "y", "z"] 73 | }`) 74 | 75 | eq(j.Get("a.b").Int(), 1) 76 | eq(j.Get("c.1").Str(), "y") 77 | 78 | v, _ := j.Gets("c", gson.Query(func(i interface{}) (val interface{}, has bool) { 79 | return i.([]interface{})[1], true 80 | })) 81 | eq(v.Str(), "y") 82 | 83 | eq(j.Get("c").Arr()[1].Str(), "y") 84 | eq(gson.New([]int{1, 2}).Arr()[1].Int(), 2) 85 | eq(j.Get("c").Join(" "), "x y z") 86 | eq(j.Get("a").Map()["b"].Int(), 1) 87 | eq(gson.New(map[string]int{"a": 1}).Map()["a"].Int(), 1) 88 | eq(len(j.Get("c").Map()), 0) 89 | 90 | eq(gson.New([]gson.JSON{ 91 | gson.New(1), 92 | gson.New(map[string]int{"a": 2}), 93 | }).Get("1.a").Int(), 2) 94 | 95 | v, _ = gson.New(map[float64]int{2: 3}).Gets(2.0) 96 | eq(v.Int(), 3) 97 | 98 | _, has := j.Gets(true) 99 | eq(has, false) 100 | 101 | eq(j.Has("a.b"), true) 102 | eq(j.Has("a.x"), false) 103 | eq(j.Has("c.10"), false) 104 | 105 | onNil := gson.JSON{} 106 | eq(onNil.Set("a.b", 10).Get("a.b").Int(), 10) 107 | 108 | self := gson.New(nil) 109 | self.Set("1", "a") 110 | self.Set("a.b.1", 1) 111 | self.Sets("ok") 112 | eq(self.Str(), "ok") 113 | self.Sets(map[string]int{"a": 1}) 114 | eq(self.Get("a").Int(), 1) 115 | self.Sets([]int{1}) 116 | eq(self.Get("0").Int(), 1) 117 | 118 | j.Sets(2.0, "a", "b") 119 | eq(j.Get("a.b").Int(), 2) 120 | 121 | j.Set("c.1", 2) 122 | eq(j.Get("c.1").Int(), 2) 123 | 124 | j.Sets(3, "a", "b") 125 | eq(j.Get("a.b").Int(), 3) 126 | 127 | eq(j.Get("s.10.b").Nil(), true) 128 | eq(j.Get("c.10").Nil(), true) 129 | 130 | j.Set("s.1.a", 10) 131 | j.Set("c.5", "ok") 132 | eq(fmt.Sprint(j), `map[a:map[b:3] c:[x 2 z ok] s:[ map[a:10]]]`) 133 | 134 | eq(j.Dels("s", 1, "a"), true) 135 | eq(fmt.Sprint(j), `map[a:map[b:3] c:[x 2 z ok] s:[ map[]]]`) 136 | eq(j.Dels("s", 1), true) 137 | eq(fmt.Sprint(j), `map[a:map[b:3] c:[x 2 z ok] s:[]]`) 138 | j.Del("c.1") 139 | eq(fmt.Sprint(j), `map[a:map[b:3] c:[x z ok] s:[]]`) 140 | eq(j.Dels("c", 10), false) 141 | eq(j.Dels("c", "1"), false) 142 | eq(j.Dels("xxx", "1"), false) 143 | eq(j.Dels(1), false) 144 | d := gson.New(1) 145 | d.Dels() 146 | eq(d.Val(), nil) 147 | } 148 | 149 | func TestUnmarshal(t *testing.T) { 150 | eq := genEq(t) 151 | 152 | v := struct { 153 | A int `json:"a"` 154 | }{} 155 | 156 | g := gson.New([]byte(`{"a":1}`)) 157 | 158 | err := g.Unmarshal(&v) 159 | if err != nil { 160 | t.Fatal(err) 161 | } 162 | 163 | if v.A != 1 { 164 | t.Fatal("parse error") 165 | } 166 | 167 | g.Get("a") 168 | eq(g.Unmarshal(&v).Error(), "gson: value has been parsed") 169 | 170 | eq(gson.JSON{}.Unmarshal(&v).Error(), "gson: no value to unmarshal") 171 | } 172 | 173 | func TestConvertors(t *testing.T) { 174 | eq := genEq(t) 175 | 176 | n := 1.2 177 | i := 1 178 | s := "ok" 179 | b := true 180 | 181 | eq(gson.Num(n), &n) 182 | eq(gson.Int(i), &i) 183 | eq(gson.Str(s), &s) 184 | eq(gson.Bool(b), &b) 185 | } 186 | 187 | func TestLab(t *testing.T) { 188 | } 189 | 190 | func n(s string) (j gson.JSON) { 191 | _ = json.Unmarshal([]byte(s), &j) 192 | return 193 | } 194 | 195 | func genEq(t *testing.T) func(a, b interface{}) { 196 | return func(a, b interface{}) { 197 | t.Helper() 198 | if !reflect.DeepEqual(a, b) { 199 | t.Log(a, "!=", b) 200 | t.Fail() 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /read.go: -------------------------------------------------------------------------------- 1 | // Package gson A tiny JSON lib to read and alter a JSON value. 2 | package gson 3 | 4 | import ( 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "reflect" 9 | "regexp" 10 | "strconv" 11 | "strings" 12 | "sync" 13 | ) 14 | 15 | // JSON represent a JSON value 16 | type JSON struct { 17 | lock *sync.Mutex 18 | value *interface{} 19 | } 20 | 21 | // MarshalJSON interface 22 | func (j JSON) MarshalJSON() ([]byte, error) { 23 | return json.Marshal(j.Val()) 24 | } 25 | 26 | // Unmarshal is the same as [json.Unmarshal] for the underlying raw value. 27 | // It should be called before other operations. 28 | func (j JSON) Unmarshal(v interface{}) error { 29 | if j.value == nil { 30 | return fmt.Errorf("gson: no value to unmarshal") 31 | } 32 | 33 | j.lock.Lock() 34 | defer j.lock.Unlock() 35 | 36 | b, ok := (*j.value).([]byte) 37 | if !ok { 38 | return fmt.Errorf("gson: value has been parsed") 39 | } 40 | 41 | return json.Unmarshal(b, v) 42 | } 43 | 44 | // JSON string 45 | func (j JSON) JSON(prefix, indent string) string { 46 | buf := bytes.NewBuffer(nil) 47 | enc := json.NewEncoder(buf) 48 | enc.SetEscapeHTML(false) 49 | enc.SetIndent(prefix, indent) 50 | _ = enc.Encode(j.Val()) 51 | s := buf.String() 52 | return s[:len(s)-1] 53 | } 54 | 55 | // Raw underlying value 56 | func (j JSON) Raw() interface{} { 57 | if j.value == nil { 58 | return nil 59 | } 60 | return *j.value 61 | } 62 | 63 | // String implements [fmt.Stringer] interface 64 | func (j JSON) String() string { 65 | return fmt.Sprintf("%v", j.Val()) 66 | } 67 | 68 | // Get by json path. It's a shortcut for Gets. 69 | func (j JSON) Get(path string) JSON { 70 | j, _ = j.Gets(Path(path)...) 71 | return j 72 | } 73 | 74 | // Has an element is found on the path 75 | func (j JSON) Has(path string) bool { 76 | _, has := j.Gets(Path(path)...) 77 | return has 78 | } 79 | 80 | // Query section 81 | type Query func(interface{}) (val interface{}, has bool) 82 | 83 | // Gets element by path sections. If a section is not string, int, or func, it will be ignored. 84 | // If it's a func, the value will be passed to it, the result of it will the next level. 85 | // The last return value will be false if not found. 86 | func (j JSON) Gets(sections ...interface{}) (JSON, bool) { 87 | for _, sect := range sections { 88 | var val interface{} 89 | var has bool 90 | 91 | if fn, ok := sect.(Query); ok { 92 | val, has = fn(j.Val()) 93 | } else { 94 | val, has = get(reflect.ValueOf(j.Val()), sect) 95 | } 96 | 97 | if !has { 98 | return New(nil), false 99 | } 100 | j.value = &val 101 | } 102 | return j, true 103 | } 104 | 105 | func get(objVal reflect.Value, sect interface{}) (val interface{}, has bool) { 106 | switch k := sect.(type) { 107 | case int: 108 | if objVal.Kind() != reflect.Slice || k >= objVal.Len() { 109 | return 110 | } 111 | 112 | has = true 113 | val = objVal.Index(k).Interface() 114 | 115 | default: 116 | sectVal := reflect.ValueOf(sect) 117 | 118 | if objVal.Kind() != reflect.Map || !sectVal.Type().AssignableTo(objVal.Type().Key()) { 119 | return 120 | } 121 | 122 | v := objVal.MapIndex(sectVal) 123 | if !v.IsValid() { 124 | return 125 | } 126 | 127 | has = true 128 | val = v.Interface() 129 | } 130 | 131 | return 132 | } 133 | 134 | // Str value 135 | func (j JSON) Str() string { 136 | v := j.Val() 137 | if v, ok := v.(string); ok { 138 | return v 139 | } 140 | return fmt.Sprintf("%v", v) 141 | } 142 | 143 | var floatType = reflect.TypeOf(.0) 144 | 145 | // Num value 146 | // returns zero value for type if underlying JSON type is not convertible. 147 | func (j JSON) Num() float64 { 148 | v := reflect.ValueOf(j.Val()) 149 | if v.IsValid() && v.Type().ConvertibleTo(floatType) { 150 | return v.Convert(floatType).Float() 151 | } 152 | return 0 153 | } 154 | 155 | // Bool value 156 | // returns zero value for type if underlying JSON type is not boolean 157 | func (j JSON) Bool() bool { 158 | if v, ok := j.Val().(bool); ok { 159 | return v 160 | } 161 | return false 162 | } 163 | 164 | // Nil or not 165 | func (j JSON) Nil() bool { 166 | return j.Val() == nil 167 | } 168 | 169 | var intType = reflect.TypeOf(0) 170 | 171 | // Int value 172 | // returns zero value for type if underlying JSON type is not convertible. 173 | func (j JSON) Int() int { 174 | v := reflect.ValueOf(j.Val()) 175 | if v.IsValid() && v.Type().ConvertibleTo(intType) { 176 | return int(v.Convert(intType).Int()) 177 | } 178 | return 0 179 | } 180 | 181 | // Map of JSON 182 | // returns empty map if underlying JSON object is not a map. 183 | func (j JSON) Map() map[string]JSON { 184 | val := reflect.ValueOf(j.Val()) 185 | if val.IsValid() && val.Kind() == reflect.Map && val.Type().Key().Kind() == reflect.String { 186 | obj := map[string]JSON{} 187 | iter := val.MapRange() 188 | for iter.Next() { 189 | obj[iter.Key().String()] = New(iter.Value().Interface()) 190 | } 191 | return obj 192 | } 193 | 194 | return make(map[string]JSON) 195 | } 196 | 197 | // Arr of JSON 198 | // returns empty array if underlying JSON is not an array. 199 | func (j JSON) Arr() []JSON { 200 | val := reflect.ValueOf(j.Val()) 201 | if val.IsValid() && val.Kind() == reflect.Slice { 202 | obj := []JSON{} 203 | l := val.Len() 204 | for i := 0; i < l; i++ { 205 | obj = append(obj, New(val.Index(i).Interface())) 206 | } 207 | return obj 208 | } 209 | 210 | return make([]JSON, 0) 211 | } 212 | 213 | // Join elements 214 | func (j JSON) Join(sep string) string { 215 | list := []string{} 216 | 217 | for _, el := range j.Arr() { 218 | list = append(list, el.Str()) 219 | } 220 | 221 | return strings.Join(list, sep) 222 | } 223 | 224 | var regIndex = regexp.MustCompile(`^0|([1-9]\d*)$`) 225 | 226 | // Path from string 227 | func Path(path string) []interface{} { 228 | list := strings.Split(path, ".") 229 | sects := make([]interface{}, len(list)) 230 | for i, s := range list { 231 | if regIndex.MatchString(s) { 232 | index, err := strconv.ParseInt(s, 10, 64) 233 | if err == nil { 234 | sects[i] = int(index) 235 | continue 236 | } 237 | } 238 | sects[i] = s 239 | } 240 | return sects 241 | } 242 | 243 | // Num returns the pointer of the v 244 | func Num(v float64) *float64 { 245 | return &v 246 | } 247 | 248 | // Int returns the pointer of the v 249 | func Int(v int) *int { 250 | return &v 251 | } 252 | 253 | // Str returns the pointer of the v 254 | func Str(v string) *string { 255 | return &v 256 | } 257 | 258 | // Bool returns the pointer of the v 259 | func Bool(v bool) *bool { 260 | return &v 261 | } 262 | -------------------------------------------------------------------------------- /write.go: -------------------------------------------------------------------------------- 1 | package gson 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "reflect" 7 | "sync" 8 | ) 9 | 10 | // New JSON from []byte, [io.Reader], or raw value. 11 | func New(v interface{}) JSON { 12 | return JSON{&sync.Mutex{}, &v} 13 | } 14 | 15 | // NewFrom json encoded string 16 | func NewFrom(s string) JSON { 17 | return New([]byte(s)) 18 | } 19 | 20 | // UnmarshalJSON interface 21 | func (j *JSON) UnmarshalJSON(b []byte) error { 22 | *j = New(b) 23 | return nil 24 | } 25 | 26 | // Val of the underlying json value. 27 | // The first time it's called, it will try to parse the underlying data. 28 | func (j JSON) Val() interface{} { 29 | if j.value == nil { 30 | return nil 31 | } 32 | 33 | j.lock.Lock() 34 | defer j.lock.Unlock() 35 | 36 | for { 37 | val, ok := (*j.value).(JSON) 38 | if ok { 39 | *j.value = *val.value 40 | } else { 41 | break 42 | } 43 | } 44 | 45 | var val interface{} 46 | switch v := (*j.value).(type) { 47 | case []byte: 48 | _ = json.Unmarshal(v, &val) 49 | *j.value = val 50 | case io.Reader: 51 | _ = json.NewDecoder(v).Decode(&val) 52 | *j.value = val 53 | } 54 | 55 | return *j.value 56 | } 57 | 58 | // Set by json path. It's a shortcut for Sets. 59 | func (j *JSON) Set(path string, val interface{}) *JSON { 60 | return j.Sets(val, Path(path)...) 61 | } 62 | 63 | var _map map[string]interface{} 64 | var interfaceType = reflect.TypeOf(_map).Elem() 65 | 66 | // Sets element by path sections. If a section is not string or int, it will be ignored. 67 | func (j *JSON) Sets(target interface{}, sections ...interface{}) *JSON { 68 | if j.value == nil { 69 | *j = New(nil) 70 | } 71 | 72 | last := len(sections) - 1 73 | val := reflect.ValueOf(j.Val()) 74 | override := func(v reflect.Value) { 75 | *j.value = v.Interface() 76 | } 77 | 78 | if last == -1 { 79 | *j.value = target 80 | return j 81 | } 82 | 83 | for i, s := range sections { 84 | sect := reflect.ValueOf(s) 85 | if val.Kind() == reflect.Interface { 86 | val = val.Elem() 87 | } 88 | 89 | switch sect.Kind() { 90 | case reflect.Int: 91 | k := int(sect.Int()) 92 | if val.Kind() != reflect.Slice || val.Len() <= k { 93 | nArr := reflect.ValueOf(make([]interface{}, k+1)) 94 | if val.Kind() == reflect.Slice { 95 | reflect.Copy(nArr, val) 96 | } 97 | val = nArr 98 | override(val) 99 | } 100 | if i == last { 101 | val.Index(k).Set(reflect.ValueOf(target)) 102 | return j 103 | } 104 | prev := val 105 | val = val.Index(k) 106 | override = func(v reflect.Value) { 107 | prev.Index(k).Set(v) 108 | } 109 | default: 110 | targetVal := reflect.ValueOf(target) 111 | if val.Kind() != reflect.Map { 112 | val = reflect.MakeMap(reflect.MapOf(sect.Type(), interfaceType)) 113 | override(val) 114 | } 115 | if i == last { 116 | val.SetMapIndex(sect, targetVal) 117 | } 118 | prev := val 119 | val = val.MapIndex(sect) 120 | override = func(v reflect.Value) { 121 | prev.SetMapIndex(sect, v) 122 | } 123 | } 124 | } 125 | return j 126 | } 127 | 128 | // Del deletes the element at the path. 129 | func (j *JSON) Del(path string) *JSON { 130 | j.Dels(Path(path)...) 131 | return j 132 | } 133 | 134 | // Dels deletes the element at the path sections. 135 | // Return true if it's deleted. 136 | func (j *JSON) Dels(sections ...interface{}) bool { 137 | l := len(sections) 138 | 139 | if l == 0 { 140 | j.value = nil 141 | return true 142 | } 143 | 144 | last := sections[l-1] 145 | 146 | parent, has := j.Gets(sections[:l-1]...) 147 | if !has { 148 | return false 149 | } 150 | 151 | parentVal := reflect.ValueOf(parent.Val()) 152 | lastVal := reflect.ValueOf(last) 153 | 154 | switch k := last.(type) { 155 | case int: 156 | pl := parentVal.Len() 157 | if parentVal.Kind() != reflect.Slice || k < 0 || k >= pl { 158 | return false 159 | } 160 | 161 | j.Sets(reflect.AppendSlice( 162 | parentVal.Slice(0, k), 163 | parentVal.Slice(k+1, pl), 164 | ).Interface(), sections[:l-1]...) 165 | 166 | default: 167 | if parentVal.Kind() != reflect.Map || !lastVal.Type().AssignableTo(parentVal.Type().Key()) { 168 | return false 169 | } 170 | 171 | parentVal.SetMapIndex(lastVal, reflect.Value{}) 172 | } 173 | 174 | return true 175 | } 176 | --------------------------------------------------------------------------------