├── .gitignore ├── range.go ├── test.json ├── map_data.go ├── go.mod ├── zhihu_test.go ├── js_data.go ├── fmt.go ├── raw_data.go ├── go.sum ├── jsnm.go ├── README.md └── jsnm_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | pprof 2 | *.json 3 | .idea/* 4 | -------------------------------------------------------------------------------- /range.go: -------------------------------------------------------------------------------- 1 | package jsnm 2 | 3 | func (j *Jsnm) Range(do func(i int, ji *Jsnm) bool) *Jsnm { 4 | arr := j.Arr() 5 | for idx, it := range arr { 6 | if do(idx, it) { 7 | break 8 | } 9 | } 10 | return j 11 | } 12 | -------------------------------------------------------------------------------- /test.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "Two", 3 | "Age": 2, 4 | "Friends": { 5 | "One": { 6 | "Name": "One", 7 | "Age": 1, 8 | "Friends": { 9 | "One": null 10 | }, 11 | "Loc": [ 12 | "One", 13 | "OneOne" 14 | ] 15 | }, 16 | "Two": null 17 | }, 18 | "Loc": [ 19 | "Two", 20 | "TwoTwo" 21 | ] 22 | } -------------------------------------------------------------------------------- /map_data.go: -------------------------------------------------------------------------------- 1 | package jsnm 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type MapData map[string]interface{} 8 | 9 | func (m *MapData) String() string { 10 | return fmt.Sprintf("{map_data:%#v}", m) 11 | } 12 | 13 | func (m MapData) Keys() []string { 14 | md := (map[string]interface{})(m) 15 | keys := make([]string, 0, len(md)) 16 | for k, _ := range md { 17 | keys = append(keys, k) 18 | } 19 | return keys 20 | } 21 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/toukii/jsnm 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/everfore/exc v1.0.1 7 | github.com/toukii/goutils v0.1.2 8 | github.com/toukii/membership v0.0.0-20160902064802-53d79198a49f 9 | ) 10 | 11 | require ( 12 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect 13 | github.com/fatih/color v1.18.0 // indirect 14 | github.com/kr/pretty v0.3.1 // indirect 15 | github.com/mattn/go-colorable v0.1.13 // indirect 16 | github.com/mattn/go-isatty v0.0.20 // indirect 17 | github.com/rogpeppe/go-internal v1.12.0 // indirect 18 | golang.org/x/sys v0.29.0 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /zhihu_test.go: -------------------------------------------------------------------------------- 1 | package jsnm 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | // http://news-at.zhihu.com/api/3/news/hot 9 | 10 | func ExampleWriter() { 11 | resp, err := http.Get("http://news-at.zhihu.com/api/3/news/hot") 12 | if checkerr(err) { 13 | fmt.Println(err) 14 | return 15 | } 16 | jm := ReaderFmt(resp.Body) 17 | // t.Log(jm.RawData()) 18 | arr := jm.Get("recent").Arr() 19 | fmt.Printf("arr's length:%d\n", len(arr)) 20 | for i, it := range arr { 21 | fmt.Println(i+1, it.Get("title").RawData().String()) 22 | fmt.Println(i+1, it.Get("url").RawData().String()) 23 | } 24 | } 25 | 26 | func checkerr(err error) bool { 27 | if err != nil { 28 | return true 29 | } 30 | return false 31 | } 32 | -------------------------------------------------------------------------------- /js_data.go: -------------------------------------------------------------------------------- 1 | package jsnm 2 | 3 | type Jsnm struct { 4 | raw_data interface{} 5 | map_data MapData 6 | arr_data []*Jsnm 7 | cache map[string]*Jsnm 8 | } 9 | 10 | func NewJsnm(raw interface{}) *Jsnm { 11 | return &Jsnm{raw_data: raw} 12 | } 13 | 14 | func (j *Jsnm) RawData() *RawData { 15 | if nil == j { 16 | return nil 17 | } 18 | return &RawData{raw: j.raw_data} 19 | } 20 | 21 | func (j *Jsnm) MapData() MapData { 22 | if j.map_data == nil { 23 | j.map_data = make(MapData) 24 | if map_data, ok := j.raw_data.(map[string]interface{}); ok { 25 | j.map_data = map_data 26 | } else { 27 | return nil 28 | } 29 | } 30 | 31 | return j.map_data 32 | } 33 | 34 | func (j *Jsnm) MustFloat64() float64 { 35 | return j.RawData().MustFloat64() 36 | } 37 | 38 | func (j *Jsnm) MustInt64() int64 { 39 | return j.RawData().MustInt64() 40 | } 41 | 42 | func (j *Jsnm) Decode() string { 43 | return j.RawData().Decode() 44 | } 45 | -------------------------------------------------------------------------------- /fmt.go: -------------------------------------------------------------------------------- 1 | package jsnm 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io" 7 | "os" 8 | 9 | "github.com/everfore/exc" 10 | "github.com/toukii/goutils" 11 | ) 12 | 13 | func BufStringFmt(bufStr string) *Jsnm { 14 | buf := bytes.NewBufferString(bufStr) 15 | return ReaderFmt(buf) 16 | } 17 | 18 | func BytesFmt(bs []byte) *Jsnm { 19 | if nil == bs { 20 | return nil 21 | } 22 | v := NewJsnm(nil) 23 | err := json.Unmarshal(bs, &v.raw_data) 24 | if goutils.LogCheckErr(err) { 25 | return nil 26 | } 27 | return v 28 | } 29 | 30 | func ReaderFmt(r io.Reader) *Jsnm { 31 | v := NewJsnm(nil) 32 | err := json.NewDecoder(r).Decode(&v.raw_data) 33 | if goutils.LogCheckErr(err) { 34 | return nil 35 | } 36 | return v 37 | } 38 | 39 | func FileNameFmt(fn string) *Jsnm { 40 | rf, err := os.OpenFile(fn, os.O_RDONLY, 0644) 41 | if goutils.LogCheckErr(err) { 42 | return nil 43 | } 44 | return ReaderFmt(rf) 45 | } 46 | 47 | func CmdFmt(cmd string) *Jsnm { 48 | bs, err := exc.NewCMD(cmd).DoNoTime() 49 | if goutils.LogCheckErr(err) { 50 | return nil 51 | } 52 | return BytesFmt(bs) 53 | } 54 | 55 | func StructFmt(bs []byte, st interface{}) error { 56 | err := json.Unmarshal(bs, st) 57 | if goutils.LogCheckErr(err) { 58 | return err 59 | } 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /raw_data.go: -------------------------------------------------------------------------------- 1 | package jsnm 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strconv" 7 | ) 8 | 9 | type RawData struct { 10 | raw interface{} 11 | } 12 | 13 | func NewRawData(raw interface{}) RawData { 14 | return RawData{raw: raw} 15 | } 16 | 17 | func (d *RawData) Raw() interface{} { 18 | if d == nil { 19 | return nil 20 | } 21 | return d.raw 22 | } 23 | 24 | func (d *RawData) String() string { 25 | if d == nil { 26 | return "" 27 | } 28 | return fmt.Sprintf("%v", d.raw) 29 | } 30 | 31 | func (d *RawData) Int64() (int64, error) { 32 | return strconv.ParseInt(d.String(), 10, 0) 33 | } 34 | 35 | func (d *RawData) MustInt64() int64 { 36 | i64, err := strconv.ParseInt(d.String(), 10, 0) 37 | if err != nil { 38 | return 0 39 | } 40 | return i64 41 | } 42 | 43 | func (d *RawData) MustFloat64() float64 { 44 | f, err := d.Float64() 45 | if err != nil { 46 | return 0.0 47 | } 48 | return f 49 | } 50 | 51 | func (d *RawData) Float64() (float64, error) { 52 | return strconv.ParseFloat(d.String(), 0) 53 | } 54 | 55 | func (d *RawData) Decode() string { 56 | typ := reflect.TypeOf(d.raw) 57 | switch typ.Kind() { 58 | case reflect.String: 59 | return d.String() 60 | case reflect.Float64, reflect.Float32: 61 | return strconv.FormatInt(int64(d.MustFloat64()), 10) 62 | case reflect.Int8, reflect.Int16, reflect.Int, reflect.Int32, reflect.Int64: 63 | return strconv.FormatInt(d.MustInt64(), 10) 64 | default: 65 | return d.String() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= 2 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= 3 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 4 | github.com/everfore/exc v1.0.1 h1:UzC3yIuQvemT2bJBfA75DA8TbwMvtGu894uYt51BhtQ= 5 | github.com/everfore/exc v1.0.1/go.mod h1:fxgbLWklumVmgTxPZrFSgr38s/jY9gVShx/d5TWF0uI= 6 | github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 7 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 8 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 9 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 10 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 14 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 15 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 16 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 17 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 18 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 19 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 20 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 21 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 22 | github.com/toukii/goutils v0.1.2 h1:iwQMI3ExZS/o2y6bRUrY4Te6IOLjju1Drd1zjFy9NoU= 23 | github.com/toukii/goutils v0.1.2/go.mod h1:U//8Y+AqvEuXPxkZ+V+vcpBfZzscDrwyNy7azHd4nTI= 24 | github.com/toukii/membership v0.0.0-20160902064802-53d79198a49f h1:FuHPY5AgwI+vZ+7/dUwpG0XeXBt+i8uwh20YlDwRDCk= 25 | github.com/toukii/membership v0.0.0-20160902064802-53d79198a49f/go.mod h1:nxsY1N5Ppc/9xKCD1xgKwmnmTHN+6M+Nvy3O//Amnx4= 26 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 27 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 28 | golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 29 | golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 30 | -------------------------------------------------------------------------------- /jsnm.go: -------------------------------------------------------------------------------- 1 | package jsnm 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // No Cache Get 11 | func (j *Jsnm) NCGet(path ...string) *Jsnm { 12 | if j == nil || len(path) <= 0 { 13 | return j 14 | } 15 | // first step: get data from mapdata 16 | map_data, ok := j.raw_data.(map[string]interface{}) 17 | if !ok { 18 | return nil 19 | } 20 | cur, ok := map_data[path[0]] 21 | if !ok { 22 | return nil 23 | } 24 | // second step: cache the data 25 | subj := NewJsnm(cur) 26 | if len(path) == 1 { 27 | return subj 28 | } 29 | return subj.NCGet(path[1:]...) 30 | } 31 | 32 | // Cache PathGet 33 | func (j *Jsnm) PathGet(path ...string) *Jsnm { 34 | if j == nil || len(path) <= 0 { 35 | return j 36 | } 37 | jm := j 38 | var cache_jm *Jsnm 39 | var exist bool 40 | var sub_data interface{} 41 | for _, subpath := range path { 42 | if jm.cache == nil { 43 | jm.cache = make(map[string]*Jsnm) 44 | //fmt.Printf("**make cache,subpath:%s, path:%#v\n",subpath,path) 45 | } else { 46 | if cache_jm, exist = jm.cache[subpath]; exist { 47 | jm = cache_jm 48 | continue 49 | } 50 | } 51 | if jm.map_data == nil { 52 | jm.map_data = make(MapData) 53 | if map_data, ok := jm.raw_data.(map[string]interface{}); ok { 54 | //fmt.Printf("@@cache map_data,subpath:%s, path:%#v\n",subpath,path) 55 | jm.map_data = map_data 56 | } else { 57 | return nil 58 | } 59 | } 60 | sub_data, exist = jm.map_data[subpath] 61 | if !exist { 62 | return nil 63 | } 64 | sub_jm := NewJsnm(sub_data) 65 | jm.cache[subpath] = sub_jm 66 | //fmt.Printf("##cache jsnm, subpath:%s\n",subpath) 67 | jm = sub_jm 68 | } 69 | return jm 70 | } 71 | 72 | // Cache Get 73 | func (j *Jsnm) Get(path string) *Jsnm { 74 | if j == nil { 75 | return nil 76 | } 77 | // first step: get data from cache 78 | if nil == j.cache { 79 | j.cache = make(map[string]*Jsnm) 80 | //fmt.Println("**make cache") 81 | } else { 82 | if cache_data, ok := j.cache[path]; ok { 83 | return cache_data 84 | } 85 | } 86 | // second step: get data from mapdata 87 | if j.map_data == nil { 88 | if map_data, ok := j.raw_data.(map[string]interface{}); ok { 89 | j.map_data = map_data 90 | //fmt.Println("@@cache map_data, path:",path) 91 | } else { 92 | return nil 93 | } 94 | } 95 | cur, ok := j.map_data[path] 96 | if !ok { 97 | return nil 98 | } 99 | // third step: cache the data 100 | will_cache_data := NewJsnm(cur) 101 | //fmt.Println("##cache jsnm, path:",path) 102 | j.cache[path] = will_cache_data 103 | return will_cache_data 104 | } 105 | 106 | // Cache Arr 107 | func (j *Jsnm) Arr() []*Jsnm { 108 | if j == nil { 109 | return nil 110 | } 111 | if j.arr_data != nil { 112 | return j.arr_data 113 | } 114 | arr, ok := (j.raw_data).([]interface{}) 115 | if !ok { 116 | return nil 117 | } 118 | ret := make([]*Jsnm, 0, len(arr)) 119 | for _, vry := range arr { 120 | ret = append(ret, NewJsnm(vry)) 121 | } 122 | //fmt.Println("cache arr_data") 123 | j.arr_data = ret 124 | return ret 125 | } 126 | 127 | // Cache ArrLocs 128 | func (j *Jsnm) ArrLocs(locs ...int) *Jsnm { 129 | if len(locs) <= 0 { 130 | return nil 131 | } 132 | subarr := j.ArrLoc(locs[0]) 133 | l := len(locs) 134 | for i := 1; i < l; i++ { 135 | if subarr == nil { 136 | return nil 137 | } 138 | subarr = subarr.ArrLoc(locs[i]) 139 | } 140 | return subarr 141 | } 142 | 143 | // Cache ArrLoc i 144 | func (j *Jsnm) ArrLoc(i int) *Jsnm { 145 | if j == nil { 146 | return nil 147 | } 148 | if nil != j.arr_data { 149 | //if i >= len(j.arr_data) { 150 | // return nil 151 | //} 152 | if j.arr_data[i] != nil && i < len(j.arr_data) { 153 | return j.arr_data[i] 154 | } 155 | } 156 | arr, ok := (j.raw_data).([]interface{}) 157 | if !ok || i >= len(arr) { 158 | return nil 159 | } 160 | arr_cache := make([]*Jsnm, len(arr)) 161 | //fmt.Println("cache arr_data") 162 | j.arr_data = arr_cache 163 | j.arr_data[i] = NewJsnm(arr[i]) 164 | return j.arr_data[i] 165 | } 166 | 167 | func (j *Jsnm) ArrPath(path ...interface{}) *Jsnm { 168 | if len(path) <= 0 { 169 | return j 170 | } 171 | switch reflect.TypeOf(path[0]).Kind() { 172 | case reflect.String: 173 | return j.Get(path[0].(string)).ArrPath(path[1:]...) 174 | case reflect.Int: 175 | return j.ArrLoc(path[0].(int)).ArrPath(path[1:]...) 176 | } 177 | return nil 178 | } 179 | 180 | func (j *Jsnm) ArrGet(path ...string) *Jsnm { 181 | if len(path) <= 0 { 182 | return j 183 | } 184 | if strings.HasSuffix(path[0], "\"") { 185 | path_0 := strings.Trim(path[0], "\"") 186 | return j.Get(path_0).ArrGet(path[1:]...) 187 | } 188 | 189 | var i byte 190 | if len(path[0]) > 0 { 191 | i = path[0][0] 192 | } 193 | if i >= 48 && i <= 57 { 194 | loc, err2 := strconv.ParseInt(path[0], 10, 64) 195 | if err2 != nil { 196 | fmt.Println(err2) 197 | } 198 | return j.ArrLoc(int(loc)).ArrGet(path[1:]...) 199 | } 200 | return j.Get(path[0]).ArrGet(path[1:]...) 201 | } 202 | 203 | func (j *Jsnm) String() string { 204 | if nil == j { 205 | return "" 206 | } 207 | return j.RawData().String() 208 | } 209 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [jsnm][1] 2 | 3 | --------------------- 4 | 5 | 6 | __json mapping for map[string]interface{}__ 7 | 8 | 提供缓存的json解析器,初衷是提高重复解析json速度。 9 | 10 | 11 | ## 用法 12 | 13 | 方法有:__Get PathGet Arr ArrLoc ArrLocs ArrPath__,为了对比,提供了不带缓存的方法:__NCGet__. 14 | 15 | - 带有数组的,要用ArrPath; 16 | 17 | - 无数组形式可以用Get或PathGet; 18 | 19 | - 若不使用缓存,用NCGet,性能会低。 20 | 21 | 22 | 1. 定义结构体如下: 23 | 24 | ```go 25 | type User struct { 26 | Name string 27 | Age byte 28 | Loc []string 29 | Friends map[string]*User 30 | } 31 | ``` 32 | 33 | 2. 数据如下: 34 | 35 | ```json 36 | { 37 | "Name": "foo", 38 | "Age": 21, 39 | "Friends": { 40 | "bar": { 41 | "Name": "bar", 42 | "Age": 22, 43 | "Friends": { 44 | "bar": null 45 | }, 46 | "Loc": [ 47 | "shanghai", 48 | "jiaxing" 49 | ] 50 | }, 51 | "kaa": null 52 | }, 53 | "Loc": [ 54 | "beijing", 55 | "tianjin" 56 | ] 57 | } 58 | ``` 59 | 60 | 3. 用法 61 | 62 | ``` 63 | js.Get("Friends").Get("bar").Get("Loc").ArrLoc(0).String() 64 | js.PathGet("Friends","bar").Get("Loc").Arr[0].String() 65 | js.Get("Friends").PathGet("bar","Loc").ArrLoc(0).String() 66 | js.PathGet("Friends","bar","Loc").Arr[0].String() 67 | js.ArrPath("Friends","bar","Loc",0).String() 68 | ``` 69 | 70 | 再如: 71 | 72 | ``` 73 | [ 74 | [ 75 | [ 76 | [ 77 | "a" 78 | ] 79 | ] 80 | ] 81 | ] 82 | ``` 83 | 84 | 获取`a`的方式: 85 | 86 | ``` 87 | js.ArrLocs(0,0,0,0).String() 88 | js.ArrLocs(0,0).ArrLoc(0).Arr[0].String() 89 | js.Arr[0].ArrLocs(0,0).ArrLoc(0).String() 90 | js.ArrLoc(0).Arr[0].ArrLocs(0,0).String() 91 | ``` 92 | 93 | ## 数据结构设计 94 | 95 | ```go 96 | type Jsnm struct { 97 | raw RawData 98 | data MapData 99 | arr_data []*Jsnm 100 | cache map[string]*Jsnm 101 | } 102 | 103 | type RawData struct { 104 | raw interface{} 105 | } 106 | 107 | type MapData map[string]interface{} 108 | ``` 109 | 110 | **RawData是原始数据;MapData是可以转换为map[string]interface{}的RawData;arr_data缓存数组;cache是缓存数据,有重合路径时,可以提高访问速度。** 111 | 112 | ## 核心函数 113 | 114 | **Get** 115 | 116 | ```go 117 | // Cache PathGet 118 | func (j *Jsnm) PathGet(path ...string) *Jsnm { 119 | if j == nil || len(path) <= 0 { 120 | return j 121 | } 122 | jm := j 123 | var cache_jm *Jsnm 124 | var exist bool 125 | var sub_data interface{} 126 | for _, subpath := range path { 127 | if jm.cache == nil { 128 | jm.cache = make(map[string]*Jsnm) 129 | } else { 130 | if cache_jm, exist = jm.cache[subpath]; exist { 131 | jm = cache_jm 132 | continue 133 | } 134 | } 135 | if jm.map_data == nil { 136 | jm.map_data = make(MapData) 137 | if map_data, ok := jm.raw_data.(map[string]interface{}); ok { 138 | jm.map_data = map_data 139 | } else { 140 | return nil 141 | } 142 | } 143 | sub_data, exist = jm.map_data[subpath] 144 | if !exist { 145 | return nil 146 | } 147 | sub_jm := NewJsnm(sub_data) 148 | jm.cache[subpath] = sub_jm 149 | jm = sub_jm 150 | } 151 | return jm 152 | } 153 | 154 | // Cache Get 155 | func (j *Jsnm) Get(path string) *Jsnm { 156 | if j==nil { 157 | return nil 158 | } 159 | // first step: get data from cache 160 | if nil == j.cache { 161 | j.cache = make(map[string]*Jsnm) 162 | } else { 163 | if cache_data, ok := j.cache[path]; ok { 164 | return cache_data 165 | } 166 | } 167 | // second step: get data from mapdata 168 | if j.map_data == nil { 169 | if map_data, ok := j.raw_data.(map[string]interface{}); ok { 170 | j.map_data = map_data 171 | } else { 172 | return nil 173 | } 174 | } 175 | cur, ok := j.map_data[path] 176 | if !ok { 177 | return nil 178 | } 179 | // third step: cache the data 180 | will_cache_data := NewJsnm(cur) 181 | j.cache[path] = will_cache_data 182 | return will_cache_data 183 | } 184 | ``` 185 | 186 | ------------------------------- 187 | 188 | **Arr** 189 | 190 | ```go 191 | // Cache Arr 192 | func (j *Jsnm) Arr() []*Jsnm { 193 | if j == nil { 194 | return nil 195 | } 196 | if j.arr_data != nil { 197 | return j.arr_data 198 | } 199 | arr, ok := (j.raw_data).([]interface{}) 200 | if !ok { 201 | return nil 202 | } 203 | ret := make([]*Jsnm, 0, len(arr)) 204 | for _, vry := range arr { 205 | ret = append(ret, NewJsnm(vry)) 206 | } 207 | j.arr_data = ret 208 | return ret 209 | } 210 | 211 | // Cache ArrLocs 212 | func (j *Jsnm) ArrLocs(locs ...int) *Jsnm { 213 | if len(locs)<=0 { 214 | return nil 215 | } 216 | subarr:= j.ArrLoc(locs[0]) 217 | l:=len(locs) 218 | for i := 1; i < l; i++ { 219 | if subarr==nil { 220 | return nil 221 | } 222 | subarr = subarr.ArrLoc(locs[i]) 223 | } 224 | return subarr 225 | } 226 | 227 | // Cache ArrLoc i 228 | func (j *Jsnm) ArrLoc(i int) *Jsnm { 229 | if j == nil { 230 | return nil 231 | } 232 | if nil != j.arr_data { 233 | if i >= len(j.arr_data) { 234 | return nil 235 | } 236 | if j.arr_data[i] != nil { 237 | return j.arr_data[i] 238 | } 239 | } 240 | arr, ok := (j.raw_data).([]interface{}) 241 | if !ok { 242 | return nil 243 | } 244 | arr_cache := make([]*Jsnm, len(arr)) 245 | j.arr_data = arr_cache 246 | j.arr_data[i] = NewJsnm(arr[i]) 247 | if i >= len(arr) { 248 | return nil 249 | } 250 | return j.arr_data[i] 251 | } 252 | 253 | // Cache ArrPath path 254 | func (j *Jsnm) ArrPath(path ...interface{}) *Jsnm { 255 | if len(path) <= 0 { 256 | return j 257 | } 258 | switch reflect.TypeOf(path[0]).Kind() { 259 | case reflect.String: 260 | return j.Get(path[0].(string)).ArrPath(path[1:]...) 261 | case reflect.Int: 262 | return j.ArrLoc(path[0].(int)).ArrPath(path[1:]...) 263 | } 264 | return nil 265 | } 266 | ``` 267 | 268 | **具体的类型转换,可添加函数实现。** 269 | 270 | _Example_ 271 | 272 | ```go 273 | age := jm.Get("Friends").Get("Age").MustInt64() 274 | fmt.Println(age) 275 | ``` 276 | 277 | 278 | ## Benchmark 279 | 280 | `go test -test.bench=".*"` 281 | 282 | ![Test][3] 283 | 284 | 可以看到,数组的解析要落后一些,原因在于对参数的检测。如果激进一点,可以将不检查参数,速度将提高至少4倍。 285 | 286 | [1]: https://github.com/toukii/jsnm "jsnm" 287 | [2]: http://7xku3c.com1.z0.glb.clouddn.com/jsnm-benchmark.png "jsnm-bench" 288 | [3]: http://7xku3c.com1.z0.glb.clouddn.com/benchmark-jsnm.png "jsnm-bench" -------------------------------------------------------------------------------- /jsnm_test.go: -------------------------------------------------------------------------------- 1 | package jsnm 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | //gsj "github.com/bitly/go-simplejson" 7 | "github.com/toukii/goutils" 8 | gsj "github.com/toukii/membership/pkg3/go-simplejson" 9 | "io/ioutil" 10 | "reflect" 11 | "testing" 12 | ) 13 | 14 | type User struct { 15 | Name string 16 | Age byte 17 | Friends map[string]*User 18 | Loc []string 19 | } 20 | 21 | func (u *User) ToJ() { 22 | bs, _ := json.MarshalIndent(u, "\t", "\t") 23 | _ = ioutil.WriteFile("test.json", bs, 0666) 24 | } 25 | 26 | func NewU(n string, a byte) *User { 27 | u := &User{Name: n, Age: a} 28 | u.Friends = make(map[string]*User) 29 | u.Friends[n] = nil 30 | u.Loc = []string{n, n + n} 31 | return u 32 | } 33 | 34 | func Mock() { 35 | u1 := NewU("One", 1) 36 | u2 := NewU("Two", 2) 37 | mp := make(map[string]interface{}) 38 | mp["objs"] = []*User{u1, u2} 39 | bs, _ := json.MarshalIndent(mp, "\t", "\t") 40 | _ = ioutil.WriteFile("test.json", bs, 0666) 41 | u2.Friends["One"] = u1 42 | u2.ToJ() 43 | } 44 | 45 | var ( 46 | bs = []byte(` 47 | [ 48 | { 49 | "1":[ 50 | { 51 | "Name": "foo", 52 | "Age": 10, 53 | "Friends": { 54 | "foo": { 55 | "11":[ 56 | { 57 | "Name": "foo11", 58 | "Age": 1011, 59 | "Friends": { 60 | "foo11": 61 | { 62 | "Name": "bar211", 63 | "Age": 1221, 64 | "Friends": { 65 | "bar21": null 66 | }, 67 | "Loc": [ 68 | "bar21", 69 | "barbar21" 70 | ] 71 | } 72 | }, 73 | "Loc": [ 74 | "foo2", 75 | "foofoo2" 76 | ] 77 | }, 78 | { 79 | "Name": "bar2", 80 | "Age": 122, 81 | "Friends": { 82 | "bar2": null 83 | }, 84 | "Loc": [ 85 | "bar2", 86 | "barbar2" 87 | ] 88 | } 89 | ] 90 | } 91 | }, 92 | "Loc": [ 93 | "foo", 94 | "foofoo" 95 | ] 96 | }, 97 | { 98 | "Name": "bar", 99 | "Age": 12, 100 | "Friends": { 101 | "bar": null 102 | }, 103 | "Loc": [ 104 | "bar", 105 | "barbar" 106 | ] 107 | } 108 | ] 109 | } 110 | ]`) 111 | 112 | arr_bs = []byte(`[ 113 | [ 114 | [ 115 | [ 116 | "a" 117 | ] 118 | ] 119 | ] 120 | ]`) 121 | 122 | jm *Jsnm 123 | jmGSJ *gsj.Json 124 | 125 | arrjm *Jsnm 126 | arrjmGSJ *gsj.Json 127 | ) 128 | 129 | func init() { 130 | fmt.Println("test...") 131 | Mock() 132 | jm = FileNameFmt("test.json") 133 | jmGSJ, _ = gsj.NewJson(goutils.ReadFile("test.json")) 134 | 135 | arrjm = BytesFmt(arr_bs) 136 | arrjmGSJ, _ = gsj.NewJson(arr_bs) 137 | } 138 | 139 | func assert(t *testing.T, get, want interface{}) bool { 140 | if get == nil && want == nil { 141 | return true 142 | } 143 | if !reflect.DeepEqual(want, get) { 144 | t.Errorf("want:%v, get: %v\n", want, get) 145 | return false 146 | } 147 | return true 148 | } 149 | 150 | func TestGet(t *testing.T) { 151 | cur := jm.Get("Friends") 152 | 153 | one_name := cur.Get("One").Get("Name") 154 | assert(t, one_name.RawData().String(), "One") 155 | 156 | one_name_X := jm.Get("Friends").Get("One").Get("Name").Get("X") 157 | if one_name_X != nil { 158 | t.Error(one_name_X, "should be nil.") 159 | } 160 | 161 | xx := one_name_X.Get("XX") 162 | if xx != nil { 163 | t.Error(xx, "should be nil.") 164 | } 165 | 166 | fon := jm.Get("Friends").Get("One").Get("Name") 167 | assert(t, fon.RawData().String(), "One") 168 | 169 | i64, _ := jm.Get("Age").RawData().Int64() 170 | assert(t, i64, int64(2)) 171 | 172 | i64 = jm.Get("Age").RawData().MustInt64() 173 | assert(t, i64, int64(2)) 174 | 175 | } 176 | 177 | func TestPathGet(t *testing.T) { 178 | path_get := jm.PathGet("Friends", "One", "Name").RawData().String() 179 | assert(t, path_get, "One") 180 | path_get = jm.PathGet("Friends", "One", "Name").RawData().String() 181 | assert(t, path_get, "One") 182 | } 183 | 184 | func TestNCGet(t *testing.T) { 185 | cur := jm.NCGet("Friends") 186 | 187 | one_name := cur.NCGet("One", "Name") 188 | assert(t, one_name.RawData().String(), "One") 189 | 190 | one_name_X := jm.NCGet("Friends", "One", "Name", "X") 191 | if one_name_X != nil { 192 | t.Error(one_name_X, "should be nil.") 193 | } 194 | 195 | xx := one_name_X.NCGet("XX") 196 | if xx != nil { 197 | t.Error(xx, "should be nil.") 198 | } 199 | 200 | fon := jm.NCGet("Friends").NCGet("One").NCGet("Name") 201 | assert(t, fon.RawData().String(), "One") 202 | 203 | i64, _ := jm.NCGet("Age").RawData().Int64() 204 | assert(t, i64, int64(2)) 205 | 206 | i64 = jm.NCGet("Age").RawData().MustInt64() 207 | assert(t, i64, int64(2)) 208 | 209 | } 210 | 211 | func TestArr(t *testing.T) { 212 | arr := jm.Get("Loc").Arr() 213 | name := arr[0].RawData().String() 214 | assert(t, name, "Two") 215 | assert(t, arr[1].RawData().String(), "TwoTwo") 216 | 217 | arr1 := jm.Get("Loc").ArrLoc(1).RawData().String() 218 | assert(t, arr1, "TwoTwo") 219 | } 220 | 221 | func TestArrRange(t *testing.T) { 222 | loc := jm.Get("Loc") 223 | names := make([]string, 0, 10) 224 | loc.Range(func(i int, ji *Jsnm) bool { 225 | names = append(names, fmt.Sprintf("%d-%s", i, ji.RawData().String())) 226 | return false 227 | }) 228 | 229 | assert(t, names, []string{"0-Two", "1-TwoTwo"}) 230 | } 231 | 232 | func TestArr_NCGet(t *testing.T) { 233 | arr := jm.NCGet("Loc").Arr() 234 | name := arr[0].RawData().String() 235 | assert(t, name, "Two") 236 | assert(t, arr[1].RawData().String(), "TwoTwo") 237 | 238 | arr1 := jm.NCGet("Loc").ArrLoc(1).RawData().String() 239 | assert(t, arr1, "TwoTwo") 240 | } 241 | 242 | func TestArrJson(t *testing.T) { 243 | us := []*User{NewU("foo", 10), NewU("bar", 12)} 244 | bs, err := json.MarshalIndent(us, "\t", "\t") 245 | if err != nil { 246 | t.Error(err) 247 | } 248 | // fmt.Println(string(bs)) 249 | jmb := BytesFmt(bs) 250 | name := jmb.ArrLoc(0).Get("Name").RawData().String() 251 | assert(t, name, "foo") 252 | 253 | ioutil.WriteFile("foo.json", bs, 0666) 254 | jmf := FileNameFmt("foo.json") 255 | namef := jmf.ArrLoc(0).Get("Name").RawData().String() 256 | assert(t, namef, "foo") 257 | foo := jmf.ArrGet("0", "Name").RawData().String() 258 | assert(t, foo, "foo") 259 | } 260 | 261 | func TestGsj(t *testing.T) { 262 | js, _ := gsj.NewJson(goutils.ReadFile("test.json")) 263 | name := js.GetPath("Friends", "One", "Name").MustString() 264 | assert(t, name, "One") 265 | } 266 | 267 | type U []*User 268 | type S struct { 269 | U 270 | } 271 | 272 | func TestLongArr(t *testing.T) { 273 | // // func TestBArr(b *testing.T) { 274 | // U := []*User{NewU("O", 1), NewU("T", 2)} 275 | // us := []S{S{U: U}, S{U: U}} 276 | // bs, _ := json.MarshalIndent(us, "\t", "\t") 277 | // fmt.Println(string(bs)) 278 | jmb := BytesFmt(bs) 279 | // rs := jmb.Arr()[0].Get("1").PathGet("Friends","foo","11")//.Arr()[0].Get("Loc").Arr()[0] 280 | //rs := jmb.Arr()[0].Get("1").ArrLoc(0).Get("Friends").Get("foo").Get("11").Arr()[0].Get("Loc").Arr()[0] 281 | //rs := jmb.ArrGet("0",`"1"`,"0","Friends","foo",`"11"`,"0","Loc","0") 282 | rs := jmb.ArrPath(0, "1", 0, "Friends", "foo", "11", 0, "Loc", 0).String() 283 | //()[0].Get("1").ArrLoc(0).Get("Friends").Get("foo").Get("11").Arr()[0].Get("Loc").Arr()[0] 284 | assert(t, rs, "foo2") 285 | // fmt.Println(ars) 286 | 287 | jmb2, err := gsj.NewJson(bs) 288 | if goutils.CheckErr(err) { 289 | t.Error(err) 290 | } 291 | rs2 := jmb2.GetIndex(0).Get("1").GetIndex(0).GetPath("Friends", "foo", "11").GetIndex(0).Get("Loc").GetIndex(0).MustString() 292 | assert(t, rs2, "foo2") 293 | } 294 | 295 | func TestDecode(t *testing.T) { 296 | 297 | js := FileNameFmt("test.json") 298 | t.Log(js.PathGet("Friends", "One", "Name").RawData().Decode()) 299 | t.Log(js.PathGet("Friends", "One", "Age").RawData().Decode()) 300 | 301 | } 302 | 303 | func Benchmark_001_short_Get_jsnm(b *testing.B) { 304 | for i := 0; i < b.N; i++ { 305 | _ = jm.Get("Friends") 306 | } 307 | } 308 | 309 | func Benchmark_001_short_NCGet(b *testing.B) { 310 | for i := 0; i < b.N; i++ { 311 | _ = jm.NCGet("Friends") 312 | } 313 | } 314 | 315 | func Benchmark_001_short_Get_gsj(b *testing.B) { 316 | for i := 0; i < b.N; i++ { 317 | _ = jmGSJ.Get("Friends") 318 | } 319 | } 320 | 321 | func Benchmark_002_PathGet_jsnm(b *testing.B) { 322 | for i := 0; i < b.N; i++ { 323 | _ = jm.PathGet("Friends", "One", "Name") 324 | } 325 | } 326 | 327 | func Benchmark_002_NCGet_path(b *testing.B) { 328 | for i := 0; i < b.N; i++ { 329 | _ = jm.NCGet("Friends", "One", "Name") 330 | } 331 | } 332 | 333 | func Benchmark_002_GetPath_gsj(b *testing.B) { 334 | for i := 0; i < b.N; i++ { 335 | _ = jmGSJ.GetPath("Friends", "One", "Name") 336 | } 337 | } 338 | 339 | func Benchmark_003_Get_jsnm(b *testing.B) { 340 | //b.StopTimer() 341 | //fmt.Println("PathGet:",jm.PathGet("Friends", "One", "Name").RawData().String()) 342 | //b.StartTimer() 343 | for i := 0; i < b.N; i++ { 344 | //_= jm.PathGet("Friends", "One", "Name").RawData().String() 345 | _ = jm.Get("Friends").Get("One").Get("Name") 346 | } 347 | } 348 | 349 | func Benchmark_003_NCGet(b *testing.B) { 350 | //b.StopTimer() 351 | //fmt.Println("NCGET-:",jm.NCGet("Friends").NCGet("One").NCGet("Name").RawData().String()) 352 | //b.StartTimer() 353 | for i := 0; i < b.N; i++ { 354 | _ = jm.NCGet("Friends").NCGet("One").NCGet("Name") 355 | } 356 | } 357 | 358 | func Benchmark_003_Get_gsj(b *testing.B) { 359 | //b.StopTimer() 360 | //fmt.Println("PathGet-gsj:",jmGSJ.GetPath("Friends", "One", "Name")) 361 | //b.StartTimer() 362 | for i := 0; i < b.N; i++ { 363 | _ = jmGSJ.Get("Friends").Get("One").Get("Name") 364 | } 365 | } 366 | 367 | func Benchmark_004_Arr_jsnm(b *testing.B) { 368 | //b.StopTimer() 369 | //fmt.Println("Arr:",arrjm.Arr()[0].Arr()[0].Arr()[0].Arr()[0]) 370 | //fmt.Println("Arr:",arrjm.ArrLoc(1).ArrLoc(0).ArrLoc(0).ArrLoc(0)) 371 | //fmt.Println("arrlocs:",arrjm.ArrLocs(0,0,0,0)) 372 | //b.StartTimer() 373 | for i := 0; i < b.N; i++ { 374 | //_= arrjm.Arr()[0].Arr()[0].Arr()[0].Arr()[0] 375 | _ = arrjm.ArrLoc(0).ArrLoc(0).ArrLoc(0).ArrLoc(0) 376 | //_=arrjm.ArrLocs(0,0,0,0) 377 | //_=arrjm.ArrLoc(0).Arr()[0].ArrLoc(0).Arr()[0] 378 | } 379 | } 380 | 381 | func Benchmark_004_Arr_gsj(b *testing.B) { 382 | //b.StopTimer() 383 | //fmt.Println("Arr-gsj:",arrjmGSJ.GetIndex(0).GetIndex(0).GetIndex(0).GetIndex(0)) 384 | //b.StartTimer() 385 | for i := 0; i < b.N; i++ { 386 | _ = arrjmGSJ.GetIndex(0).GetIndex(0).GetIndex(0).GetIndex(0) 387 | } 388 | } 389 | 390 | func Benchmark_005_jsnm(b *testing.B) { 391 | b.StopTimer() 392 | jmb := BytesFmt(bs) 393 | //rs:= jmb.Arr()[0].Get("1").ArrLoc(0).PathGet("Friends","foo","11").Arr()[0].Get("Loc").Arr()[0] 394 | //fmt.Println(rs) 395 | b.StartTimer() 396 | for i := 0; i < b.N; i++ { 397 | _ = jmb.Arr()[0].Get("1").ArrLoc(0).PathGet("Friends", "foo", "11").Arr()[0].Get("Loc").Arr()[0] 398 | //_=jmb.ArrGet("0",`"1"`,"0","Friends","foo",`"11"`,"0","Loc","0") 399 | //_= jmb.ArrPath(0,"1",0,"Friends","foo","11",0,"Loc",0) 400 | } 401 | } 402 | 403 | func Benchmark_005_gsj(b *testing.B) { 404 | b.StopTimer() 405 | jmb2, err := gsj.NewJson(bs) 406 | if goutils.CheckErr(err) { 407 | b.Error(err) 408 | } 409 | //rs := jmb2.GetIndex(0).Get("1").GetIndex(0).GetPath("Friends","foo","11").GetIndex(0).Get("Loc").GetIndex(0) 410 | //fmt.Print(rs) 411 | b.StartTimer() 412 | for i := 0; i < b.N; i++ { 413 | _ = jmb2.GetIndex(0).Get("1").GetIndex(0).GetPath("Friends", "foo", "11").GetIndex(0).Get("Loc").GetIndex(0) 414 | } 415 | } 416 | 417 | func Benchmark_006_new_jsnm(b *testing.B) { 418 | 419 | for i := 0; i < b.N; i++ { 420 | _ = &Jsnm{raw_data: bs} 421 | } 422 | } 423 | 424 | func Benchmark_006_new_gsj(b *testing.B) { 425 | 426 | for i := 0; i < b.N; i++ { 427 | _, _ = gsj.NewJson(bs) 428 | } 429 | } 430 | --------------------------------------------------------------------------------