From 91c2a64deeccd6bbc5db319fe5d92d000bb9ae5c Mon Sep 17 00:00:00 2001 From: imneov <31612033+imneov@users.noreply.github.com> Date: Wed, 2 Mar 2022 19:09:49 +0800 Subject: [PATCH] Feat/improver (#9) * feat: update readme * feat: remove github.com/tkeel-io/core/pkg/tql * feat: update gomod * fix: number parse * feat: embed collectjs & fix test * feat: fix parse bug * fix: unit test (GroupBy\MergeBy\KeyBy) * feat: refactor Node * feat: clean code * feat: update gomod * feat: clean code --- json/gjson/SYNTAX.md => SYNTAX.md | 0 TDTL.g4 | 2 +- collect_sort.go | 40 + collect_sort_test.go | 38 + collect_utils.go | 33 + collectjs.go | 253 +++ collectjs_patch.go | 91 + collectjs_test.go | 261 +++ collectjs_types.go | 288 +++ context.go | 7 +- context_json.go | 64 +- context_json_test.go | 20 - evaluator.go | 17 +- evaluator_test.go | 29 +- go.mod | 10 +- go.sum | 63 + json/gjson/logo.png | 0 json/gojsonq/.gitignore | 3 - json/gojsonq/.travis.yml | 25 - json/gojsonq/CONTRIBUTING.md | 13 - json/gojsonq/LICENSE.md | 21 - json/gojsonq/Makefile | 52 - json/gojsonq/README.md | 91 - json/gojsonq/decoder.go | 16 - json/gojsonq/decoder_test.go | 21 - json/gojsonq/doc.go | 19 - json/gojsonq/gojsonq.png | 0 json/gojsonq/helper.go | 266 --- json/gojsonq/helper_test.go | 477 ----- json/gojsonq/jetbrains-grayscale.png | 0 json/gojsonq/jsonq.go | 858 --------- json/gojsonq/jsonq_test.go | 1561 ----------------- json/gojsonq/jsonq_testdata_test.go | 167 -- json/gojsonq/option.go | 46 - json/gojsonq/option_test.go | 62 - json/gojsonq/query.go | 305 ---- json/gojsonq/query_test.go | 797 --------- json/gojsonq/result.go | 516 ------ json/gojsonq/result_test.go | 899 ---------- json/json_test.go | 121 -- .../.github/PULL_REQUEST_TEMPLATE.md | 13 - json/jsonparser/.gitignore | 10 - json/jsonparser/.travis.yml | 8 - json/types.go | 16 - parse.go | 48 + parser/tdtl_lexer.go | 60 +- parser_test.go | 16 +- {json => pkg/json}/gjson/LICENSE | 0 {json => pkg/json}/gjson/README.md | 0 pkg/json/gjson/SYNTAX.md | 227 +++ {json => pkg/json}/gjson/gjson.go | 0 {json => pkg/json}/gjson/gjson_gae.go | 0 {json => pkg/json}/gjson/gjson_ngae.go | 4 +- {json => pkg/json}/gjson/gjson_test.go | 4 +- pkg/json/gjson/logo.png | Bin 0 -> 15936 bytes {json => pkg/json}/jsonparser/Dockerfile | 0 {json => pkg/json}/jsonparser/LICENSE | 0 {json => pkg/json}/jsonparser/Makefile | 0 {json => pkg/json}/jsonparser/README.md | 0 .../json}/jsonparser/benchmark/benchmark.go | 0 .../benchmark/benchmark_codecgen.go | 0 .../benchmark/benchmark_easyjson.go | 0 .../jsonparser/benchmark/benchmark_ffjson.go | 0 .../benchmark/benchmark_large_payload_test.go | 0 .../benchmark_medium_payload_test.go | 0 .../benchmark/benchmark_small_payload_test.go | 0 {json => pkg/json}/jsonparser/bytes.go | 0 {json => pkg/json}/jsonparser/bytes_safe.go | 0 {json => pkg/json}/jsonparser/bytes_test.go | 0 {json => pkg/json}/jsonparser/bytes_unsafe.go | 1 - .../json}/jsonparser/bytes_unsafe_test.go | 0 {json => pkg/json}/jsonparser/escape.go | 0 {json => pkg/json}/jsonparser/escape_test.go | 0 {json => pkg/json}/jsonparser/parser.go | 65 +- .../json}/jsonparser/parser_error_test.go | 0 {json => pkg/json}/jsonparser/parser_test.go | 25 +- sample/parseTS.go | 8 +- tdtl_test.go | 55 +- test.go | 2 +- types.go | 207 +-- types_test.go | 40 + utils.go | 9 - 82 files changed, 1567 insertions(+), 6803 deletions(-) rename json/gjson/SYNTAX.md => SYNTAX.md (100%) create mode 100644 collect_sort.go create mode 100644 collect_sort_test.go create mode 100644 collect_utils.go create mode 100644 collectjs.go create mode 100644 collectjs_patch.go create mode 100644 collectjs_test.go create mode 100644 collectjs_types.go delete mode 100644 json/gjson/logo.png delete mode 100644 json/gojsonq/.gitignore delete mode 100644 json/gojsonq/.travis.yml delete mode 100644 json/gojsonq/CONTRIBUTING.md delete mode 100644 json/gojsonq/LICENSE.md delete mode 100644 json/gojsonq/Makefile delete mode 100644 json/gojsonq/README.md delete mode 100644 json/gojsonq/decoder.go delete mode 100644 json/gojsonq/decoder_test.go delete mode 100644 json/gojsonq/doc.go delete mode 100644 json/gojsonq/gojsonq.png delete mode 100644 json/gojsonq/helper.go delete mode 100644 json/gojsonq/helper_test.go delete mode 100644 json/gojsonq/jetbrains-grayscale.png delete mode 100644 json/gojsonq/jsonq.go delete mode 100644 json/gojsonq/jsonq_test.go delete mode 100644 json/gojsonq/jsonq_testdata_test.go delete mode 100644 json/gojsonq/option.go delete mode 100644 json/gojsonq/option_test.go delete mode 100644 json/gojsonq/query.go delete mode 100644 json/gojsonq/query_test.go delete mode 100644 json/gojsonq/result.go delete mode 100644 json/gojsonq/result_test.go delete mode 100644 json/json_test.go delete mode 100644 json/jsonparser/.github/PULL_REQUEST_TEMPLATE.md delete mode 100644 json/jsonparser/.gitignore delete mode 100644 json/jsonparser/.travis.yml delete mode 100644 json/types.go rename {json => pkg/json}/gjson/LICENSE (100%) rename {json => pkg/json}/gjson/README.md (100%) create mode 100644 pkg/json/gjson/SYNTAX.md rename {json => pkg/json}/gjson/gjson.go (100%) rename {json => pkg/json}/gjson/gjson_gae.go (100%) rename {json => pkg/json}/gjson/gjson_ngae.go (97%) rename {json => pkg/json}/gjson/gjson_test.go (99%) create mode 100644 pkg/json/gjson/logo.png rename {json => pkg/json}/jsonparser/Dockerfile (100%) rename {json => pkg/json}/jsonparser/LICENSE (100%) rename {json => pkg/json}/jsonparser/Makefile (100%) rename {json => pkg/json}/jsonparser/README.md (100%) rename {json => pkg/json}/jsonparser/benchmark/benchmark.go (100%) rename {json => pkg/json}/jsonparser/benchmark/benchmark_codecgen.go (100%) rename {json => pkg/json}/jsonparser/benchmark/benchmark_easyjson.go (100%) rename {json => pkg/json}/jsonparser/benchmark/benchmark_ffjson.go (100%) rename {json => pkg/json}/jsonparser/benchmark/benchmark_large_payload_test.go (100%) rename {json => pkg/json}/jsonparser/benchmark/benchmark_medium_payload_test.go (100%) rename {json => pkg/json}/jsonparser/benchmark/benchmark_small_payload_test.go (100%) rename {json => pkg/json}/jsonparser/bytes.go (100%) rename {json => pkg/json}/jsonparser/bytes_safe.go (100%) rename {json => pkg/json}/jsonparser/bytes_test.go (100%) rename {json => pkg/json}/jsonparser/bytes_unsafe.go (96%) rename {json => pkg/json}/jsonparser/bytes_unsafe_test.go (100%) rename {json => pkg/json}/jsonparser/escape.go (100%) rename {json => pkg/json}/jsonparser/escape_test.go (100%) rename {json => pkg/json}/jsonparser/parser.go (94%) rename {json => pkg/json}/jsonparser/parser_error_test.go (100%) rename {json => pkg/json}/jsonparser/parser_test.go (98%) diff --git a/json/gjson/SYNTAX.md b/SYNTAX.md similarity index 100% rename from json/gjson/SYNTAX.md rename to SYNTAX.md diff --git a/TDTL.g4 b/TDTL.g4 index 58ec52b..a168313 100644 --- a/TDTL.g4 +++ b/TDTL.g4 @@ -57,7 +57,7 @@ SUB: '-'; DOT: '.'; TRUE: T R U E; FALSE: F A L S E; -INDENTIFIER: [a-zA-Z0-9_#][a-zA-Z_\-#$@0-9]*; +INDENTIFIER: [a-zA-Z_#][a-zA-Z_\-#$@0-9]*; NUMBER: '0' | [1-9][0-9]* ; INTEGER: ('+' | '-')? NUMBER; FLOAT: ('+' | '-')? (NUMBER+ DOT NUMBER+ | NUMBER+ DOT | DOT NUMBER+); diff --git a/collect_sort.go b/collect_sort.go new file mode 100644 index 0000000..4164dcc --- /dev/null +++ b/collect_sort.go @@ -0,0 +1,40 @@ +package tdtl + +import "sort" + +// A couple of type definitions to make the units clear. +type earthMass float64 +type au float64 + +// By is the type of a "less" function that defines the ordering of its Planet arguments. +type By func(p1, p2 *Collect) bool + +// Sort is a method on the function type, By, that sorts the argument slice according to the function. +func (by By) Sort(collects []*Collect) { + ps := &collectSorter{ + collects: collects, + by: by, // The Sort method's receiver is the function (closure) that defines the sort order. + } + sort.Sort(ps) +} + +// planetSorter joins a By function and a slice of Planets to be sorted. +type collectSorter struct { + collects []*Collect + by func(p1, p2 *Collect) bool // Closure used in the Less method. +} + +// Len is part of sort.Interface. +func (s *collectSorter) Len() int { + return len(s.collects) +} + +// Swap is part of sort.Interface. +func (s *collectSorter) Swap(i, j int) { + s.collects[i], s.collects[j] = s.collects[j], s.collects[i] +} + +// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter. +func (s *collectSorter) Less(i, j int) bool { + return s.by(s.collects[i], s.collects[j]) +} diff --git a/collect_sort_test.go b/collect_sort_test.go new file mode 100644 index 0000000..d43908b --- /dev/null +++ b/collect_sort_test.go @@ -0,0 +1,38 @@ +package tdtl + +import ( + "bytes" + "testing" +) + +var planets = []*Collect{ + New(`["Venus", 0.815, 0.7]`), + New(`["Earth", 1.0, 1.0]`), + New(`["Mars", 0.107, 1.5]`), +} + +func TestSort(t *testing.T) { + + // Closures that order the Planet structure. + name := func(p1, p2 *Collect) bool { + return bytes.Compare(p1.Get("[0]").Raw(), p2.Get("[0]").Raw()) > 0 + } + mass := func(p1, p2 *Collect) bool { + return bytes.Compare(p1.Get("[1]").Raw(), p2.Get("[1]").Raw()) > 0 + } + distance := func(p1, p2 *Collect) bool { + return bytes.Compare(p1.Get("[2]").Raw(), p2.Get("[2]").Raw()) > 0 + } + + // Sort the planets by the various criteria. + By(name).Sort(planets) + t.Log("By name:", string(planets[0].Raw()), string(planets[1].Raw()), string(planets[2].Raw())) + + By(mass).Sort(planets) + t.Log("By mass:", string(planets[0].Raw()), string(planets[1].Raw()), string(planets[2].Raw())) + + By(distance).Sort(planets) + t.Log("By distance:", string(planets[0].Raw()), string(planets[1].Raw()), string(planets[2].Raw())) + + // Output: +} diff --git a/collect_utils.go b/collect_utils.go new file mode 100644 index 0000000..26d2dd7 --- /dev/null +++ b/collect_utils.go @@ -0,0 +1,33 @@ +package tdtl + +import ( + "strings" +) + +func Byte(raw string) []byte { + return []byte(raw) +} + +func path2JSONPARSER(path string) []string { + keys := []string{} + if len(path) > 0 { + if path[0] == '"' && path[len(path)-1] == '"' { + return []string{path[1 : len(path)-1]} + } + path = strings.Replace(path, "[", ".[", -1) + keys = strings.Split(path, ".") + } + if len(keys) > 0 && keys[0] == "" { + return keys[1:] + } + return keys +} + +func path2GJSON(path string) string { + path = strings.Replace(path, "[", ".", -1) + path = strings.Replace(path, "]", "", -1) + if len(path) > 0 && path[0] == '.' { + return path[1:] + } + return path +} diff --git a/collectjs.go b/collectjs.go new file mode 100644 index 0000000..d8bd51d --- /dev/null +++ b/collectjs.go @@ -0,0 +1,253 @@ +package tdtl + +import ( + "errors" + "fmt" + "strings" + + "github.com/tkeel-io/tdtl/pkg/json/jsonparser" +) + +var EmptyBytes = []byte("") + +type Collect = JSONNode + +func New(raw interface{}) *Collect { + switch raw := raw.(type) { + case string: + return newCollect(Byte(raw)) + case []byte: + return newCollect(raw) + case Result: + return newCollectFromJsonResult(raw) + } + return UNDEFINED_RESULT +} + +func newCollect(data []byte) *Collect { + collect := &Collect{} + value := make([]byte, len(data)) + copy(value, data) + collect.path = "" + collect.value = value + if _, jtype, _, err := jsonparser.Get(data); err == nil { + collect.datatype = datetype(jtype) + } else { + collect.err = err + } + return collect +} + +func newCollectFromJsonResult(ret Result) *Collect { + collect := &Collect{} + collect.path = "" + collect.datatype = datetype(ret) + if collect.datatype == String { + collect.value = []byte(ret.Str) + } else { + collect.value = []byte(ret.Raw) + } + + return collect +} + +func newCollectFromJsonparserResult(dataType jsonparser.ValueType, value []byte) *Collect { + collect := &Collect{} + collect.path = "" + collect.value = []byte(value) + collect.datatype = datetype(dataType) + return collect +} + +// GetError returns collect error. +func (cc *Collect) GetError() error { + return cc.err +} + +func (cc *Collect) Node() Node { + return cc.To(cc.datatype) +} + +func (cc *Collect) Get(path ...string) *Collect { + absPath := strings.Join(path, ".") + if absPath == "" { + return cc + } + ret := get(cc.value, absPath) + return ret +} + +func (cc *Collect) Set(path string, value Node) { + cc.value, cc.err = set(cc.value, path, value.Raw()) +} + +func (cc *Collect) Append(path string, value Node) { + cc.value, cc.err = add(cc.value, path, value.Raw()) +} + +func (cc *Collect) Del(path ...string) { + cc.value = del(cc.value, path...) +} + +func (cc *Collect) Copy() *JSONNode { + return newCollect(cc.value) +} + +func (cc *Collect) Foreach(fn ForeachHandle) { + cc.value = forEach(cc.value, cc.datatype, fn) +} + +func (cc *Collect) Map(handle MapHandle) { + ret := cc.Copy() + cc.Foreach(func(key []byte, value *Collect) { + newValue := handle(key, value) + ret.Set(string(key), newValue) + }) + cc.value, cc.datatype = ret.value, ret.datatype +} + +func (cc *Collect) GroupBy(path string) *Collect { + if cc.datatype != Array { + cc.err = fmt.Errorf("datatype is not array") + return cc + } + + ret := New("{}") + cc.Foreach(func(key []byte, value *Collect) { + keyValue := get(value.Raw(), path).String() + if len(keyValue) == 0 { + return + } + keyValue = strings.Replace(keyValue, ".", "_", -1) + ret.Append(keyValue, value) + }) + return ret +} + +func (c *Collect) MergeBy(paths ...string) *Collect { + if c.datatype != Array { + c.err = fmt.Errorf("MergeBy: datatype is not array") + return c + } + + var err error + ret := New("{}") + c.Foreach(func(key []byte, value *Collect) { + keys := make([]string, 0, len(paths)) + for _, path := range paths { + keyValueRaw := value.Get(path).String() + if len(keyValueRaw) == 0 { + break + } + keys = append(keys, keyValueRaw) + } + + if len(keys) == 0 { + return + } + + keyValue := strings.Join(keys, "+") + keyValue = strings.Replace(keyValue, ".", "_", -1) + + nv := ret.Get(keyValue) + nv = nv.Merge(value) + if nv.err != nil { + ret.err = err + } + ret.Set(keyValue, nv) + }) + return ret +} + +func (cc *Collect) SortBy(fn func(p1 *Collect, p2 *Collect) bool) { + if cc.datatype != Array && cc.datatype != Object { + cc.err = errors.New("SortBy:datatype is not array or object") + return + } + carr := make([]*Collect, 0) + cc.Foreach(func(key []byte, value *Collect) { + carr = append(carr, value) + }) + By(fn).Sort(carr) + + ret := New("[]") + for _, c := range carr { + ret.Append("", c) + } + cc.value = ret.value + cc.datatype = ret.datatype +} + +func (c *Collect) KeyBy(path string) *Collect { + if c.datatype != Array { + c.err = errors.New("KeyBy:datatype is not array") + } + + ret := New("{}") + c.Foreach(func(key []byte, value *Collect) { + keyValue := value.Get(path) + ret.Set(keyValue.String(), value) + }) + + return ret +} + +func (cc *Collect) Merge(mc *Collect) *Collect { + if cc.datatype != Object && mc.datatype != Object { + cc.err = errors.New("datatype is not object") + return cc + } + if cc.datatype == Null { + return mc + } + + mc.Foreach(func(key []byte, value *Collect) { + cc.Set(string(key), value) + }) + + return cc +} + +// +//func (c *Collect) Sort( path string) ([]byte, error) { +// if c.datatype != Array { +// c.err = errors.New("SortBy:datatype is not array") +// return nil, errors.New("datatype is not array") +// } +// +// var err error +// ret := []byte("[]") +// c.Foreach(func(key []byte, value *Collect) { +// keyValue := get(value.Raw(), path).Raw() +// if keyValue[0] == '"' && keyValue[len(keyValue)-1] == '"' { +// keyValue = keyValue[1 : len(keyValue)-1] +// } +// if ret, err = jsonparser.Append(ret, value.Raw(), string(keyValue)); nil != err { +// c.err = err +// } +// }) +// +// return ret, c.err +//} + +func Combine(cKey *Collect, cValue *Collect) ([]byte, error) { + if cKey.datatype != Array { + return nil, errors.New("datatype is not array") + } else if cValue.datatype != Array { + return nil, errors.New("datatype is not array") + } + + var ( + idx int + err error + ret = []byte("{}") + ) + + cKey.Foreach(func(key []byte, value *Collect) { + if ret, err = jsonparser.Set(ret, get(cValue.value, fmt.Sprintf("[%d]", idx)).Raw(), value.String()); nil != err { + cKey.err = err + } + idx++ + }) + return ret, cKey.err +} diff --git a/collectjs_patch.go b/collectjs_patch.go new file mode 100644 index 0000000..2ebad55 --- /dev/null +++ b/collectjs_patch.go @@ -0,0 +1,91 @@ +package tdtl + +import ( + "fmt" + "github.com/tkeel-io/tdtl/pkg/json/gjson" + "github.com/tkeel-io/tdtl/pkg/json/jsonparser" + "strings" +) + +func _gjson2JsonNode(ret gjson.Result) Node { + switch ret.Type { + case gjson.True: + return BoolNode(true) + case gjson.False: + return BoolNode(false) + case gjson.Number: // return Float\Int + r := StringNode(ret.Raw) + if strings.Index(r.String(), ".") == -1 { + return r.To(Int) + } + return r.To(Float) + case gjson.String: + return StringNode(ret.Str) + case gjson.JSON: + return JSONNode{ + value: []byte(ret.Raw), + datatype: JSON, + } + } + return NULL_RESULT +} + +func get(raw []byte, path string) *Collect { + path = path2GJSON(path) + ret := gjson.GetBytes(raw, path) + return New(ret) +} +// +//func Get(raw []byte, path string) []byte { +// //keys := path2JSONPARSER(path) +// // +// //if value, dataType, _, err := jsonparser.Get(raw, keys...); err == nil { +// // return warpValue(dataType, value) +// //} else { +// // +// //} +// path = path2GJSON(path) +// ret := gjson.GetBytes(raw, path) +// ee := gjson.Get(ret.String(), "") +// fmt.Println(ee, ee.Type) +// return []byte(ret.String()) +//} + + +func set(raw []byte, path string, value []byte) ([]byte, error) { + keys := path2JSONPARSER(path) + return jsonparser.Set(raw, value, keys...) +} +func add(raw []byte, path string, value []byte) ([]byte, error) { + keys := path2JSONPARSER(path) + return jsonparser.Append(raw, value, keys...) +} + +func del(raw []byte, path ...string) []byte { + for _, v := range path { + keys := path2JSONPARSER(v) + raw = jsonparser.Delete(raw, keys...) + } + return raw +} + +func forEach(raw []byte, datatype Type, fn func(key []byte, value *Collect)) []byte { + // dispose object. + if datatype == Object { + jsonparser.ObjectEach(raw, func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error { + fn(key, newCollectFromJsonparserResult(dataType, value)) + return nil + }) + } + + // dispose array. + if datatype == Array { + idx := 0 + jsonparser.ArrayEach(raw, func(value []byte, dataType jsonparser.ValueType, offset int) error { + fn(Byte(fmt.Sprintf("[%d]", idx)), newCollectFromJsonparserResult(dataType, value)) + idx++ + return nil + }) + } + return raw +} diff --git a/collectjs_test.go b/collectjs_test.go new file mode 100644 index 0000000..1544f8f --- /dev/null +++ b/collectjs_test.go @@ -0,0 +1,261 @@ +package tdtl + +import ( + "bytes" + "fmt" + "reflect" + "testing" +) + +var raw = Byte(`{"cpu":1,"mem": ["lo0", "eth1", "eth2"],"a":[{"v":0},{"v":1},{"v":2}],"b":[{"v":{"cv":1}},{"v":{"cv":2}},{"v":{"cv":3}}],"where": 10,"metadata": {"name": "Light1", "price": 11.05}}`) +var rawArray = Byte(`[{"v":{"cv":1}},{"v":{"cv":2}},{"v":{"cv":3}}]`) +var rawEmptyArray = Byte(`[]`) + +func TestCollect_Get(t *testing.T) { + tests := []struct { + name string + raw []byte + path string + want interface{} + }{ + {"1", raw, "", string(raw)}, + {"2", raw, "cpu", "1"}, + {"2.1", raw, "mem[0]", "\"lo0\""}, + {"3", raw, "a", `[{"v":0},{"v":1},{"v":2}]`}, + {"4", raw, "a[0]", `{"v":0}`}, + {"5", raw, "a[1]", `{"v":1}`}, + {"6", raw, "a[2]", `{"v":2}`}, + {"7", raw, "a[#]", `3`}, // count + {"8", raw, "a[#].v", `[0,1,2]`}, + {"9", raw, "b[0].v", `{"cv":1}`}, + {"10", raw, "b[1].v", `{"cv":2}`}, + {"11", raw, "b[2].v", `{"cv":3}`}, + {"12", raw, "b[#].v", `[{"cv":1},{"cv":2},{"cv":3}]`}, + {"13", raw, "b[1].v.cv", `2`}, + {"14", raw, "b[#].v.cv", `[1,2,3]`}, + {"14", rawArray, "[0]", `{"v":{"cv":1}}`}, + } + for _, tt := range tests { + cc := newCollect(tt.raw) + t.Run(tt.name, func(t *testing.T) { + if got := cc.Get(tt.path); !reflect.DeepEqual(string(got.Raw()), tt.want) { + t.Errorf("Get() = %v, want %v", string(got.Raw()), tt.want) + } + }) + } +} + +func TestCollect_Set(t *testing.T) { + tests := []struct { + name string + path string + value *Collect + want interface{} + }{ + //{"2", "cpu", StringNode("2"), `{"cpu":2,"mem": ["lo0", "eth1", "eth2"],"a":[{"v":0},{"v":1},{"v":2}],"b":[{"v":{"cv":1}},{"v":{"cv":2}},{"v":{"cv":3}}],"where": 10,"metadata": {"name": "Light1", "price": 11.05}}`}, + {"3", "a", New(`{"v":0}`), `{"cpu":1,"mem": ["lo0", "eth1", "eth2"],"a":{"v":0},"b":[{"v":{"cv":1}},{"v":{"cv":2}},{"v":{"cv":3}}],"where": 10,"metadata": {"name": "Light1", "price": 11.05}}`}, + {"4", "a[0]", New(`0`), `{"cpu":1,"mem": ["lo0", "eth1", "eth2"],"a":[0,{"v":1},{"v":2}],"b":[{"v":{"cv":1}},{"v":{"cv":2}},{"v":{"cv":3}}],"where": 10,"metadata": {"name": "Light1", "price": 11.05}}`}, + {"5", "a[0].v", New(`{"v":0}`), `{"cpu":1,"mem": ["lo0", "eth1", "eth2"],"a":[{"v":{"v":0}},{"v":1},{"v":2}],"b":[{"v":{"cv":1}},{"v":{"cv":2}},{"v":{"cv":3}}],"where": 10,"metadata": {"name": "Light1", "price": 11.05}}`}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cc := newCollect(raw) + cc.Set(tt.path, tt.value) + if got := cc.Raw(); !reflect.DeepEqual(string(got), tt.want) { + t.Errorf("\nGet() = %v\nWant() = %v", string(got), tt.want) + } + }) + } +} + +func TestCollect_Append(t *testing.T) { + tests := []struct { + name string + raw []byte + path string + value *Collect + wantErr interface{} + want interface{} + }{ + {"1", raw, "cpu", New("2"), `Unknown value type`, raw}, + {"2", raw, "mem", New("2"), nil, `{"cpu":1,"mem": ["lo0", "eth1", "eth2",2],"a":[{"v":0},{"v":1},{"v":2}],"b":[{"v":{"cv":1}},{"v":{"cv":2}},{"v":{"cv":3}}],"where": 10,"metadata": {"name": "Light1", "price": 11.05}}`}, + {"3", raw, "a", New(`{"v":11}`), nil, `{"cpu":1,"mem": ["lo0", "eth1", "eth2"],"a":[{"v":0},{"v":1},{"v":2},{"v":11}],"b":[{"v":{"cv":1}},{"v":{"cv":2}},{"v":{"cv":3}}],"where": 10,"metadata": {"name": "Light1", "price": 11.05}}`}, + {"4", raw, "a[0]", New(`0`), `Unknown value type`, raw}, + {"5", raw, "a[0].v", New(`{"v":0}`), `Unknown value type`, raw}, + {"5", rawEmptyArray, "", New(`{"v":0}`), nil, `[{"v":0}]`}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cc := newCollect(tt.raw) + cc.Append(tt.path, tt.value) + if tt.wantErr != nil { + if !reflect.DeepEqual(cc.Error().Error(), tt.wantErr) { + t.Errorf("Get(2) = %v, want %v", cc.Error(), tt.wantErr) + } + } else { + if cc.Error() != nil { + t.Errorf("Get(1) = %v, want %v", cc.Error(), tt.wantErr) + } + if !reflect.DeepEqual(cc.String(), tt.want) { + t.Errorf("Get() = %v, want %v", cc.String(), tt.want) + } + } + }) + } +} + +func TestCollect_Del(t *testing.T) { + tests := []struct { + name string + path []string + want interface{} + }{ + {"2", []string{"cpu"}, `{"mem": ["lo0", "eth1", "eth2"],"a":[{"v":0},{"v":1},{"v":2}],"b":[{"v":{"cv":1}},{"v":{"cv":2}},{"v":{"cv":3}}],"where": 10,"metadata": {"name": "Light1", "price": 11.05}}`}, + {"3", []string{"a", "b", "cpu"}, `{"mem": ["lo0", "eth1", "eth2"],"where": 10,"metadata": {"name": "Light1", "price": 11.05}}`}, + {"4", []string{"a[0]"}, `{"cpu":1,"mem": ["lo0", "eth1", "eth2"],"a":[{"v":1},{"v":2}],"b":[{"v":{"cv":1}},{"v":{"cv":2}},{"v":{"cv":3}}],"where": 10,"metadata": {"name": "Light1", "price": 11.05}}`}, + {"5", []string{"a[0].v"}, `{"cpu":1,"mem": ["lo0", "eth1", "eth2"],"a":[{},{"v":1},{"v":2}],"b":[{"v":{"cv":1}},{"v":{"cv":2}},{"v":{"cv":3}}],"where": 10,"metadata": {"name": "Light1", "price": 11.05}}`}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cc := newCollect(raw) + cc.Del(tt.path...) + if got := cc.Raw(); !reflect.DeepEqual(string(got), tt.want) { + t.Errorf("Get() = %v, want %v", string(got), tt.want) + } + }) + } +} + +func Example_Combine() { + collection := New(`["name", "number"]`) + collection2 := New(`["Mohamed Salah", 11]`) + combine, _ := Combine(collection, collection2) + fmt.Println(string(combine)) + + // Output: + // {"name":"Mohamed Salah","number":11} +} + +var rawGroup = Byte(`[ + {"count": "1","product": "Chair","manufacturer": "IKEA"}, + {"sum": "10","product": "Desk","manufacturer": "IKEA"}, + {"product": "Chair","manufacturer": "Herman Miller"} +]`) + +func Example_GroupBy() { + cc := New(rawGroup) + ret := cc.GroupBy("manufacturer") //node_memory_MemTotal_bytes + fmt.Println(ret.String(), ret.Error()) + + // Output: + // {"IKEA":[{"count": "1","product": "Chair","manufacturer": "IKEA"},{"sum": "10","product": "Desk","manufacturer": "IKEA"}],"Herman Miller":[{"product": "Chair","manufacturer": "Herman Miller"}]} +} + +func Example_MergeBy() { + cc := New(rawGroup) + ret := cc.MergeBy("product", "manufacturer") //node_memory_MemTotal_bytes + fmt.Println(ret.String(), ret.Error()) + + // Output: + // {"Chair+IKEA":{"count": "1","product": "Chair","manufacturer": "IKEA"},"Desk+IKEA":{"sum": "10","product": "Desk","manufacturer": "IKEA"},"Chair+Herman Miller":{"product": "Chair","manufacturer": "Herman Miller"}} +} + +func Example_KeyBy() { + cc := New(rawGroup) + ret := cc.KeyBy("manufacturer") //node_memory_MemTotal_bytes + fmt.Println(ret.String(), ret.Error()) + + // Output: + // {"IKEA":{"sum": "10","product": "Desk","manufacturer": "IKEA"},"Herman Miller":{"product": "Chair","manufacturer": "Herman Miller"}} +} + +func Example_Merge() { + var rawObject1 = New(`{"id": 1,"price": 29}`) + var rawObject2 = New(`{"price": "229","discount": false}`) + ret := rawObject1.Merge(rawObject2) + fmt.Println(rawObject1.String(), rawObject1.Error()) + fmt.Println(ret.String(), ret.Error()) + + var rawObject3 = rawObject1.Get("__not_exist") + ret = rawObject3.Merge(rawObject2) + fmt.Println(rawObject3.Error()) + fmt.Println(ret.String(), ret.Error()) + + var rawObject4 = rawObject1.Get("__not_exist") + ret = rawObject2.Merge(rawObject4) + fmt.Println(rawObject2.String(), rawObject2.Error()) + fmt.Println(ret.String(), ret.Error()) + + // Output: + //{"id": 1,"price": "229","discount":false} + //{"id": 1,"price": "229","discount":false} + // + //{"price": "229","discount": false} + //{"price": "229","discount": false} + //{"price": "229","discount": false} +} + +func Example_Demo() { + collection1 := New(`{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"node_memory_MemAvailable_bytes","instance":"192.168.14.102:9100","job":"linux"},"value":[1620999810.899,"6519189504"]},{"metric":{"__name__":"node_memory_MemAvailable_bytes","instance":"192.168.14.146:9100","job":"linux"},"value":[1620999810.899,"1787977728"]},{"metric":{"__name__":"node_memory_MemAvailable_bytes","instance":"192.168.21.163:9100","job":"linux"},"value":[1620999810.899,"5775802368"]},{"metric":{"__name__":"node_memory_MemAvailable_bytes","instance":"192.168.21.174:9100","job":"linux"},"value":[1620999810.899,"19626115072"]},{"metric":{"__name__":"node_memory_MemAvailable_bytes","instance":"localhost:9100","job":"linux"},"value":[1620999810.899,"3252543488"]},{"metric":{"__name__":"node_memory_MemTotal_bytes","instance":"192.168.14.102:9100","job":"linux"},"value":[1620999810.899,"8203091968"]},{"metric":{"__name__":"node_memory_MemTotal_bytes","instance":"192.168.14.146:9100","job":"linux"},"value":[1620999810.899,"8203091968"]},{"metric":{"__name__":"node_memory_MemTotal_bytes","instance":"192.168.21.163:9100","job":"linux"},"value":[1620999810.899,"8202657792"]},{"metric":{"__name__":"node_memory_MemTotal_bytes","instance":"192.168.21.174:9100","job":"linux"},"value":[1620999810.899,"25112969216"]},{"metric":{"__name__":"node_memory_MemTotal_bytes","instance":"localhost:9100","job":"linux"},"value":[1620999810.899,"3972988928"]}]}}`) + result := collection1.Get("data.result") + result.Map(func(key []byte, c *Collect) Node { + val := New("{}") + val.Set("timestamp", c.Get("value[0]")) + val.Set("value", c.Get("value[1]")) + ret := New("{}") + ret.Set(c.Get("metric.__name__").String(), val) + ret.Set("instance", c.Get("metric.instance")) + return ret + }) + ret := result.GroupBy("instance") //node_memory_MemTotal_bytes + + metricValue := func(p1, p2 *Collect) bool { + return bytes.Compare(p1.Get("[0]").Raw(), p2.Get("[0]").Raw()) > 0 + } + + ret.SortBy(metricValue) + fmt.Println(string(result.Raw())) + + // Output: + // [{"node_memory_MemAvailable_bytes":{"timestamp":1620999810.899,"value":"6519189504"},"instance":"192.168.14.102:9100"},{"node_memory_MemAvailable_bytes":{"timestamp":1620999810.899,"value":"1787977728"},"instance":"192.168.14.146:9100"},{"node_memory_MemAvailable_bytes":{"timestamp":1620999810.899,"value":"5775802368"},"instance":"192.168.21.163:9100"},{"node_memory_MemAvailable_bytes":{"timestamp":1620999810.899,"value":"19626115072"},"instance":"192.168.21.174:9100"},{"node_memory_MemAvailable_bytes":{"timestamp":1620999810.899,"value":"3252543488"},"instance":"localhost:9100"},{"node_memory_MemTotal_bytes":{"timestamp":1620999810.899,"value":"8203091968"},"instance":"192.168.14.102:9100"},{"node_memory_MemTotal_bytes":{"timestamp":1620999810.899,"value":"8203091968"},"instance":"192.168.14.146:9100"},{"node_memory_MemTotal_bytes":{"timestamp":1620999810.899,"value":"8202657792"},"instance":"192.168.21.163:9100"},{"node_memory_MemTotal_bytes":{"timestamp":1620999810.899,"value":"25112969216"},"instance":"192.168.21.174:9100"},{"node_memory_MemTotal_bytes":{"timestamp":1620999810.899,"value":"3972988928"},"instance":"localhost:9100"}] +} + +func Example_Demo2() { + collection1 := New(`{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"node_memory_MemAvailable_bytes","instance":"192.168.14.102:9100","job":"linux"},"value":[1620999810.899,"1"]},{"metric":{"__name__":"node_memory_MemAvailable_bytes","instance":"192.168.14.146:9100","job":"linux"},"value":[1620999810.899,"3"]},{"metric":{"__name__":"node_memory_MemAvailable_bytes","instance":"192.168.21.163:9100","job":"linux"},"value":[1620999810.899,"2"]},{"metric":{"__name__":"node_memory_MemAvailable_bytes","instance":"192.168.21.174:9100","job":"linux"},"value":[1620999810.899,"19626115072"]},{"metric":{"__name__":"node_memory_MemAvailable_bytes","instance":"localhost:9100","job":"linux"},"value":[1620999810.899,"3252543488"]},{"metric":{"__name__":"node_memory_MemTotal_bytes","instance":"192.168.14.102:9100","job":"linux"},"value":[1620999810.899,"8203091968"]},{"metric":{"__name__":"node_memory_MemTotal_bytes","instance":"192.168.14.146:9100","job":"linux"},"value":[1620999810.899,"8203091968"]},{"metric":{"__name__":"node_memory_MemTotal_bytes","instance":"192.168.21.163:9100","job":"linux"},"value":[1620999810.899,"8202657792"]},{"metric":{"__name__":"node_memory_MemTotal_bytes","instance":"192.168.21.174:9100","job":"linux"},"value":[1620999810.899,"25112969216"]},{"metric":{"__name__":"node_memory_MemTotal_bytes","instance":"localhost:9100","job":"linux"},"value":[1620999810.899,"3972988928"]}]}}`) + result := collection1.Get("data.result") + result.Map(func(key []byte, c *Collect) Node { + val := New("{}") + val.Set("timestamp", c.Get("value[0]")) + val.Set("value", c.Get("value[1]")) + ret := New("{}") + ret.Set(c.Get("metric.__name__").String(), val) + ret.Set("instance", c.Get("metric.instance")) + return ret + }) + + sorted := result.MergeBy("instance") //node_memory_MemTotal_bytes + + MemAvailable := func(p1, p2 *Collect) bool { + return bytes.Compare(p1.Get("node_memory_MemAvailable_bytes.value").Raw(), p2.Get("node_memory_MemAvailable_bytes.value").Raw()) > 0 + } + MemTotal := func(p1, p2 *Collect) bool { + return bytes.Compare(p1.Get("node_memory_MemTotal_bytes.value").Raw(), p2.Get("node_memory_MemTotal_bytes.value").Raw()) < 0 + } + + sorted.SortBy(MemTotal) + sorted.SortBy(MemAvailable) + fmt.Println(string(sorted.Raw())) + + // Output: + // [{"node_memory_MemAvailable_bytes":{"timestamp":1620999810.899,"value":"3252543488"},"instance":"localhost:9100","node_memory_MemTotal_bytes":{"timestamp":1620999810.899,"value":"3972988928"}},{"node_memory_MemAvailable_bytes":{"timestamp":1620999810.899,"value":"3"},"instance":"192.168.14.146:9100","node_memory_MemTotal_bytes":{"timestamp":1620999810.899,"value":"8203091968"}},{"node_memory_MemAvailable_bytes":{"timestamp":1620999810.899,"value":"2"},"instance":"192.168.21.163:9100","node_memory_MemTotal_bytes":{"timestamp":1620999810.899,"value":"8202657792"}},{"node_memory_MemAvailable_bytes":{"timestamp":1620999810.899,"value":"19626115072"},"instance":"192.168.21.174:9100","node_memory_MemTotal_bytes":{"timestamp":1620999810.899,"value":"25112969216"}},{"node_memory_MemAvailable_bytes":{"timestamp":1620999810.899,"value":"1"},"instance":"192.168.14.102:9100","node_memory_MemTotal_bytes":{"timestamp":1620999810.899,"value":"8203091968"}}] +} + +//func Example_AAA() { +// +// fmt.Println(gjson.Get(string(rawArray), "0")) +// fmt.Println(gjson.Get(string(`["Mohamed Salah", 11]`), "0")) +// +// // Output: +// // {"v":{"cv":1}} +// //Mohamed Salah +//} diff --git a/collectjs_types.go b/collectjs_types.go new file mode 100644 index 0000000..e1c2bff --- /dev/null +++ b/collectjs_types.go @@ -0,0 +1,288 @@ +/* +Copyright 2021 The tKeel Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package tdtl + +import ( + "fmt" + "github.com/tkeel-io/tdtl/pkg/json/gjson" + "github.com/tkeel-io/tdtl/pkg/json/jsonparser" + "strconv" + "strings" +) + +type MapHandle func(key []byte, value *Collect) Node +type ForeachHandle func(key []byte, value *Collect) +type SortHandle func(p1 *Collect, p2 *Collect) bool + +var ( + UNDEFINED_RESULT = &JSONNode{datatype: Undefined} + NULL_RESULT = &JSONNode{datatype: Undefined} +) + +// result represents a json value that is returned from Get(). +type Result = gjson.Result + +// Type node type +type Type int + +const ( + // Undefine is Not a value + // This isn't explicitly representable in JSON except by omitting the value. + Undefined Type = iota + // Null is a null json value + Null + // Bool is a json boolean + Bool + // Number is json number, include Int and Float + Number + // Int is json number, a discrete Int + Int + // Float is json number + Float + // String is a json string + String + // JSON is a raw block of JSON + JSON + // Object is a type of JSON + Object + // Array is a type of JSON + Array +) + +// String returns a string representation of the type. +func (t Type) String() string { + switch t { + default: + return "Undefined" + case Null: + return "Null" + case Bool: + return "Bool" + case Int: + return "Int" + case Float: + return "Float" + case String: + return "String" + case JSON: + return "JSON" + } +} + +var jsonparserDatetype = map[jsonparser.ValueType]Type{ + jsonparser.NotExist: Null, + jsonparser.String: String, + jsonparser.Number: Number, + jsonparser.Object: Object, + jsonparser.Array: Array, + jsonparser.Boolean: Bool, + jsonparser.Null: Null, + jsonparser.Unknown: Null, +} + +var gjsonDatetype = map[gjson.Type]Type{ + gjson.Null: Null, + gjson.Number: Number, + gjson.String: String, + gjson.True: Bool, + gjson.False: Bool, + gjson.JSON: JSON, +} + +func datetype(data interface{}) Type { + switch data := data.(type) { + case jsonparser.ValueType: + return jsonparserDatetype[data] + case gjson.Result: + typ := gjsonDatetype[data.Type] + if typ == JSON { + if data.IsArray() { + return Array + } + if data.IsObject() { + return Object + } + } + return typ + } + return Null +} + +// True is a json true boolean + +// JSON is a raw block of JSON + +//Node interface +type Node interface { + Type() Type + To(Type) Node + Raw() []byte + String() string + Error() error +} + +type BoolNode bool + +func (r BoolNode) Type() Type { return Bool } +func (r BoolNode) Error() error { return nil } +func (r BoolNode) To(typ Type) Node { + switch typ { + case Bool: + return r + case String: + return StringNode(fmt.Sprintf("%t", r)) + } + return UNDEFINED_RESULT +} +func (r BoolNode) Raw() []byte { + return []byte(r.String()) +} +func (r BoolNode) String() string { + return fmt.Sprintf("%t", r) +} + +type IntNode int64 + +func (r IntNode) Type() Type { return Int } +func (r IntNode) Error() error { return nil } +func (r IntNode) To(typ Type) Node { + switch typ { + case Number, Int: + return r + case Float: + return FloatNode(r) + case String: + return StringNode(fmt.Sprintf("%d", r)) + } + return UNDEFINED_RESULT +} +func (r IntNode) Raw() []byte { + return []byte(r.String()) +} +func (r IntNode) String() string { + return fmt.Sprintf("%d", r) +} + +type FloatNode float64 + +func (r FloatNode) Type() Type { return Float } +func (r FloatNode) Error() error { return nil } +func (r FloatNode) To(typ Type) Node { + switch typ { + case Number, Float: + return r + case Int: + return IntNode(r) + case String: + return StringNode(fmt.Sprintf("%f", r)) + } + return UNDEFINED_RESULT +} +func (r FloatNode) Raw() []byte { + return []byte(r.String()) +} +func (r FloatNode) String() string { + return fmt.Sprintf("%.6f", r) +} + +type StringNode string + +func (r StringNode) Type() Type { return String } +func (r StringNode) Error() error { return nil } +func (r StringNode) To(typ Type) Node { + switch typ { + case String: + return r + case Bool: + b, err := strconv.ParseBool(string(r)) + if err != nil { + return UNDEFINED_RESULT + } + return BoolNode(b) + case Number: + if strings.Index(string(r), ".") == -1 { + return r.To(Int) + } + return r.To(Float) + case Int: + b, err := strconv.ParseInt(string(r), 10, 64) + if err != nil { + return UNDEFINED_RESULT + } + return IntNode(b) + case Float: + b, err := strconv.ParseFloat(string(r), 64) + if err != nil { + return UNDEFINED_RESULT + } + return FloatNode(b) + } + return UNDEFINED_RESULT +} +func (r StringNode) Raw() []byte { + return []byte(fmt.Sprintf("\"%s\"", r)) +} +func (r StringNode) String() string { + return string(r) +} + +// JSONNode maybe Object or Array +type JSONNode struct { + value []byte + path string + datatype Type + offset int + err error +} + +func (r JSONNode) Type() Type { return r.datatype } +func (r JSONNode) Error() error { return r.err } +func (cc JSONNode) To(typ Type) Node { + switch typ { + case JSON, Object, Array: + return cc + case Bool: + return cc.To(String).To(Bool) + case Number: + return cc.To(String).To(Number) + case Int: + return cc.To(String).To(Int) + case Float: + return cc.To(String).To(Float) + case String: + return StringNode(cc.String()) + case Null: + return UNDEFINED_RESULT + case Undefined: + return UNDEFINED_RESULT + default: + return UNDEFINED_RESULT + } + return UNDEFINED_RESULT +} +func (r JSONNode) Raw() []byte { + switch r.datatype { + case String: + ret := append([]byte{byte('"')}, r.value...) + ret = append(ret, byte('"')) + return ret + default: + return []byte(r.String()) + } +} +func (r JSONNode) String() string { + return string(r.value) +} diff --git a/context.go b/context.go index dec1d6a..37d8bdb 100644 --- a/context.go +++ b/context.go @@ -94,9 +94,9 @@ var base64Func = func(args ...Node) Node { node := "" switch arg := arg.(type) { case JSONNode: - node = string(arg) + node = arg.String() case StringNode: - node = string(arg) + node = arg.String() default: fmt.Println( "[-]ruleql type error", @@ -139,7 +139,8 @@ type MutilContext []Context func (mc MutilContext) Value(key string) Node { for _, v := range mc { r := v.Value(key) - if r.Type() != Undefined { + t:= r.Type() + if t != Undefined { return r } } diff --git a/context_json.go b/context_json.go index 4ba638d..89310c6 100644 --- a/context_json.go +++ b/context_json.go @@ -17,9 +17,6 @@ package tdtl import ( "regexp" - "strings" - - "github.com/tkeel-io/tdtl/json/gjson" ) var ( @@ -28,76 +25,25 @@ var ( ) type jsonContext struct { - raw string + raw *Collect } //NewJSONContext new context from json func NewJSONContext(jsonRaw string) Context { return &jsonContext{ - raw: jsonRaw, + raw: New(jsonRaw), } } //Value get value from context func (c *jsonContext) Value(path string) Node { - if path == "*" { - return JSONNode(c.raw) - } - ret := gjson.Get(c.raw, thePath(path)) - switch ret.Type { - case gjson.True: - return BoolNode(true) - case gjson.False: - return BoolNode(false) - case gjson.String: - return StringNode(ret.Str) - case gjson.Number: - if strings.Index(ret.Raw, ".") != -1 { - return FloatNode(ret.Num) - } - return IntNode(ret.Num) - case gjson.JSON: - return JSONNode(ret.Raw) - case gjson.Null: - return UNDEFINED_RESULT + if path == "" { + return c.raw.Node() } - return UNDEFINED_RESULT + return c.raw.Get(path).Node() } //Call call function from context func (c *jsonContext) Call(expr *CallExpr, args []Node) Node { return UNDEFINED_RESULT } - -func thePath(path string) string { - //return pattern.ReplaceAllString(path, template) - path = strings.ReplaceAll(path, "[", ".") - path = strings.ReplaceAll(path, "]", "") - return path -} - -//Value get value from context -func (c *jsonContext) Range(path string) Node { - if path == "*" { - return JSONNode(c.raw) - } - ret := gjson.Get(c.raw, thePath(path)) - switch ret.Type { - case gjson.True: - return BoolNode(true) - case gjson.False: - return BoolNode(false) - case gjson.String: - return StringNode(ret.Str) - case gjson.Number: - if strings.Index(ret.Raw, ".") != -1 { - return FloatNode(ret.Num) - } - return IntNode(ret.Num) - case gjson.JSON: - return JSONNode(ret.Raw) - case gjson.Null: - return UNDEFINED_RESULT - } - return UNDEFINED_RESULT -} diff --git a/context_json_test.go b/context_json_test.go index 2de2c11..d754298 100644 --- a/context_json_test.go +++ b/context_json_test.go @@ -1,22 +1,2 @@ package tdtl -import "testing" - -func Test_thePath(t *testing.T) { - tests := []struct { - name string - path string - want string - }{ - {"", "", ""}, - {"", "a", "a"}, - {"", "a.b", "a.b"}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := thePath(tt.path); got != tt.want { - t.Errorf("thePath() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/evaluator.go b/evaluator.go index 2b2dac0..9a18c5b 100644 --- a/evaluator.go +++ b/evaluator.go @@ -92,8 +92,6 @@ func evalRuleQL(ctx Context, expr Expr) Node { return expr case JSONNode: return expr - case DefaultNode: - return expr } return UNDEFINED_RESULT } @@ -149,8 +147,6 @@ func eval(ctx Context, expr Expr) Node { return expr case *CallExpr: return evalCallExpr(ctx, expr) - case DefaultNode: - return expr } return UNDEFINED_RESULT } @@ -169,13 +165,14 @@ func evalSelect(ctx Context, expr Expr) Node { } func evalFieldListExpr(ctx Context, list FieldsExpr) Node { - v := JSONNode("{}") + v := New("{}") for _, expr := range list { ret := eval(ctx, expr.exp) if expr.alias != "" { - val, err := v.Update(expr.alias, ret) - if err == nil { - v = JSONNode(val) + v.Set(expr.alias, ret) + if v.Error() != nil { + //fmt.Println("error in %v", v.Error()) + continue } } } @@ -273,13 +270,13 @@ func evalBinary(op int, lhs, rhs Node) Node { case BoolNode: return evalBinaryBool(op, lhs, rhs) } - case *DefaultNode: + case *JSONNode: if isBooleanOP(op) { return evalBinary(op, BoolNode(false), rhs) } } return UNDEFINED_RESULT - case *DefaultNode: + case *JSONNode: if isBooleanOP(op) { return BoolNode(false) } diff --git a/evaluator_test.go b/evaluator_test.go index 181fc77..f4765e3 100644 --- a/evaluator_test.go +++ b/evaluator_test.go @@ -19,7 +19,7 @@ func TestDefaultValue_Eval(t *testing.T) { name string json string expr string - want interface{} + want Node }{ {"number", JSONRaw.SimpleJSON, `(YX_0002 * 2 + YX_0003)`, IntNode(4)}, {"number", JSONRaw.SimpleJSON, `1`, IntNode(1)}, @@ -81,18 +81,19 @@ func TestDefaultValue_Eval(t *testing.T) { {"json", JSONRaw.JSON, "temperature + 2.0 * (temperature + 2) * 3", FloatNode(362)}, {"json", JSONRaw.JSON, "temperature + 2.0 * (temperature + 2) * 3 > 362", BoolNode(false)}, {"json", JSONRaw.JSON, "temperature + 2.0 * (temperature + 2) * 3 < 363", BoolNode(true)}, - {"json", JSONRaw.JSON, "friends[0]", JSONNode("{\"first\": \"Dale\", \"last\": \"Murphy\", \"age\": 44}")}, + {"json", JSONRaw.JSON, "friends[0]", New("{\"first\": \"Dale\", \"last\": \"Murphy\", \"age\": 44}")}, {"json", JSONRaw.JSON, "friends[#]", IntNode(3)}, - {"json", JSONRaw.JSON, "friends[#].first", JSONNode("[\"Dale\",\"Roger\",\"Jane\"]")}, + {"json", JSONRaw.JSON, "friends[#].first", New("[\"Dale\",\"Roger\",\"Jane\"]")}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { v := NewJSONContext(tt.json) expr, _ := ParseExpr(tt.expr) // Field expr - print() - if got := eval(v, expr); !reflect.DeepEqual(got, tt.want) { + //print() + got := eval(v, expr) + if got.Type() == tt.want.Type() && !reflect.DeepEqual(got.Raw(), tt.want.Raw()) { t.Errorf("expr %v", expr) - t.Errorf("defaultEvalContext.Eval() = %v, want %v", got, tt.want) + t.Errorf("defaultEvalContext.Eval() \ngot= %v \nwant= %v", got, tt.want) } }) } @@ -168,15 +169,15 @@ func TestSQL(t *testing.T) { name string ctx Context expr string - want JSONNode + want *Collect }{ - {"sql", ctx, `insert into target1 select entity1.color`, JSONNode(`{}`)}, - {"sql", ctx, `insert into target1 select entity1.*`, JSONNode(`{}`)}, - {"sql", ctx, `insert into target1 select entity1.color as aaa`, JSONNode(`{"aaa":"red"}`)}, - {"sql", ctx, `insert into target1 select entity1.temperature + 1 AS temp`, JSONNode(`{"temp":51}`)}, - {"sql", ctx, `insert into target1 select entity1.temperature - 1 AS temp`, JSONNode(`{"temp":49}`)}, - {"sql", ctx, `insert into target1 select entity1.temperature + '1' AS temp`, JSONNode(`{"temp":"501"}`)}, - {"sql", ctx, `insert into target1 select entity1.temperature - '1' AS temp`, JSONNode(`{"temp":49}`)}, + {"sql", ctx, `insert into target1 select entity1.color`, New(`{}`)}, + {"sql", ctx, `insert into target1 select entity1.*`, New(`{}`)}, + {"sql", ctx, `insert into target1 select entity1.color as aaa`, New(`{"aaa":"red"}`)}, + {"sql", ctx, `insert into target1 select entity1.temperature + 1 AS temp`, New(`{"temp":51}`)}, + {"sql", ctx, `insert into target1 select entity1.temperature - 1 AS temp`, New(`{"temp":49}`)}, + {"sql", ctx, `insert into target1 select entity1.temperature + '1' AS temp`, New(`{"temp":"501"}`)}, + {"sql", ctx, `insert into target1 select entity1.temperature - '1' AS temp`, New(`{"temp":49}`)}, //{"sql", ctx, `insert into select entity1.params.OPCUA#Lu1_Bottom_Waice_Temp.value - 20 AS temp`, JSONNode(`{"temp":103}`)}, //{"sql", ctx, // `insert into select * from a/b`, diff --git a/go.mod b/go.mod index 8c07bcb..cfe34d8 100644 --- a/go.mod +++ b/go.mod @@ -13,22 +13,20 @@ require ( github.com/mreiferson/go-ujson v0.0.0-20200614021406-c02629f4935e github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 github.com/smartystreets/goconvey v1.7.2 + github.com/stretchr/testify v1.7.0 github.com/tidwall/match v1.1.1 github.com/tidwall/pretty v1.2.0 ) require ( - github.com/stretchr/testify v1.7.0 - github.com/tkeel-io/collectjs v0.0.0-20220223040031-79acd37e4d90 -) - -require ( + github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect + github.com/kr/pretty v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/smartystreets/assertions v1.2.0 // indirect - github.com/tidwall/gjson v1.12.0 // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/go.sum b/go.sum index e69de29..54968ad 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,63 @@ +github.com/Jeffail/gabs v1.4.0 h1://5fYRRTq1edjfIrQGvdkcd22pkYUrHZ5YC/H2GJVAo= +github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= +github.com/a8m/djson v0.0.0-20170509170705-c02c5aef757f h1:su5fhWd5UCmmRQEFPQPalJ304Qtcgk9ZDDnKnvpsraU= +github.com/a8m/djson v0.0.0-20170509170705-c02c5aef757f/go.mod h1:w3s8fnedJo6LJQ7dUUf1OcetqgS1hGpIDjY5bBowg1Y= +github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f h1:0cEys61Sr2hUBEXfNV8eyQP01oZuBgoMeHunebPirK8= +github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y= +github.com/antonholmquist/jason v1.0.0 h1:Ytg94Bcf1Bfi965K2q0s22mig/n4eGqEij/atENBhA0= +github.com/antonholmquist/jason v1.0.0/go.mod h1:+GxMEKI0Va2U8h3os6oiUAetHAlGMvxjdpAH/9uvUMA= +github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mreiferson/go-ujson v0.0.0-20200614021406-c02629f4935e h1:d62FkSNtPk3+foLRLC43qnUlrMxf6icm/pJYop5nfpQ= +github.com/mreiferson/go-ujson v0.0.0-20200614021406-c02629f4935e/go.mod h1:pRizrH03mzcoHZVa3eK2eoMfq4COW0kGOqapG3/ewkE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 h1:xoIK0ctDddBMnc74udxJYBqlo9Ylnsp1waqjLsnef20= +github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= +github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/json/gjson/logo.png b/json/gjson/logo.png deleted file mode 100644 index e69de29..0000000 diff --git a/json/gojsonq/.gitignore b/json/gojsonq/.gitignore deleted file mode 100644 index 2782bd9..0000000 --- a/json/gojsonq/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -data.json -.idea -.DS_Store diff --git a/json/gojsonq/.travis.yml b/json/gojsonq/.travis.yml deleted file mode 100644 index 49c5d55..0000000 --- a/json/gojsonq/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -language: go -sudo: false - -matrix: - include: - - go: 1.6 - - go: 1.7 - - go: 1.8 - - go: 1.9 - - go: 1.10.x - - go: 1.11.x - - go: 1.12.x - - go: 1.13.x - - go: tip - allow_failures: - - go: tip -before_install: - - go get github.com/mattn/goveralls -script: - - $GOPATH/bin/goveralls -service=travis-ci - - go get -t -v ./... - - diff -u <(echo -n) <(gofmt -d .) - - go vet $(go list ./... | grep -v /vendor/) - - go test -v -race ./... - - go test --bench . --benchmem=true diff --git a/json/gojsonq/CONTRIBUTING.md b/json/gojsonq/CONTRIBUTING.md deleted file mode 100644 index 225cdb2..0000000 --- a/json/gojsonq/CONTRIBUTING.md +++ /dev/null @@ -1,13 +0,0 @@ -# Contributing - -## Must follow the guide for issues - - Use the search tool before opening a new issue. - - Please provide source code and stack trace if you found a bug. - - Please review the existing issues and then provide feedback - -## Pull Request Process - - Before sending PR, create issue and discuss about the changes - - You MUST send pull requests against `dev` branch - - It should pass all tests in the available continuous integrations systems such as TravisCI. - - You should add/modify tests to cover your proposed code changes. - - If your pull request contains a new feature, please document it on the README. diff --git a/json/gojsonq/LICENSE.md b/json/gojsonq/LICENSE.md deleted file mode 100644 index 63673ea..0000000 --- a/json/gojsonq/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -# The MIT License (MIT) - -Copyright (c) 2018 Saddam H - -> 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: -> -> The above copyright notice and this permission notice shall be included in -> all copies or substantial portions of the Software. -> -> 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. diff --git a/json/gojsonq/Makefile b/json/gojsonq/Makefile deleted file mode 100644 index 600ff91..0000000 --- a/json/gojsonq/Makefile +++ /dev/null @@ -1,52 +0,0 @@ -# Makefile includes some useful commands to build or format incentives -# More commands could be added - -# Variables -PROJECT = gojsonq -REPO_ROOT = github.com/thedevsaddam -ROOT = ${REPO_ROOT}/${PROJECT} - -fmt: - goimports -w . - gofmt -s -w . - -compile: fmt - go install . - -check: fmt - golangci-lint run --deadline 10m ./... - staticcheck -checks="all,-S1*" ./... - -dep: - go mod download - go mod vendor - go mod tidy - -# A user can invoke tests in different ways: -# - make test runs all tests; -# - make test TEST_TIMEOUT=10 runs all tests with a timeout of 10 seconds; -# - make test TEST_PKG=./model/... only runs tests for the model package; -# - make test TEST_ARGS="-v -short" runs tests with the specified arguments; -# - make test-race runs tests with race detector enabled. -TEST_TIMEOUT = 60 -TEST_PKGS ?= ./... -TEST_TARGETS := test-short test-verbose test-race test-cover -.PHONY: $(TEST_TARGETS) test -test-short: TEST_ARGS=-short -test-verbose: TEST_ARGS=-v -test-race: TEST_ARGS=-race -test-cover: TEST_ARGS=-cover -$(TEST_TARGETS): test - -test: compile - go test -timeout $(TEST_TIMEOUT)s $(TEST_ARGS) $(TEST_PKGS) - -clean: - @go clean - -.PHONY: help -help: - @$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | \ - awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | \ - sort | \ - egrep -v -e '^[^[:alnum:]]' -e '^$@$$' \ No newline at end of file diff --git a/json/gojsonq/README.md b/json/gojsonq/README.md deleted file mode 100644 index 21c3f6b..0000000 --- a/json/gojsonq/README.md +++ /dev/null @@ -1,91 +0,0 @@ -![gojsonq-logo](gojsonq.png) - -[![Build Status](https://travis-ci.org/thedevsaddam/gojsonq.svg?branch=master)](https://travis-ci.org/thedevsaddam/gojsonq) -[![Project status](https://img.shields.io/badge/version-v2-green.svg)](https://github.com/thedevsaddam/gojsonq/releases) -[![Go Report Card](https://goreportcard.com/badge/github.com/thedevsaddam/gojsonq)](https://goreportcard.com/report/github.com/thedevsaddam/gojsonq) -[![Coverage Status](https://coveralls.io/repos/github/thedevsaddam/gojsonq/badge.svg?branch=master)](https://coveralls.io/github/thedevsaddam/gojsonq) -[![GoDoc](https://godoc.org/github.com/thedevsaddam/gojsonq?status.svg)](https://pkg.go.dev/github.com/thedevsaddam/gojsonq/v2) -[![License](https://img.shields.io/dub/l/vibe-d.svg)](LICENSE.md) - -A simple Go package to Query over JSON Data. It provides [simple](https://github.com/thedevsaddam/gojsonq/wiki/Queries#jsonstringjson), [elegant](https://github.com/thedevsaddam/gojsonq/wiki/Queries#selectproperties) and [fast](https://github.com/thedevsaddam/gojsonq/wiki/Benchmark) [ODM](https://github.com/thedevsaddam/gojsonq/wiki/Queries#frompath) like API to access, query JSON document - -### Installation - -Install the package using -```go -$ go get github.com/thedevsaddam/gojsonq/v2 -``` - -### Usage - -To use the package import it in your `*.go` code -```go -import "github.com/thedevsaddam/gojsonq/v2" -``` - -Let's see a quick example: - -[See in playground](https://play.golang.org/p/UiqyllP2vkn) - -```go -package main - -import gojsonq "github.com/thedevsaddam/gojsonq/v2" - -func main() { - const json = `{"name":{"first":"Tom","last":"Hanks"},"age":61}` - name := gojsonq.New().FromString(json).Find("name.first") - println(name.(string)) // Tom -} -``` - -Another example: - -[See in playground](https://play.golang.org/p/QLVxpi6nVbi) - -```go -package main - -import ( - "fmt" - - gojsonq "github.com/thedevsaddam/gojsonq/v2" -) - -func main() { - const json = `{"city":"dhaka","type":"weekly","temperatures":[30,39.9,35.4,33.5,31.6,33.2,30.7]}` - avg := gojsonq.New().FromString(json).From("temperatures").Avg() - fmt.Printf("Average temperature: %.2f", avg) // 33.471428571428575 -} -``` - -You can query your document using the various query methods such as **[Find](https://github.com/thedevsaddam/gojsonq/wiki/Queries#findpath)**, **[First](https://github.com/thedevsaddam/gojsonq/wiki/Queries#first)**, **[Nth](https://github.com/thedevsaddam/gojsonq/wiki/Queries#nthindex)**, **[Pluck](https://github.com/thedevsaddam/gojsonq/wiki/Queries#pluckproperty)**, **[Where](https://github.com/thedevsaddam/gojsonq/wiki/Queries#wherekey-op-val)**, **[OrWhere](https://github.com/thedevsaddam/gojsonq/wiki/Queries#orwherekey-op-val)**, **[WhereIn](https://github.com/thedevsaddam/gojsonq/wiki/Queries#whereinkey-val)**, **[WhereStartsWith](https://github.com/thedevsaddam/gojsonq/wiki/Queries#wherestartswithkey-val)**, **[WhereEndsWith](https://github.com/thedevsaddam/gojsonq/wiki/Queries#whereendswithkey-val)**, **[WhereContains](https://github.com/thedevsaddam/gojsonq/wiki/Queries#wherecontainskey-val)**, **[Sort](https://github.com/thedevsaddam/gojsonq/wiki/Queries#sortorder)**, **[GroupBy](https://github.com/thedevsaddam/gojsonq/wiki/Queries#groupbyproperty)**, **[SortBy](https://github.com/thedevsaddam/gojsonq/wiki/Queries#sortbyproperty-order)** and so on. Also you can aggregate data after query using **[Avg](https://github.com/thedevsaddam/gojsonq/wiki/Queries#avgproperty)**, **[Count](https://github.com/thedevsaddam/gojsonq/wiki/Queries#count)**, **[Max](https://github.com/thedevsaddam/gojsonq/wiki/Queries#maxproperty)**, **[Min](https://github.com/thedevsaddam/gojsonq/wiki/Queries#minproperty)**, **[Sum](https://github.com/thedevsaddam/gojsonq/wiki/Queries#sumproperty)** etc. - -## Find more query API in [Wiki page](https://github.com/thedevsaddam/gojsonq/wiki/Queries) - -## Bugs and Issues - -If you encounter any bugs or issues, feel free to [open an issue at -github](https://github.com/thedevsaddam/gojsonq/issues). - -Also, you can shoot me an email to - for hugs or bugs. - -## Credit - -Special thanks to [Nahid Bin Azhar](https://github.com/nahid) for the inspiration and guidance for the package. Thanks to [Ahmed Shamim Hasan Shaon](https://github.com/me-shaon) for his support from the very beginning. - -## Contributors -* [Lenin Hasda](https://github.com/leninhasda) -* [Sadlil Rhythom](https://github.com/sadlil) -* [See contributors list here](https://github.com/thedevsaddam/gojsonq/graphs/contributors) - -## Contribution -If you are interested to make the package better please send pull requests or create an issue so that others can fix. -[Read the contribution guide here](CONTRIBUTING.md) - -## Special Thanks - - -## License -The **gojsonq** is an open-source software licensed under the [MIT License](LICENSE.md). diff --git a/json/gojsonq/decoder.go b/json/gojsonq/decoder.go deleted file mode 100644 index a650bcd..0000000 --- a/json/gojsonq/decoder.go +++ /dev/null @@ -1,16 +0,0 @@ -package gojsonq - -import "encoding/json" - -// Decoder provide contract to decode JSON using custom decoder -type Decoder interface { - Decode(data []byte, v interface{}) error -} - -// DefaultDecoder use json.Unmarshal to decode JSON -type DefaultDecoder struct{} - -// Decode decodes using json.Unmarshal -func (u *DefaultDecoder) Decode(data []byte, v interface{}) error { - return json.Unmarshal(data, v) -} diff --git a/json/gojsonq/decoder_test.go b/json/gojsonq/decoder_test.go deleted file mode 100644 index da3110a..0000000 --- a/json/gojsonq/decoder_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package gojsonq - -import ( - "testing" -) - -func Test_DefaultDecoder(t *testing.T) { - dd := DefaultDecoder{} - var user = struct { - Name string `json:"name"` - Age int `json:"age"` - }{} - if err := dd.Decode([]byte(`{"name": "tom", "age": 27}`), &user); err != nil { - t.Errorf("failed to decode using default decoder: %v", err) - } - - if user.Name != "tom" || user.Age != 27 { - t.Error("failed to decode properly by default decoder") - } - -} diff --git a/json/gojsonq/doc.go b/json/gojsonq/doc.go deleted file mode 100644 index 2ef2a70..0000000 --- a/json/gojsonq/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -// Package gojsonq provides a simple, elegant and fast ODM like API to access/query JSON document. -// -// JSON document can be read from file, string or io.Reader. -// Accessing the value of json property or querying document is simple as the example below: -// -// package main -// -// import "github.com/thedevsaddam/gojsonq" -// -// const json = `{"name":{"first":"Tom","last":"Hanks"},"age":61}` -// -// func main() { -// name := gojsonq.New().FromString(json).Find("name.first") -// println(name.(string)) // Tom -// } -// -// For more details, see the documentation and examples. -// -package gojsonq diff --git a/json/gojsonq/gojsonq.png b/json/gojsonq/gojsonq.png deleted file mode 100644 index e69de29..0000000 diff --git a/json/gojsonq/helper.go b/json/gojsonq/helper.go deleted file mode 100644 index 855208b..0000000 --- a/json/gojsonq/helper.go +++ /dev/null @@ -1,266 +0,0 @@ -package gojsonq - -import ( - "errors" - "fmt" - "reflect" - "sort" - "strconv" - "strings" -) - -func abs(i int) int { - if i < 0 { - i = -1 * i - } - return i -} - -func isIndex(in string) bool { - return strings.HasPrefix(in, "[") && strings.HasSuffix(in, "]") -} - -func getIndex(in string) (int, error) { - if !isIndex(in) { - return -1, fmt.Errorf("invalid index") - } - is := strings.TrimLeft(in, "[") - is = strings.TrimRight(is, "]") - oint, err := strconv.Atoi(is) - if err != nil { - return -1, err - } - return oint, nil -} - -func toString(v interface{}) string { - return fmt.Sprintf("%v", v) -} - -// toFloat64 converts interface{} value to float64 if value is numeric else return false -func toFloat64(v interface{}) (float64, bool) { - var f float64 - flag := true - // as Go convert the json Numeric value to float64 - switch u := v.(type) { - case int: - f = float64(u) - case int8: - f = float64(u) - case int16: - f = float64(u) - case int32: - f = float64(u) - case int64: - f = float64(u) - case float32: - f = float64(u) - case float64: - f = u - default: - flag = false - } - return f, flag -} - -// sortList sorts a list of interfaces -func sortList(list []interface{}, asc bool) []interface{} { - var ss []string - var ff []float64 - var result []interface{} - for _, v := range list { - // sort elements for string - if sv, ok := v.(string); ok { - ss = append(ss, sv) - } - // sort elements for float64 - if fv, ok := v.(float64); ok { - ff = append(ff, fv) - } - } - - if len(ss) > 0 { - if asc { - sort.Strings(ss) - } else { - sort.Sort(sort.Reverse(sort.StringSlice(ss))) - } - for _, v := range ss { - result = append(result, v) - } - } - if len(ff) > 0 { - if asc { - sort.Float64s(ff) - } else { - sort.Sort(sort.Reverse(sort.Float64Slice(ff))) - } - for _, v := range ff { - result = append(result, v) - } - } - return result -} - -type sortMap struct { - data interface{} - key string - desc bool - separator string - errs []error -} - -// Sort sorts the slice of maps -func (s *sortMap) Sort(data interface{}) { - s.data = data - sort.Sort(s) -} - -// Len satisfies the sort.Interface -func (s *sortMap) Len() int { - return reflect.ValueOf(s.data).Len() -} - -// Swap satisfies the sort.Interface -func (s *sortMap) Swap(i, j int) { - if i > j { - i, j = j, i - } - list := reflect.ValueOf(s.data) - tmp := list.Index(i).Interface() - list.Index(i).Set(list.Index(j)) - list.Index(j).Set(reflect.ValueOf(tmp)) -} - -// TODO: need improvement -// Less satisfies the sort.Interface -// This will work for string/float64 only -func (s *sortMap) Less(i, j int) (res bool) { - list := reflect.ValueOf(s.data) - x := list.Index(i).Interface() - y := list.Index(j).Interface() - - // compare nested values - if strings.Contains(s.key, s.separator) { - xv, errX := getNestedValue(x, s.key, s.separator) - if errX != nil { - s.errs = append(s.errs, errX) - } - yv, errY := getNestedValue(y, s.key, s.separator) - if errY != nil { - s.errs = append(s.errs, errY) - } - res = s.compare(xv, yv) - } - - xv, okX := x.(map[string]interface{}) - if !okX { - return - } - yv := y.(map[string]interface{}) - if mvx, ok := xv[s.key]; ok { - mvy := yv[s.key] - res = s.compare(mvx, mvy) - } - - return -} - -// compare compare two values -func (s *sortMap) compare(x, y interface{}) (res bool) { - if mfv, ok := x.(float64); ok { - if mvy, oky := y.(float64); oky { - if s.desc { - return mfv > mvy - } - res = mfv < mvy - } - } - - if mfv, ok := x.(string); ok { - if mvy, oky := y.(string); oky { - if s.desc { - return mfv > mvy - } - res = mfv < mvy - } - } - - return -} - -// getNestedValue fetch nested value from node -func getNestedValue(input interface{}, node, separator string) (interface{}, error) { - pp := strings.Split(node, separator) - for _, n := range pp { - if isIndex(n) { - // find slice/array - if arr, ok := input.([]interface{}); ok { - indx, err := getIndex(n) - if err != nil { - return input, err - } - arrLen := len(arr) - if arrLen == 0 || - indx > arrLen-1 { - return empty, errors.New("empty array") - } - input = arr[indx] - } - } else { - // find in map - validNode := false - if mp, ok := input.(map[string]interface{}); ok { - input, ok = mp[n] - validNode = ok - } - - // find in group data - if mp, ok := input.(map[string][]interface{}); ok { - input, ok = mp[n] - validNode = ok - } - - if !validNode { - return empty, fmt.Errorf("invalid node name %s", n) - } - } - } - - return input, nil -} - -// makeAlias provide syntactic suger. when provide Property name as "user.name as userName" -// it return userName as output and pure node name like: "user.name". -// If "user.name" does not use "as" clause then it'll return "user.name", "user.name" -func makeAlias(in, separator string) (string, string) { - const alias = " as " - in = strings.Replace(in, " As ", alias, -1) - in = strings.Replace(in, " AS ", alias, -1) - - if strings.Contains(in, alias) { - ss := strings.Split(in, alias) - return strings.TrimSpace(ss[0]), strings.TrimSpace(ss[1]) - } - - if strings.Contains(in, separator) { - ss := strings.Split(in, separator) - return in, ss[len(ss)-1] - } - - return in, in -} - -// length return length of strings/array/map -func length(v interface{}) (int, error) { - switch val := v.(type) { - case string: - return len(val), nil - case []interface{}: - return len(val), nil - case map[string]interface{}: - return len(val), nil - default: - return -1, errors.New("invalid type for length") - } -} diff --git a/json/gojsonq/helper_test.go b/json/gojsonq/helper_test.go deleted file mode 100644 index e3c55a4..0000000 --- a/json/gojsonq/helper_test.go +++ /dev/null @@ -1,477 +0,0 @@ -package gojsonq - -import ( - "bytes" - "encoding/json" - "testing" -) - -func Test_abs(t *testing.T) { - testCases := []struct { - data int - expected int - }{ - { - data: 15, - expected: 15, - }, - { - data: -25, - expected: 25, - }, - { - data: 0, - expected: 0, - }, - } - for _, tc := range testCases { - if o := abs(tc.data); o != tc.expected { - t.Errorf("expected: %v got: %v", tc.expected, o) - } - } -} - -func Test_isIndex(t *testing.T) { - testCases := []struct { - node string - expected bool - }{ - { - node: "items", - expected: false, - }, - { - node: "[0]", - expected: true, - }, - { - node: "[101]", - expected: true, - }, - { - node: "101", - expected: false, - }, - } - for _, tc := range testCases { - if o := isIndex(tc.node); o != tc.expected { - t.Errorf("expected: %v got: %v", tc.expected, o) - } - } -} - -func Test_getIndex(t *testing.T) { - testCases := []struct { - node string - expected int - }{ - { - node: "Invalid integer", - expected: -1, - }, - { - node: "item", - expected: -1, - }, - { - node: "[0]", - expected: 0, - }, - { - node: "101", - expected: -1, - }, - { - node: "[101]", - expected: 101, - }, - } - for _, tc := range testCases { - if o, _ := getIndex(tc.node); o != tc.expected { - t.Errorf("expected: %v got: %v", tc.expected, o) - } - } -} - -func Test_toString(t *testing.T) { - testCases := []struct { - val interface{} - expected string - }{ - { - val: 10, - expected: "10", - }, - { - val: -10, - expected: "-10", - }, - { - val: 10.99, - expected: "10.99", - }, - { - val: -10.99, - expected: "-10.99", - }, - { - val: true, - expected: "true", - }, - } - - for _, tc := range testCases { - if o := toString(tc.val); o != tc.expected { - t.Errorf("expected: %v got: %v", tc.expected, o) - } - } -} - -func Test_toFloat64(t *testing.T) { - testCases := []struct { - val interface{} - expected float64 - }{ - { - val: 10, - expected: 10, - }, - { - val: int8(1), - expected: 1, - }, - { - val: int16(91), - expected: 91, - }, - { - val: int32(88), - expected: 88, - }, - { - val: int64(898), - expected: 898, - }, - { - val: float32(99.01), - // The nearest IEEE754 float32 value of 99.01 is 99.01000213623047; which are not equal (while using ==). - // Need suggestions for precision float value. - // one way to solve the comparison using convertFloat(string with float precision)==float64 - expected: 99.01000213623047, - }, - { - val: float32(-99), - expected: -99, - }, - { - val: -99.91, - expected: -99.91, - }, - { - val: "", - expected: 0, - }, - { - val: []int{}, - expected: 0, - }, - } - - for _, tc := range testCases { - if o, _ := toFloat64(tc.val); o != tc.expected { - t.Errorf("expected: %v got: %v", tc.expected, o) - } - } -} - -func Test_sorter(t *testing.T) { - testCases := []struct { - tag string - asc bool - inArr []interface{} - outArr []interface{} - }{ - { - tag: "list of string, result should be in ascending order", - asc: true, - inArr: []interface{}{"x", "b", "a", "c", "z"}, - outArr: []interface{}{"a", "b", "c", "x", "z"}, - }, - { - tag: "list of string, result should be in descending order", - asc: false, - inArr: []interface{}{"x", "b", "a", "c", "z"}, - outArr: []interface{}{"z", "x", "c", "b", "a"}, - }, - { - tag: "list of float64, result should be in ascending order", - asc: true, - inArr: []interface{}{8.0, 7.0, 1.0, 3.0, 5.0, 8.0}, - outArr: []interface{}{1.0, 3.0, 5.0, 7.0, 8.0, 8.0}, - }, - { - tag: "list of float64, result should be in descending order", - asc: false, - inArr: []interface{}{8.0, 7.0, 1.0, 3.0, 5.0, 8.0}, - outArr: []interface{}{8.0, 8.0, 7.0, 5.0, 3.0, 1.0}, - }, - } - - for _, tc := range testCases { - obb, _ := json.Marshal(sortList(tc.inArr, tc.asc)) - ebb, _ := json.Marshal(tc.outArr) - if !bytes.Equal(obb, ebb) { - t.Errorf("expected: %v got: %v", string(obb), string(ebb)) - } - } -} - -func Test_sortMap(t *testing.T) { - testCases := []struct { - tag string - inObjs interface{} - outObjs interface{} - key string - asc bool - }{ - { - tag: "should return in ascending order of string value name", - key: "name", - asc: true, - inObjs: []map[string]interface{}{ - {"name": "Z", "height": 5.8}, - {"name": "A", "height": 5.5}, - {"name": "D", "height": 4.9}, - {"name": "X", "height": 5.9}, - }, - outObjs: []map[string]interface{}{ - {"name": "A", "height": 5.5}, - {"name": "D", "height": 4.9}, - {"name": "X", "height": 5.9}, - {"name": "Z", "height": 5.8}, - }, - }, - { - tag: "should return in descending order of string value name", - key: "name", - asc: false, - inObjs: []map[string]interface{}{ - {"name": "Z", "height": 5.8}, - {"name": "A", "height": 5.5}, - {"name": "D", "height": 4.9}, - {"name": "X", "height": 5.9}, - }, - outObjs: []map[string]interface{}{ - {"name": "Z", "height": 5.8}, - {"name": "X", "height": 5.9}, - {"name": "D", "height": 4.9}, - {"name": "A", "height": 5.5}, - }, - }, - { - tag: "should return in ascending order of float value height", - key: "height", - asc: true, - inObjs: []map[string]interface{}{ - {"name": "Z", "height": 5.8}, - {"name": "A", "height": 5.5}, - {"name": "D", "height": 4.9}, - {"name": "X", "height": 5.9}, - }, - outObjs: []map[string]interface{}{ - {"name": "D", "height": 4.9}, - {"name": "A", "height": 5.5}, - {"name": "Z", "height": 5.8}, - {"name": "X", "height": 5.9}, - }, - }, - { - tag: "should return in descending order of float value height", - key: "height", - asc: false, - inObjs: []map[string]interface{}{ - {"name": "Z", "height": 5.8}, - {"name": "A", "height": 5.5}, - {"name": "D", "height": 4.9}, - {"name": "X", "height": 5.9}, - }, - outObjs: []map[string]interface{}{ - {"name": "X", "height": 5.9}, - {"name": "Z", "height": 5.8}, - {"name": "A", "height": 5.5}, - {"name": "D", "height": 4.9}, - }, - }, - { - key: "height", - asc: false, - inObjs: []string{"a", "z", "x"}, - outObjs: []string{"a", "z", "x"}, - }, - { - key: "invalid_key", - asc: false, - inObjs: []string{"x", "z", "a"}, - outObjs: []string{"x", "z", "a"}, - }, - } - - for _, tc := range testCases { - inObjs := tc.inObjs - sm := &sortMap{} - sm.key = tc.key - sm.desc = !tc.asc - sm.Sort(inObjs) - assertInterface(t, inObjs, tc.outObjs, tc.tag) - } -} - -func Test_getNestedValue(t *testing.T) { - var content interface{} - if err := json.Unmarshal([]byte(jsonStr), &content); err != nil { - t.Error("failed to decode json:", err) - } - - testCases := []struct { - tag string - query string - expected interface{} - expectError bool - }{ - { - tag: "accessing node", - query: "vendor.name", - expected: `Star Trek`, - expectError: false, - }, - { - tag: "should return nil", - query: "vendor.xox", - expected: nil, - expectError: true, - }, - { - tag: "should return a map", - query: "vendor.items.[0]", - expected: map[string]interface{}{"id": 1, "name": "MacBook Pro 13 inch retina", "price": 1350}, - expectError: false, - }, - { - tag: "accessing not existed index", - query: "vendor.items.[10]", - expected: nil, - expectError: true, - }, - { - tag: "accessing invalid index error", - query: "vendor.items.[x]", - expected: nil, - expectError: true, - }, - { - tag: "should receive valid float value", - query: "vendor.items.[0].price", - expected: 1350, - expectError: false, - }, - } - - for _, tc := range testCases { - out, err := getNestedValue(content, tc.query, defaultSeparator) - if tc.expectError && err == nil { - t.Error("failed to catch error") - } - if !tc.expectError { - assertInterface(t, tc.expected, out, tc.tag) - } - } -} - -func Test_makeAlias(t *testing.T) { - testCases := []struct { - tag string - input string - node string - alias string - separator string - }{ - { - tag: "scenario 1", - input: "user.name as uname", - node: "user.name", - alias: "uname", - separator: ".", - }, - { - tag: "scenario 2", - input: "post.title", - node: "post.title", - alias: "title", - separator: ".", - }, - { - tag: "scenario 3", - input: "name", - node: "name", - alias: "name", - separator: ".", - }, - { - tag: "scenario 4", - input: "post->title", - node: "post->title", - alias: "title", - separator: "->", - }, - } - - for _, tc := range testCases { - n, a := makeAlias(tc.input, tc.separator) - if tc.node != n || tc.alias != a { - t.Errorf("Tag: %v\nExpected: %v %v \nGot: %v %v\n", tc.tag, tc.node, tc.alias, n, a) - } - } -} - -func Test_length(t *testing.T) { - testCases := []struct { - tag string - input interface{} - output int - errExpected bool - }{ - { - tag: "scenario 1: should return 5 with no error", - input: "Hello", - output: 5, - errExpected: false, - }, - { - tag: "scenario 2: must return error with -1", - input: 45, - output: -1, - errExpected: true, - }, - { - tag: "scenario 3: must return length of array", - input: []interface{}{"john", "31", false}, - output: 3, - errExpected: false, - }, - { - tag: "scenario 4: must return length of map", - input: map[string]interface{}{"name": "john", "age": 31, "is_designer": false}, - output: 3, - errExpected: false, - }, - } - - for _, tc := range testCases { - out, outErr := length(tc.input) - if out != tc.output { - if tc.errExpected && outErr == nil { - t.Errorf("tag: %s\nExpected: %v\nGot: %v", tc.tag, tc.output, out) - } - } - } -} diff --git a/json/gojsonq/jetbrains-grayscale.png b/json/gojsonq/jetbrains-grayscale.png deleted file mode 100644 index e69de29..0000000 diff --git a/json/gojsonq/jsonq.go b/json/gojsonq/jsonq.go deleted file mode 100644 index f01e796..0000000 --- a/json/gojsonq/jsonq.go +++ /dev/null @@ -1,858 +0,0 @@ -package gojsonq - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" -) - -// New returns a new instance of JSONQ -func New(options ...OptionFunc) *JSONQ { - jq := &JSONQ{ - queryMap: defaultQueries(), - option: option{ - decoder: &DefaultDecoder{}, - separator: defaultSeparator, - }, - } - for _, option := range options { - if err := option(jq); err != nil { - jq.addError(err) - } - } - return jq -} - -// empty represents an empty result -var empty interface{} - -const defaultSeparator = "." - -// query describes a query -type query struct { - key, operator string - value interface{} -} - -// JSONQ describes a JSONQ type which contains all the state -type JSONQ struct { - option option // contains options for JSONQ - queryMap map[string]QueryFunc // contains query functions - node string // contains node name - raw json.RawMessage // raw message from source (reader, string or file) - rootJSONContent interface{} // original decoded json data - jsonContent interface{} // copy of original decoded json data for further processing - queryIndex int // contains number of orWhere query call - queries [][]query // nested queries - attributes []string // select attributes that will be available in final resuls - offsetRecords int // number of records that will be skipped in final result - limitRecords int // number of records that will be available in final result - distinctProperty string // contain the distinct attribute name - errors []error // contains all the errors when processing -} - -// String satisfies stringer interface -func (j *JSONQ) String() string { - return fmt.Sprintf("\nContent: %s\nQueries:%v\n", string(j.raw), j.queries) -} - -// decode decodes the raw message to Go data structure -func (j *JSONQ) decode() *JSONQ { - err := j.option.decoder.Decode(j.raw, &j.rootJSONContent) - if err != nil { - return j.addError(err) - } - j.jsonContent = j.rootJSONContent - return j -} - -// Copy returns a new fresh instance of JSONQ with the original copy of data so that you can do -// concurrent operation on the same data without being decoded again -func (j *JSONQ) Copy() *JSONQ { - tmp := *j - return tmp.reset() -} - -// File read the json content from physical file -func (j *JSONQ) File(filename string) *JSONQ { - bb, err := ioutil.ReadFile(filename) - if err != nil { - return j.addError(err) - } - j.raw = bb - return j.decode() // handle error -} - -// JSONString reads the json content from valid json string -// Deprecated: this method will remove in next major release -func (j *JSONQ) JSONString(json string) *JSONQ { - return j.FromString(json) -} - -// FromString reads the content from valid json/xml/csv/yml string -func (j *JSONQ) FromString(str string) *JSONQ { - j.raw = []byte(str) - return j.decode() // handle error -} - -// Reader reads the json content from io reader -func (j *JSONQ) Reader(r io.Reader) *JSONQ { - buf := new(bytes.Buffer) - _, err := buf.ReadFrom(r) - if err != nil { - return j.addError(err) - } - j.raw = buf.Bytes() - buf.Reset() // reset the buffer - return j.decode() -} - -// Error returns first occurred error -func (j *JSONQ) Error() error { - errsln := len(j.errors) - if errsln == 0 { - return nil - } - return j.errors[0] -} - -// Errors returns list of all errors -func (j *JSONQ) Errors() []error { - return j.errors -} - -// addError adds error to error list -func (j *JSONQ) addError(err error) *JSONQ { - j.errors = append(j.errors, fmt.Errorf("gojsonq: %v", err)) - return j -} - -// Macro adds a new query func to the JSONQ -func (j *JSONQ) Macro(operator string, fn QueryFunc) *JSONQ { - if _, ok := j.queryMap[operator]; ok { - j.addError(fmt.Errorf("%s is already registered in query map", operator)) - return j - } - j.queryMap[operator] = fn - return j -} - -// From seeks the json content to provided node. e.g: "users.[0]" or "users.[0].name" -func (j *JSONQ) From(node string) *JSONQ { - j.node = node - v, err := getNestedValue(j.jsonContent, node, j.option.separator) - if err != nil { - j.addError(err) - } - j.jsonContent = v - return j -} - -// FromInterface reads the content from valid map[string]interface{} -func (j *JSONQ) FromInterface(v interface{}) *JSONQ { - switch data := v.(type) { - case []interface{}, map[string]interface{}, map[string][]interface{}: - j.rootJSONContent = data - j.jsonContent = j.rootJSONContent - default: - j.addError(fmt.Errorf("invalid type [%T]", v)) - } - return j -} - -// Select use for selection of the properties from query result -func (j *JSONQ) Select(properties ...string) *JSONQ { - j.attributes = append(j.attributes, properties...) - return j -} - -// Offset skips the number of records in result -func (j *JSONQ) Offset(offset int) *JSONQ { - j.offsetRecords = offset - return j -} - -// offset skips the records from result -func (j *JSONQ) offset() *JSONQ { - if list, ok := j.jsonContent.([]interface{}); ok { - if j.offsetRecords < 0 { - j.addError(fmt.Errorf("%d is invalid offset", j.offsetRecords)) - return j - } - if len(list) >= j.offsetRecords { - j.jsonContent = list[j.offsetRecords:] - } else { - j.jsonContent = make([]interface{}, 0) - } - } - return j -} - -// Limit limits the number of records in result -func (j *JSONQ) Limit(limit int) *JSONQ { - j.limitRecords = limit - return j -} - -// limit return the number of records in result set depending on the limit value -func (j *JSONQ) limit() *JSONQ { - if list, ok := j.jsonContent.([]interface{}); ok { - if j.limitRecords <= 0 { - j.addError(fmt.Errorf("%d is invalid limit", j.limitRecords)) - return j - } - if len(list) > j.limitRecords { - j.jsonContent = list[:j.limitRecords] - } - } - return j -} - -// Where builds a where clause. e.g: Where("name", "contains", "doe") -func (j *JSONQ) Where(key, cond string, val interface{}) *JSONQ { - q := query{ - key: key, - operator: cond, - value: val, - } - if j.queryIndex == 0 && len(j.queries) == 0 { - var qq []query - qq = append(qq, q) - j.queries = append(j.queries, qq) - } else { - j.queries[j.queryIndex] = append(j.queries[j.queryIndex], q) - } - - return j -} - -// WhereEqual is an alias of Where("key", "=", val) -func (j *JSONQ) WhereEqual(key string, val interface{}) *JSONQ { - return j.Where(key, operatorEq, val) -} - -// WhereNotEqual is an alias of Where("key", "!=", val) -func (j *JSONQ) WhereNotEqual(key string, val interface{}) *JSONQ { - return j.Where(key, operatorNotEq, val) -} - -// WhereNil is an alias of Where("key", "=", nil) -func (j *JSONQ) WhereNil(key string) *JSONQ { - return j.Where(key, operatorEq, nil) -} - -// WhereNotNil is an alias of Where("key", "!=", nil) -func (j *JSONQ) WhereNotNil(key string) *JSONQ { - return j.Where(key, operatorNotEq, nil) -} - -// WhereIn is an alias for where("key", "in", []string{"a", "b"}) -func (j *JSONQ) WhereIn(key string, val interface{}) *JSONQ { - j.Where(key, operatorIn, val) - return j -} - -// WhereNotIn is an alias for where("key", "notIn", []string{"a", "b"}) -func (j *JSONQ) WhereNotIn(key string, val interface{}) *JSONQ { - j.Where(key, operatorNotIn, val) - return j -} - -// OrWhere builds an OrWhere clause, basically it's a group of AND clauses -func (j *JSONQ) OrWhere(key, cond string, val interface{}) *JSONQ { - j.queryIndex++ - var qq []query - qq = append(qq, query{ - key: key, - operator: cond, - value: val, - }) - j.queries = append(j.queries, qq) - return j -} - -// WhereStartsWith satisfies Where clause which starts with provided value(string) -func (j *JSONQ) WhereStartsWith(key string, val interface{}) *JSONQ { - return j.Where(key, operatorStartsWith, val) -} - -// WhereEndsWith satisfies Where clause which ends with provided value(string) -func (j *JSONQ) WhereEndsWith(key string, val interface{}) *JSONQ { - return j.Where(key, operatorEndsWith, val) -} - -// WhereContains satisfies Where clause which contains provided value(string) -func (j *JSONQ) WhereContains(key string, val interface{}) *JSONQ { - return j.Where(key, operatorContains, val) -} - -// WhereStrictContains satisfies Where clause which contains provided value(string). -// This is case sensitive -func (j *JSONQ) WhereStrictContains(key string, val interface{}) *JSONQ { - return j.Where(key, operatorStrictContains, val) -} - -// WhereLenEqual is an alias of Where("key", "leneq", val) -func (j *JSONQ) WhereLenEqual(key string, val interface{}) *JSONQ { - return j.Where(key, operatorLenEq, val) -} - -// WhereLenNotEqual is an alias of Where("key", "lenneq", val) -func (j *JSONQ) WhereLenNotEqual(key string, val interface{}) *JSONQ { - return j.Where(key, operatorLenNotEq, val) -} - -// findInArray traverses through a list and returns the value list. -// This helps to process Where/OrWhere queries -func (j *JSONQ) findInArray(aa []interface{}) []interface{} { - result := make([]interface{}, 0) - for _, a := range aa { - if m, ok := a.(map[string]interface{}); ok { - result = append(result, j.findInMap(m)...) - } - } - return result -} - -// findInMap traverses through a map and returns the matched value list. -// This helps to process Where/OrWhere queries -func (j *JSONQ) findInMap(vm map[string]interface{}) []interface{} { - result := make([]interface{}, 0) - orPassed := false - for _, qList := range j.queries { - andPassed := true - for _, q := range qList { - cf, ok := j.queryMap[q.operator] - if !ok { - j.addError(fmt.Errorf("invalid operator %s", q.operator)) - return result - } - nv, errnv := getNestedValue(vm, q.key, j.option.separator) - if errnv != nil { - j.addError(errnv) - andPassed = false - } else { - qb, err := cf(nv, q.value) - if err != nil { - j.addError(err) - } - andPassed = andPassed && qb - } - } - orPassed = orPassed || andPassed - } - if orPassed { - result = append(result, vm) - } - return result -} - -// processQuery makes the result -func (j *JSONQ) processQuery() *JSONQ { - if aa, ok := j.jsonContent.([]interface{}); ok { - j.jsonContent = j.findInArray(aa) - } - return j -} - -// prepare builds the queries -func (j *JSONQ) prepare() *JSONQ { - if len(j.queries) > 0 { - j.processQuery() - } - if j.distinctProperty != "" { - j.distinct() - } - if len(j.attributes) > 0 { - j.jsonContent = j.only(j.attributes...) - } - j.queryIndex = 0 - return j -} - -// GroupBy builds a chunk of exact matched data in a group list using provided attribute/column/property -func (j *JSONQ) GroupBy(property string) *JSONQ { - j.prepare() - - dt := map[string][]interface{}{} - if aa, ok := j.jsonContent.([]interface{}); ok { - for _, a := range aa { - if vm, ok := a.(map[string]interface{}); ok { - v, err := getNestedValue(vm, property, j.option.separator) - if err != nil { - j.addError(err) - } else { - dt[toString(v)] = append(dt[toString(v)], vm) - } - } - } - } - // replace the new result with the previous result - j.jsonContent = dt - return j -} - -// Sort sorts an array -// default ascending order, pass "desc" for descending order -func (j *JSONQ) Sort(order ...string) *JSONQ { - j.prepare() - - asc := true - if len(order) > 1 { - return j.addError(fmt.Errorf("sort accepts only one argument asc/desc")) - } - if len(order) > 0 && order[0] == "desc" { - asc = false - } - if arr, ok := j.jsonContent.([]interface{}); ok { - j.jsonContent = sortList(arr, asc) - } - return j -} - -// SortBy sorts an array -// default ascending order, pass "desc" for descending order -func (j *JSONQ) SortBy(order ...string) *JSONQ { - j.prepare() - asc := true - if len(order) == 0 { - return j.addError(fmt.Errorf("provide at least one argument as property name")) - } - if len(order) > 2 { - return j.addError(fmt.Errorf("sort accepts only two arguments. first argument property name and second argument asc/desc")) - } - - if len(order) > 1 && order[1] == "desc" { - asc = false - } - - return j.sortBy(order[0], asc) -} - -// Distinct builds distinct value using provided attribute/column/property -func (j *JSONQ) Distinct(property string) *JSONQ { - j.distinctProperty = property - return j -} - -// distinct builds distinct value using provided attribute/column/property -func (j *JSONQ) distinct() *JSONQ { - m := map[string]bool{} - var dt = make([]interface{}, 0) - if aa, ok := j.jsonContent.([]interface{}); ok { - for _, a := range aa { - if vm, ok := a.(map[string]interface{}); ok { - v, err := getNestedValue(vm, j.distinctProperty, j.option.separator) - if err != nil { - j.addError(err) - } else { - if _, exist := m[toString(v)]; !exist { - dt = append(dt, vm) - m[toString(v)] = true - } - } - } - } - } - // replace the new result with the previous result - j.jsonContent = dt - return j -} - -// sortBy sorts list of map -func (j *JSONQ) sortBy(property string, asc bool) *JSONQ { - sortResult, ok := j.jsonContent.([]interface{}) - if !ok { - return j - } - if len(sortResult) == 0 { - return j - } - - sm := &sortMap{} - sm.separator = j.option.separator - sm.key = property - if !asc { - sm.desc = true - } - sm.Sort(sortResult) - - for _, e := range sm.errs { - j.addError(e) - } - - // replace the new result with the previous result - j.jsonContent = sortResult - return j -} - -// only return selected properties in result -func (j *JSONQ) only(properties ...string) interface{} { - var result = make([]interface{}, 0) - if aa, ok := j.jsonContent.([]interface{}); ok { - for _, am := range aa { - tmap := map[string]interface{}{} - for _, prop := range properties { - node, alias := makeAlias(prop, j.option.separator) - rv, errV := getNestedValue(am, node, j.option.separator) - if errV != nil { - j.addError(errV) - continue - } - tmap[alias] = rv - } - if len(tmap) > 0 { - result = append(result, tmap) - } - } - } - return result -} - -// Only collects the properties from a list of object -func (j *JSONQ) Only(properties ...string) interface{} { - return j.prepare().only(properties...) -} - -// OnlyR collects the properties from a list of object and return as Result instance -func (j *JSONQ) OnlyR(properties ...string) (*Result, error) { - v := j.Only(properties...) - if err := j.Error(); err != nil { - return nil, err - } - return NewResult(v), nil -} - -// Pluck build an array of values form a property of a list of objects -func (j *JSONQ) Pluck(property string) interface{} { - j.prepare() - if j.distinctProperty != "" { - j.distinct() - } - if j.limitRecords != 0 { - j.limit() - } - var result = make([]interface{}, 0) - if aa, ok := j.jsonContent.([]interface{}); ok { - for _, am := range aa { - if mv, ok := am.(map[string]interface{}); ok { - if v, ok := mv[property]; ok { - result = append(result, v) - } - } - } - } - return result -} - -// PluckR build an array of values form a property of a list of objects and return as Result instance -func (j *JSONQ) PluckR(property string) (*Result, error) { - v := j.Pluck(property) - if err := j.Error(); err != nil { - return nil, err - } - return NewResult(v), nil -} - -// reset resets the current state of JSONQ instance -func (j *JSONQ) reset() *JSONQ { - j.raw = nil - j.jsonContent = j.rootJSONContent - j.node = "" - j.queries = make([][]query, 0) - j.attributes = make([]string, 0) - j.queryIndex = 0 - j.offsetRecords = 0 - j.limitRecords = 0 - j.distinctProperty = "" - return j -} - -// Reset resets the current state of JSON instance and make a fresh object with the original json content -func (j *JSONQ) Reset() *JSONQ { - return j.reset() -} - -// Get return the result -func (j *JSONQ) Get() interface{} { - j.prepare() - if j.offsetRecords != 0 { - j.offset() - } - if j.limitRecords != 0 { - j.limit() - } - return j.jsonContent -} - -// GetR return the query results as Result instance -func (j *JSONQ) GetR() (*Result, error) { - v := j.Get() - if err := j.Error(); err != nil { - return nil, err - } - return NewResult(v), nil -} - -// First returns the first element of a list -func (j *JSONQ) First() interface{} { - j.prepare() - if arr, ok := j.jsonContent.([]interface{}); ok { - if len(arr) > 0 { - return arr[0] - } - } - return empty -} - -// FirstR returns the first element of a list as Result instance -func (j *JSONQ) FirstR() (*Result, error) { - v := j.First() - if err := j.Error(); err != nil { - return nil, err - } - return NewResult(v), nil -} - -// Last returns the last element of a list -func (j *JSONQ) Last() interface{} { - j.prepare() - if arr, ok := j.jsonContent.([]interface{}); ok { - if l := len(arr); l > 0 { - return arr[l-1] - } - } - return empty -} - -// LastR returns the last element of a list as Result instance -func (j *JSONQ) LastR() (*Result, error) { - v := j.Last() - if err := j.Error(); err != nil { - return nil, err - } - return NewResult(v), nil -} - -// Nth returns the nth element of a list -func (j *JSONQ) Nth(index int) interface{} { - if index == 0 { - j.addError(fmt.Errorf("index is not zero based")) - return empty - } - - j.prepare() - if arr, ok := j.jsonContent.([]interface{}); ok { - alen := len(arr) - if alen == 0 { - j.addError(fmt.Errorf("list is empty")) - return empty - } - if abs(index) > alen { - j.addError(fmt.Errorf("index out of range")) - return empty - } - if index > 0 { - return arr[index-1] - } - return arr[alen+index] - } - return empty -} - -// NthR returns the nth element of a list as Result instance -func (j *JSONQ) NthR(index int) (*Result, error) { - v := j.Nth(index) - if err := j.Error(); err != nil { - return nil, err - } - return NewResult(v), nil -} - -// Find returns the result of a exact matching path -func (j *JSONQ) Find(path string) interface{} { - return j.From(path).Get() -} - -// FindR returns the result as Result instance from the exact matching path -func (j *JSONQ) FindR(path string) (*Result, error) { - v := j.Find(path) - if err := j.Error(); err != nil { - return nil, err - } - return NewResult(v), nil -} - -// Count returns the number of total items. -// This could be a length of list/array/map -func (j *JSONQ) Count() int { - j.prepare() - var lnth int - // list of items - if list, ok := j.jsonContent.([]interface{}); ok { - lnth = len(list) - } - // return map len // TODO: need to think about map - if m, ok := j.jsonContent.(map[string]interface{}); ok { - lnth = len(m) - } - // group data items - if m, ok := j.jsonContent.(map[string][]interface{}); ok { - lnth = len(m) - } - - return lnth -} - -// Out write the queried data to defined custom type -func (j *JSONQ) Out(v interface{}) { - data, err := json.Marshal(j.Get()) - if err != nil { - j.addError(err) - return - } - if err := json.Unmarshal(data, &v); err != nil { - j.addError(err) - } -} - -// Writer write the queried data to a io.Writer -func (j *JSONQ) Writer(w io.Writer) { - err := json.NewEncoder(w).Encode(j.Get()) - if err != nil { - j.addError(err) - return - } -} - -// More provides the functionality to query over the resultant data. See https://github.com/thedevsaddam/gojsonq/wiki/Queries#More -func (j *JSONQ) More() *JSONQ { - j.raw = nil - j.rootJSONContent = j.Get() - j.node = "" - j.queries = make([][]query, 0) - j.attributes = make([]string, 0) - j.queryIndex = 0 - j.limitRecords = 0 - j.distinctProperty = "" - return j -} - -// getFloatValFromArray returns a list of float64 values from array/map for aggregation -func (j *JSONQ) getFloatValFromArray(arr []interface{}, property ...string) []float64 { - var ff []float64 - for _, a := range arr { - if av, ok := a.(float64); ok { - if len(property) > 0 { - j.addError(fmt.Errorf("unnecessary property name for array")) - return nil - } - ff = append(ff, av) - } - if mv, ok := a.(map[string]interface{}); ok { - if len(property) == 0 { - j.addError(fmt.Errorf("property name can not be empty for object")) - return nil - } - if fi, ok := mv[property[0]]; ok { - if flt, ok := fi.(float64); ok { - ff = append(ff, flt) - } else { - j.addError(fmt.Errorf("property %s's value '%v' is not numeric", property[0], fi)) - return nil - } - } else { - j.addError(fmt.Errorf("property '%s' does not exist", property[0])) - return nil - } - } - } - - return ff -} - -// getAggregationValues returns a list of float64 values for aggregation -func (j *JSONQ) getAggregationValues(property ...string) []float64 { - j.prepare() - if j.distinctProperty != "" { - j.distinct() - } - if j.limitRecords != 0 { - j.limit() - } - - var ff []float64 - if arr, ok := j.jsonContent.([]interface{}); ok { - ff = j.getFloatValFromArray(arr, property...) - } - - if mv, ok := j.jsonContent.(map[string]interface{}); ok { - if len(property) == 0 { - j.addError(fmt.Errorf("property can not be empty for object")) - return nil - } - if fi, ok := mv[property[0]]; ok { - if flt, ok := fi.(float64); ok { - ff = append(ff, flt) - } else { - j.addError(fmt.Errorf("property %s's value '%v' is not numeric", property[0], fi)) - return nil - } - } else { - j.addError(fmt.Errorf("property '%s' does not exist", property[0])) - return nil - } - } - return ff -} - -// Sum returns sum of values from array or from map using property -func (j *JSONQ) Sum(property ...string) float64 { - var sum float64 - for _, flt := range j.getAggregationValues(property...) { - sum += flt - } - return sum -} - -// Avg returns average of values from array or from map using property -func (j *JSONQ) Avg(property ...string) float64 { - var sum float64 - fl := j.getAggregationValues(property...) - for _, flt := range fl { - sum += flt - } - return sum / float64(len(fl)) -} - -// Min returns minimum value from array or from map using property -func (j *JSONQ) Min(property ...string) float64 { - var min float64 - flist := j.getAggregationValues(property...) - if len(flist) > 0 { - min = flist[0] - } - for _, flt := range flist { - if flt < min { - min = flt - } - } - return min -} - -// Max returns maximum value from array or from map using property -func (j *JSONQ) Max(property ...string) float64 { - var max float64 - flist := j.getAggregationValues(property...) - if len(flist) > 0 { - max = flist[0] - } - for _, flt := range flist { - if flt > max { - max = flt - } - } - return max -} diff --git a/json/gojsonq/jsonq_test.go b/json/gojsonq/jsonq_test.go deleted file mode 100644 index ca34902..0000000 --- a/json/gojsonq/jsonq_test.go +++ /dev/null @@ -1,1561 +0,0 @@ -package gojsonq - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "math" - "reflect" - "strings" - "testing" -) - -func TestNew(t *testing.T) { - jq := New() - if reflect.ValueOf(jq).Type().String() != "*gojsonq.JSONQ" { - t.Error("failed to match JSONQ type") - } -} - -func TestJSONQ_String(t *testing.T) { - jq := New() - expected := fmt.Sprintf("\nContent: %s\nQueries:%v\n", string(jq.raw), jq.queries) - if out := jq.String(); out != expected { - t.Errorf("Expected: %v\n Got: %v", expected, out) - } -} - -func TestJSONQ_decode(t *testing.T) { - testCases := []struct { - tag string - jsonStr string - errExpect bool - }{ - { - tag: "valid json", - jsonStr: `{"name": "John Doe", "age": 30}`, - errExpect: false, - }, - { - tag: "invalid json should return error", - jsonStr: `{"name": "John Doe", "age": 30, "only_key"}`, - errExpect: true, - }, - } - - for _, tc := range testCases { - jq := New() - jq.raw = json.RawMessage(tc.jsonStr) - jq.decode() - if err := jq.Error(); err != nil && !tc.errExpect { - t.Errorf("failed %s", tc.tag) - } - } -} - -func TestJSONQ_Copy(t *testing.T) { - jq := New() - mp := map[string]int{} - for i := 0; i < 100; i++ { - adr := fmt.Sprintf("%p", jq.Copy()) - if _, ok := mp[adr]; ok { - t.Error("failed to copy JSONQ") - } else { - mp[adr] = i - } - } -} - -func TestJSONQ_File(t *testing.T) { - filename := "data.json" - - path, f := createTestFile(t, filename) - defer f() - - testCases := []struct { - tag string - filename string - expectedErr bool - }{ - { - tag: "valid file name does not expect error", - filename: path, - expectedErr: false, - }, - { - tag: "invalid valid file name expecting error", - filename: "invalid_file.xjson", - expectedErr: true, - }, - } - - for _, tc := range testCases { - err := New().File(tc.filename).Error() - if tc.expectedErr && err == nil { - t.Errorf("%s", tc.tag) - } - } -} - -func TestJSONQ_JSONString(t *testing.T) { - testCases := []struct { - tag string - jsonStr string - errExpect bool - }{ - { - tag: "valid json", - jsonStr: `{"name": "John Doe", "age": 30}`, - errExpect: false, - }, - { - tag: "invalid json should return error", - jsonStr: `{"name": "John Doe", "age": 30, "only_key"}`, - errExpect: true, - }, - } - - for _, tc := range testCases { - if err := New().FromString(tc.jsonStr).Error(); err != nil && !tc.errExpect { - t.Errorf("failed %s", tc.tag) - } - } -} - -func TestJSONQ_FromString(t *testing.T) { - testCases := []struct { - tag string - inputStr string - errExpect bool - }{ - // TODO: Doesn't need to test decoder for input content - { - tag: "valid json", - inputStr: `{"name": "John Doe", "age": 30}`, - errExpect: false, - }, - { - tag: "invalid json should return error", - inputStr: `{"name": "John Doe", "age": 30, "only_key"}`, - errExpect: true, - }, - } - - for _, tc := range testCases { - if err := New().FromString(tc.inputStr).Error(); err != nil && !tc.errExpect { - t.Errorf("failed %s", tc.tag) - } - } -} - -func TestJSONQ_Reader(t *testing.T) { - testCases := []struct { - tag string - jsonStr string - errExpect bool - }{ - { - tag: "valid json", - jsonStr: `{"name": "John Doe", "age": 30}`, - errExpect: false, - }, - { - tag: "invalid json should return error", - jsonStr: `{"name": "John Doe", "age": 30, "only_key"}`, - errExpect: true, - }, - } - - for _, tc := range testCases { - rdr := strings.NewReader(tc.jsonStr) - if err := New().Reader(rdr).Error(); err != nil && !tc.errExpect { - t.Errorf("failed %s", tc.tag) - } - } -} - -type invalidReader string - -func (invalidReader) Read(p []byte) (n int, err error) { - return 0, errors.New("this reader always return an error") -} - -func TestJSONQ_Reader_expecting_error(t *testing.T) { - var rdr invalidReader - if err := New().Reader(rdr).Error(); err == nil { - t.Errorf("failed to catch Reader error") - } -} - -func TestJSONQ_Errors(t *testing.T) { - testCases := []struct { - tag string - jsonStr string - }{ - { - tag: "invalid json 1", - jsonStr: `{"name": "John Doe", "age": 30, :""}`, - }, - { - tag: "invalid json 2", - jsonStr: `{"name": "John Doe", "age": 30, "only_key"}`, - }, - } - - for _, tc := range testCases { - if errs := New().FromString(tc.jsonStr).Errors(); len(errs) == 0 { - t.Errorf("failed %s", tc.tag) - } - } -} - -func TestJSONQ_Macro(t *testing.T) { - jq := New() - jq.Macro("mac1", func(x, y interface{}) (bool, error) { - return true, nil - }) - - if _, ok := jq.queryMap["mac1"]; !ok { - t.Error("failed to register macro") - } - - jq.Macro("mac1", func(x, y interface{}) (bool, error) { - return true, nil - }) - if jq.Error() == nil { - t.Error("failed to throw error for already registered macro") - } -} - -func TestJSONQ_From_Set(t *testing.T) { - node := "root.items.[0].name" - jq := New().From(node) - if jq.node != node { - t.Error("failed to set node name") - } -} - -func TestJSONQ_Select(t *testing.T) { - jq := New().Select("id", "name") - if len(jq.attributes) != 2 { - t.Error("failed to set properties") - } -} - -func TestJSONQ_Offset(t *testing.T) { - jq := New().Offset(3) - if jq.offsetRecords != 3 { - t.Error("failed to set offset records value") - } -} - -func TestJSONQ_Limit(t *testing.T) { - jq := New().Limit(12) - if jq.limitRecords != 12 { - t.Error("failed to set limit records value") - } -} - -func TestJSONQ_reset(t *testing.T) { - node := "root.items" - jq := New().From(node).Select("name", "age").WhereEqual("price", "1900").WhereEqual("id", 1) - jq.reset() - if len(jq.queries) != 0 || - len(jq.attributes) != 0 || - jq.queryIndex != 0 { - t.Error("reset failed") - } -} - -func TestJSONQ_Reset(t *testing.T) { - node := "root.items" - jq := New().From(node).WhereEqual("price", "1900").WhereEqual("id", 1) - jq.Reset() - if len(jq.queries) != 0 || jq.queryIndex != 0 || jq.node != "" { - t.Error("reset failed") - } -} - -func TestJSONQ_From(t *testing.T) { - testCases := []struct { - tag string - query string - expected string - expectError bool - }{ - { - tag: "accessing node", - query: "vendor.name", - expected: `"Star Trek"`, - expectError: false, - }, - { - tag: "accessing not existed index", - query: "vendor.items.[0]", - expected: `{"id":1,"name":"MacBook Pro 13 inch retina","price":1350}`, - expectError: false, - }, - { - tag: "accessing not existed index", - query: "vendor.items.[10]", - expected: `null`, - expectError: false, - }, - { - tag: "accessing invalid index error", - query: "vendor.items.[x]", - expectError: true, - }, - } - - for _, tc := range testCases { - jq := New().FromString(jsonStr) - out := jq.From(tc.query).Get() - if tc.expectError && jq.Error() == nil { - t.Error("failed to catch error") - } - if !tc.expectError { - assertJSON(t, out, tc.expected, tc.tag) - } - } - - jq := New().FromString(jsonStr) - expJSON := `[{"id":3,"name":"Sony VAIO","price":1200}]` - out := jq.From("vendor.items").GroupBy("price").From("1200").Get() - assertJSON(t, out, expJSON, "accessing group by data") -} - -func TestJSONQ_FromInterface(t *testing.T) { - var v map[string]interface{} - err := json.Unmarshal([]byte(jsonStr), &v) - if err != nil { - t.Error(err) - } - jq := New().FromInterface(v) - if jq.rootJSONContent == nil || jq.jsonContent == nil { - t.Errorf("failed to assign value using FromInterface method") - } - - var customType float64 - jq = New().FromInterface(customType) - if jq.Error() == nil { - t.Errorf("failed to set error properly for FromInterface method") - } -} - -func TestJSONQ_Where_single_where(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Where("price", "=", 1700) - expected := `[{"id":2,"name":"MacBook Pro 15 inch retina","price":1700}]` - out := jq.Get() - assertJSON(t, out, expected, "single Where") -} - -func TestJSONQ_Where_deep_nested_value(t *testing.T) { - jq := New().FromString(jsonStrUsers). - From("users"). - Where("name.first", "=", "John") - expected := `[{"id":1,"name":{"first":"John","last":"Ramboo"}},{"id":3,"name":{"first":"John","last":"Doe"}}]` - out := jq.Get() - assertJSON(t, out, expected, "single Where with nested value") -} - -func TestJSONQ_Where_multiple_where_expecting_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Where("price", "=", 1700). - Where("id", "=", 2) - expected := `[{"id":2,"name":"MacBook Pro 15 inch retina","price":1700}]` - out := jq.Get() - assertJSON(t, out, expected, "multiple Where expecting data") -} - -func TestJSONQ_Where_multiple_where_expecting_empty_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Where("price", "=", 1700). - Where("id", "=", "1700") - expected := `[]` - out := jq.Get() - assertJSON(t, out, expected, "multiple Where expecting empty result") -} - -func TestJSONQ_Where_multiple_where_with_invalid_operator_expecting_error(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Where("price", "invalid_op", 1700) - jq.Get() - - if jq.Error() == nil { - t.Error("expecting: invalid operator invalid_op") - } -} - -func TestJSONQ_Where_multiple_where_with_invalid_operand_expecting_error(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Where("price", "contains", 1700) - jq.Get() - - if jq.Error() == nil { - t.Error("expecting: invalid operator invalid_op") - } -} - -func TestJSONQ_single_WhereEqual(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereEqual("price", 1700) - expected := `[{"id":2,"name":"MacBook Pro 15 inch retina","price":1700}]` - out := jq.Get() - assertJSON(t, out, expected, "single WhereEqual") -} - -func TestJSONQ_multiple_WhereEqual_expecting_data(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereEqual("price", 1700). - WhereEqual("id", 2) - expected := `[{"id":2,"name":"MacBook Pro 15 inch retina","price":1700}]` - out := jq.Get() - assertJSON(t, out, expected, "multiple WhereEqual expecting data") -} - -func TestJSONQ_multiple_WhereEqual_expecting_empty_data(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereEqual("price", 1700). - WhereEqual("id", "1700") - expected := `[]` - out := jq.Get() - assertJSON(t, out, expected, "multiple WhereEqual expecting empty result") -} - -func TestJSONQ_single_WhereNotEqual(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereNotEqual("price", 850) - expected := `[{"id":1,"name":"MacBook Pro 13 inch retina","price":1350},{"id":2,"name":"MacBook Pro 15 inch retina","price":1700},{"id":3,"name":"Sony VAIO","price":1200},{"id":6,"name":"HP core i7","price":950}]` - out := jq.Get() - assertJSON(t, out, expected, "single WhereNotEqual") -} - -func TestJSONQ_multiple_WhereNotEqual(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereNotEqual("price", 850). - WhereNotEqual("id", 2) - expected := `[{"id":1,"name":"MacBook Pro 13 inch retina","price":1350},{"id":3,"name":"Sony VAIO","price":1200},{"id":6,"name":"HP core i7","price":950}]` - out := jq.Get() - assertJSON(t, out, expected, "multiple WhereNotEqual expecting result") -} - -func TestJSONQ_WhereNil(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereNil("id") - expected := `[{"id":null,"name":"HP core i3 SSD","price":850}]` - out := jq.Get() - assertJSON(t, out, expected) -} - -func TestJSONQ_WhereNotNil(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereNotNil("id") - expected := `[{"id":1,"name":"MacBook Pro 13 inch retina","price":1350},{"id":2,"name":"MacBook Pro 15 inch retina","price":1700},{"id":3,"name":"Sony VAIO","price":1200},{"id":4,"name":"Fujitsu","price":850},{"id":5,"key":2300,"name":"HP core i5","price":850},{"id":6,"name":"HP core i7","price":950}]` - out := jq.Get() - assertJSON(t, out, expected) -} - -func TestJSONQ_WhereIn_expecting_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereIn("id", []int{1, 3, 5}) - expected := `[{"id":1,"name":"MacBook Pro 13 inch retina","price":1350},{"id":3,"name":"Sony VAIO","price":1200},{"id":5,"key":2300,"name":"HP core i5","price":850}]` - out := jq.Get() - assertJSON(t, out, expected, "WhereIn expecting result") -} - -func TestJSONQ_WhereIn_expecting_empty_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereIn("id", []int{18, 39, 85}) - expected := `[]` - out := jq.Get() - assertJSON(t, out, expected, "WhereIn expecting empty result") -} - -func TestJSONQ_WhereNotIn_expecting_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereNotIn("id", []int{1, 3, 5, 6}) - expected := `[{"id":2,"name":"MacBook Pro 15 inch retina","price":1700},{"id":4,"name":"Fujitsu","price":850},{"id":null,"name":"HP core i3 SSD","price":850}]` - out := jq.Get() - assertJSON(t, out, expected, "WhereIn expecting result") -} - -func TestJSONQ_WhereNotIn_expecting_empty_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereNotIn("price", []float64{850, 950, 1200, 1700, 1350}) - expected := `[]` - out := jq.Get() - assertJSON(t, out, expected, "WhereIn expecting empty result") -} - -func TestJSONQ_OrWhere(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - OrWhere("price", ">", 1200) - expected := `[{"id":1,"name":"MacBook Pro 13 inch retina","price":1350},{"id":2,"name":"MacBook Pro 15 inch retina","price":1700}]` - out := jq.Get() - assertJSON(t, out, expected, "OrWhere expecting result") -} - -func TestJSONQ_WhereStartsWith_expecting_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereStartsWith("name", "Mac") - expected := `[{"id":1,"name":"MacBook Pro 13 inch retina","price":1350},{"id":2,"name":"MacBook Pro 15 inch retina","price":1700}]` - out := jq.Get() - assertJSON(t, out, expected, "WhereStartsWith expecting result") -} - -func TestJSONQ_WhereStartsWith_expecting_empty_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereStartsWith("name", "xyz") - expected := `[]` - out := jq.Get() - assertJSON(t, out, expected, "WhereStartsWith expecting empty result") -} - -func TestJSONQ_WhereEndsWith(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereEndsWith("name", "retina") - expected := `[{"id":1,"name":"MacBook Pro 13 inch retina","price":1350},{"id":2,"name":"MacBook Pro 15 inch retina","price":1700}]` - out := jq.Get() - assertJSON(t, out, expected, "WhereStartsWith expecting result") -} - -func TestJSONQ_WhereEndsWith_empty_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereEndsWith("name", "xyz") - expected := `[]` - out := jq.Get() - assertJSON(t, out, expected, "WhereStartsWith expecting empty result") -} - -func TestJSONQ_WhereContains_expecting_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereContains("name", "RetinA") - expected := `[{"id":1,"name":"MacBook Pro 13 inch retina","price":1350},{"id":2,"name":"MacBook Pro 15 inch retina","price":1700}]` - out := jq.Get() - assertJSON(t, out, expected, "WhereContains expecting result") -} - -func TestJSONQ_WhereContains_expecting_empty_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereContains("name", "xyz") - expected := `[]` - out := jq.Get() - assertJSON(t, out, expected, "WhereContains expecting empty result") -} - -func TestJSONQ_WhereStrictContains_expecting_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereStrictContains("name", "retina") - expected := `[{"id":1,"name":"MacBook Pro 13 inch retina","price":1350},{"id":2,"name":"MacBook Pro 15 inch retina","price":1700}]` - out := jq.Get() - assertJSON(t, out, expected, "WhereContains expecting result") -} - -func TestJSONQ_WhereStrictContains_expecting_empty_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - WhereStrictContains("name", "RetinA") - expected := `[]` - out := jq.Get() - assertJSON(t, out, expected, "WhereContains expecting empty result") -} - -func TestJSONQ_GroupBy(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - GroupBy("price") - expected := `{"1200":[{"id":3,"name":"Sony VAIO","price":1200}],"1350":[{"id":1,"name":"MacBook Pro 13 inch retina","price":1350}],"1700":[{"id":2,"name":"MacBook Pro 15 inch retina","price":1700}],"850":[{"id":4,"name":"Fujitsu","price":850},{"id":5,"key":2300,"name":"HP core i5","price":850},{"id":null,"name":"HP core i3 SSD","price":850}],"950":[{"id":6,"name":"HP core i7","price":950}]}` - out := jq.Get() - assertJSON(t, out, expected, "GroupBy expecting result") -} - -func TestJSONQ_GroupBy_expecting_error(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - GroupBy("invalid_key") - expected := `{}` - out := jq.Get() - assertJSON(t, out, expected, "GroupBy expecting empty result") - if len(jq.Errors()) == 0 { - t.Error("failed to catch GroupBy error") - } -} - -func TestJSONQ_GroupBy_nested_property(t *testing.T) { - jq := New().FromString(jsonStrUsers). - From("users"). - GroupBy("name.first") - expected := `{"Ethan":[{"id":2,"name":{"first":"Ethan","last":"Hunt"}}],"John":[{"id":1,"name":{"first":"John","last":"Ramboo"}},{"id":3,"name":{"first":"John","last":"Doe"}}]}` - out := jq.Get() - assertJSON(t, out, expected, "GroupBy nested expecting result") -} - -func TestJSONQ_GroupBy_nested_property_expecting_error(t *testing.T) { - jq := New().FromString(jsonStrUsers). - From("users"). - GroupBy("name.invalid_key") - out := jq.Get() - expected := `{}` - assertJSON(t, out, expected, "Nsested GroupBy expecting empty result") - if len(jq.errors) == 0 { - t.Error("failed to catch GroupBy nested property error") - } -} - -func TestJSONQ_Sort_string_ascending_order(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.names"). - Sort() - expected := `["Abby","Jane Doe","Jerry","John Doe","Nicolas","Tom"]` - out := jq.Get() - assertJSON(t, out, expected, "sorting array of string in ascending desc") -} - -func TestJSONQ_Sort_float64_descending_order(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.prices"). - Sort("desc") - expected := `[2400,2100,1200,400.87,150.1,89.9]` - out := jq.Get() - assertJSON(t, out, expected, "sorting array of float in descending order") -} - -func TestJSONQ_Sort_with_two_args_expecting_error(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.prices"). - Sort("asc", "desc") - jq.Get() - if jq.Error() == nil { - t.Error("expecting an error") - } -} - -func TestJSONQ_SortBy_float_ascending_order(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - SortBy("price") - expected := `[{"id":null,"name":"HP core i3 SSD","price":850},{"id":4,"name":"Fujitsu","price":850},{"id":5,"key":2300,"name":"HP core i5","price":850},{"id":6,"name":"HP core i7","price":950},{"id":3,"name":"Sony VAIO","price":1200},{"id":1,"name":"MacBook Pro 13 inch retina","price":1350},{"id":2,"name":"MacBook Pro 15 inch retina","price":1700}]` - out := jq.Get() - assertJSON(t, out, expected, "sorting array of object by its key (price-float64) in ascending desc") -} - -func TestJSONQ_SortBy_float_descending_order(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - SortBy("price", "desc") - expected := `[{"id":2,"name":"MacBook Pro 15 inch retina","price":1700},{"id":1,"name":"MacBook Pro 13 inch retina","price":1350},{"id":3,"name":"Sony VAIO","price":1200},{"id":6,"name":"HP core i7","price":950},{"id":4,"name":"Fujitsu","price":850},{"id":5,"key":2300,"name":"HP core i5","price":850},{"id":null,"name":"HP core i3 SSD","price":850}]` - out := jq.Get() - assertJSON(t, out, expected, "sorting array of object by its key (price-float64) in descending desc") -} - -func TestJSONQ_SortBy_string_ascending_order(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - SortBy("name") - expected := `[{"id":4,"name":"Fujitsu","price":850},{"id":null,"name":"HP core i3 SSD","price":850},{"id":5,"key":2300,"name":"HP core i5","price":850},{"id":6,"name":"HP core i7","price":950},{"id":1,"name":"MacBook Pro 13 inch retina","price":1350},{"id":2,"name":"MacBook Pro 15 inch retina","price":1700},{"id":3,"name":"Sony VAIO","price":1200}]` - out := jq.Get() - assertJSON(t, out, expected, "sorting array of object by its key (name-string) in ascending desc") -} - -func TestJSONQ_SortBy_string_descending_order(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - SortBy("name", "desc") - expected := `[{"id":3,"name":"Sony VAIO","price":1200},{"id":2,"name":"MacBook Pro 15 inch retina","price":1700},{"id":1,"name":"MacBook Pro 13 inch retina","price":1350},{"id":6,"name":"HP core i7","price":950},{"id":5,"key":2300,"name":"HP core i5","price":850},{"id":null,"name":"HP core i3 SSD","price":850},{"id":4,"name":"Fujitsu","price":850}]` - out := jq.Get() - assertJSON(t, out, expected, "sorting array of object by its key (name-string) in descending desc") -} - -func TestJSONQ_SortBy_deep_nested_string_ascending_order(t *testing.T) { - jq := New().FromString(jsonStrUsers). - From("users"). - SortBy("name.first") - expected := `[{"id":2,"name":{"first":"Ethan","last":"Hunt"}},{"id":1,"name":{"first":"John","last":"Ramboo"}},{"id":3,"name":{"first":"John","last":"Doe"}}]` - out := jq.Get() - assertJSON(t, out, expected, "sorting array of object by its key (name-string) in descending desc") -} - -func TestJSONQ_SortBy_deep_nested_string_invalid_key_should_return_error(t *testing.T) { - jq := New().FromString(jsonStrUsers). - From("users"). - SortBy("name.middle") - expected := `[{"id":1,"name":{"first":"John","last":"Ramboo"}},{"id":2,"name":{"first":"Ethan","last":"Hunt"}},{"id":3,"name":{"first":"John","last":"Doe"}}]` // no ordering, remain same - out := jq.Get() - assertJSON(t, out, expected, "sorting array of object by its key (name-string) in descending desc") - if len(jq.errors) == 0 { - t.Error("invalid path should return error/errors in SortBy") - } -} - -func TestJSONQ_SortBy_no_argument_expecting_error(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - SortBy() - jq.Get() - if jq.Error() == nil { - t.Error("expecting an error") - } -} - -func TestJSONQ_SortBy_more_than_two_argument_expecting_error(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - SortBy("name", "desc", "asc") - jq.Get() - if jq.Error() == nil { - t.Error("expecting an error") - } -} - -func TestJSONQ_SortBy_expecting_as_provided_node_is_not_list(t *testing.T) { - jq := New().FromString(jsonStr). - From("name"). - SortBy("name", "desc") - out := jq.Get() - expJSON := `"computers"` - assertJSON(t, out, expJSON) -} - -func TestJSONQ_SortBy_expecting_empty_as_provided_node_is_not_list(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items").Where("price", ">", 2500). - SortBy("name", "desc") - out := jq.Get() - expJSON := `[]` - assertJSON(t, out, expJSON) -} - -func TestJSONQ_Distinct(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Distinct("price") - expected := `[{"id":1,"name":"MacBook Pro 13 inch retina","price":1350},{"id":2,"name":"MacBook Pro 15 inch retina","price":1700},{"id":3,"name":"Sony VAIO","price":1200},{"id":4,"name":"Fujitsu","price":850},{"id":6,"name":"HP core i7","price":950}]` - out := jq.Get() - assertJSON(t, out, expected, "Distinct expecting result") -} - -func TestJSONQ_Distinct_expecting_error(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Distinct("invalid_key") - expected := `[]` - out := jq.Get() - assertJSON(t, out, expected, "Distinct expecting empty result") - if len(jq.Errors()) == 0 { - t.Error("failed to catch Distinct error") - } -} - -func TestJSONQ_Only(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items") - expected := `[{"id":1,"price":1350},{"id":2,"price":1700},{"id":3,"price":1200},{"id":4,"price":850},{"id":5,"price":850},{"id":6,"price":950},{"id":null,"price":850}]` - out := jq.Only("id", "price") - assertJSON(t, out, expected) -} - -func TestJSONQ_Only_with_distinct(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items").Distinct("price") - expected := `[{"id":1,"price":1350},{"id":2,"price":1700},{"id":3,"price":1200},{"id":4,"price":850},{"id":6,"price":950}]` - out := jq.Only("id", "price") - assertJSON(t, out, expected) -} - -func TestJSONQ_OnlyR(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items") - result, err := jq.OnlyR("name", "price") - if reflect.ValueOf(result).Type().String() != "*gojsonq.Result" && err != nil { - t.Error("failed to match Result type") - } -} - -func TestJSONQ_OnlyR_error(t *testing.T) { - jq := New().FromString(jsonStr). - From("invalid_path") - result, err := jq.OnlyR("name", "price") - if result != nil && err == nil { - t.Error("failed to catch error") - } -} - -func TestJSONQ_First_expecting_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items") - expected := `{"id":1,"name":"MacBook Pro 13 inch retina","price":1350}` - out := jq.First() - assertJSON(t, out, expected, "First expecting result") -} - -func TestJSONQ_First_expecting_empty_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Where("price", ">", 1800) - expected := `null` - out := jq.First() - assertJSON(t, out, expected, "First expecting empty result") -} - -func TestJSONQ_First_distinct_expecting_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items").Distinct("price").Where("price", "=", 850) - expected := `{"id":4,"name":"Fujitsu","price":850}` - out := jq.First() - assertJSON(t, out, expected, "First with distinct & where expecting result result") -} - -func TestJSONQ_FirstR(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items").Distinct("price").Where("price", "=", 850) - result, err := jq.FirstR() - if reflect.ValueOf(result).Type().String() != "*gojsonq.Result" && err != nil { - t.Error("failed to match Result type") - } -} - -func TestJSONQ_FirstR_error(t *testing.T) { - jq := New().FromString(jsonStr). - From("invalid").Distinct("price").Where("price", "=", 850) - result, err := jq.FirstR() - if result != nil && err == nil { - t.Error("failed to catch error") - } -} - -func TestJSONQ_Last_expecting_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items") - expected := `{"id":null,"name":"HP core i3 SSD","price":850}` - out := jq.Last() - assertJSON(t, out, expected, "Last expecting result") -} - -func TestJSONQ_Last_expecting_empty_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Where("price", ">", 1800) - expected := `null` - out := jq.Last() - assertJSON(t, out, expected, "Last expecting empty result") -} - -func TestJSONQ_Last_distinct_expecting_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items").Distinct("price").Where("price", "=", 850) - expected := `{"id":4,"name":"Fujitsu","price":850}` - out := jq.Last() - assertJSON(t, out, expected, "Last with distinct & where expecting result result") -} - -func TestJSONQ_LastR(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items").Distinct("price").Where("price", "=", 850) - result, err := jq.LastR() - if reflect.ValueOf(result).Type().String() != "*gojsonq.Result" && err != nil { - t.Error("failed to match Result type") - } -} - -func TestJSONQ_LastR_error(t *testing.T) { - jq := New().FromString(jsonStr). - From("invalid_path").Distinct("price").Where("price", "=", 850) - result, err := jq.LastR() - if result != nil && err == nil { - t.Error("failed to catch error") - } -} - -func TestJSONQ_Nth_expecting_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items") - expected := `{"id":1,"name":"MacBook Pro 13 inch retina","price":1350}` - out := jq.Nth(1) - assertJSON(t, out, expected, "Nth expecting result") -} - -func TestJSONQ_Nth_expecting_empty_result_with_error(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Where("price", ">", 1800) - expected := `null` - out := jq.Nth(1) - assertJSON(t, out, expected, "Nth expecting empty result with an error") - - if jq.Error() == nil { - t.Error("expecting an error for empty result nth value") - } -} - -func TestJSONQ_Nth_expecting_empty_result_with_error_index_out_of_range(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items") - expected := `null` - out := jq.Nth(100) - assertJSON(t, out, expected, "Nth expecting empty result with an error of index out of range") - - if jq.Error() == nil { - t.Error("expecting an error for empty result nth value") - } -} - -func TestJSONQ_Nth_expecting_result_from_last_using_negative_index(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items") - expected := `{"id":null,"name":"HP core i3 SSD","price":850}` - out := jq.Nth(-1) - assertJSON(t, out, expected, "Nth expecting result form last when providing -1") -} - -func TestJSONQ_Nth_expecting_error_providing_zero_as_index(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Where("price", ">", 1800) - jq.Nth(0) - if jq.Error() == nil { - t.Error("expecting error") - } -} - -func TestJSONQ_Nth_expecting_empty_result_as_node_is_map(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items.[0]") - out := jq.Nth(0) - expected := `null` - assertJSON(t, out, expected, "Nth expecting empty result if the node is a map") -} - -func TestJSONQ_Nth_expecting_empty_result_as_node_is_object(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items.[0]") - out := jq.Nth(1) - expected := `null` - assertJSON(t, out, expected, "Nth expecting empty result if the node is a object") -} - -func TestJSONQ_Nth_distinct_expecting_result(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items").Distinct("price") - expected := `{"id":1,"name":"MacBook Pro 13 inch retina","price":1350}` - out := jq.Nth(1) - assertJSON(t, out, expected, "Last with distinct & where expecting result result") -} - -func TestJSONQ_NthR(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items").Distinct("price").Where("price", "=", 850) - result, err := jq.NthR(1) - if reflect.ValueOf(result).Type().String() != "*gojsonq.Result" && err != nil { - t.Error("failed to match Result type") - } -} - -func TestJSONQ_NthR_error(t *testing.T) { - jq := New().FromString(jsonStr). - From("invalid_path").Distinct("price").Where("price", "=", 850) - result, err := jq.NthR(1) - if result != nil && err == nil { - t.Error("failed to catch error") - } -} - -func TestJSONQ_Find_simple_property(t *testing.T) { - jq := New().FromString(jsonStr) - out := jq.Find("name") - expected := `"computers"` - assertJSON(t, out, expected, "Find expecting name computers") -} - -func TestJSONQ_Find_nested_property(t *testing.T) { - jq := New().FromString(jsonStr) - out := jq.Find("vendor.items.[0]") - expected := `{"id":1,"name":"MacBook Pro 13 inch retina","price":1350}` - assertJSON(t, out, expected, "Find expecting a nested object") -} - -func TestJSONQ_FindR(t *testing.T) { - jq := New().FromString(jsonStr) - result, err := jq.FindR("vendor.items.[0]") - if reflect.ValueOf(result).Type().String() != "*gojsonq.Result" && err != nil { - t.Error("failed to match Result type") - } -} - -func TestJSONQ_FindR_error(t *testing.T) { - jq := New().FromString(jsonStr) - result, err := jq.FindR("invalid_path") - if result != nil && err == nil { - t.Error("failed to catch error") - } -} - -func TestJSONQ_Pluck_expecting_list_of_float64(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items") - out := jq.Pluck("price") - expected := `[1350,1700,1200,850,850,950,850]` - assertJSON(t, out, expected, "Pluck expecting prices from list of objects") -} - -func TestJSONQ_Pluck_expecting_empty_list_of_float64(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items") - out := jq.Pluck("invalid_prop") - expected := `[]` - assertJSON(t, out, expected, "Pluck expecting empty list from list of objects, because of invalid property name") -} - -func TestJSONQ_Pluck_expecting_with_distinct(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items").Distinct("price").Limit(3) - out := jq.Pluck("price") - expected := `[1350,1700,1200]` - assertJSON(t, out, expected, "Expecting distinct price with limit 3") -} - -func TestJSONQ_PluckR(t *testing.T) { - jq := New().FromString(jsonStr).From("vendor.items") - result, err := jq.PluckR("price") - if reflect.ValueOf(result).Type().String() != "*gojsonq.Result" && err != nil { - t.Error("failed to match Result type") - } -} - -func TestJSONQ_PluckR_error(t *testing.T) { - jq := New().FromString(jsonStr).From("invalid_path") - result, err := jq.PluckR("price") - if result != nil && err == nil { - t.Error("failed to catch error") - } -} - -func TestJSONQ_Count_expecting_int_from_list(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items") - out := jq.Count() - expected := `7` - assertJSON(t, out, expected, "Count expecting a int number of total item of an array") -} - -func TestJSONQ_Count_expecting_int_from_list_of_objects(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items.[0]") - out := jq.Count() - expected := `3` - assertJSON(t, out, expected, "Count expecting a int number of total item of an array of objects") -} - -func TestJSONQ_Count_expecting_int_from_objects(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - GroupBy("price") - out := jq.Count() - expected := `5` - assertJSON(t, out, expected, "Count expecting a int number of total item of an array of grouped objects") -} - -func TestJSONQ_Count_with_Distinct_expecting_int_from_objects(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items").Distinct("price") - out := jq.Count() - expected := `5` - assertJSON(t, out, expected, "Count expecting a int number of total item of an array of distinct priced objects") -} - -func TestJSONQ_Out_expecting_result(t *testing.T) { - type item struct { - ID int `json:"id"` - Name string `json:"name"` - Price int `json:"price"` - } - exptItm := item{ - ID: 1, - Name: "MacBook Pro 13 inch retina", - Price: 1350, - } - itm := item{} - jq := New().FromString(jsonStr). - From("vendor.items.[0]") - jq.Out(&itm) - assertInterface(t, exptItm, itm, "failed to get Out result") -} - -func TestJSONQ_Out_expecting_decoding_error(t *testing.T) { - type item struct { - ID bool `json:"id"` - Name string `json:"name"` - Price int `json:"price"` - } - itm := item{} - jq := New().FromString(jsonStr). - From("vendor.items.[0]") - jq.Out(&itm) - if jq.Error() == nil { - t.Errorf("failed to get Out decoding error: %v", jq.Error()) - } -} - -func TestJSONQ_Out_expecting_encoding_error(t *testing.T) { - type item struct { - ID bool `json:"id"` - Name string `json:"name"` - Price int `json:"price"` - } - itm := item{} - jq := New() - jq.jsonContent = math.Inf(1) - jq.Out(&itm) - if jq.Error() == nil { - t.Errorf("failed to get Out encoding error: %v", jq.Error()) - } -} - -func TestJSONQ_Writer_expecting_result(t *testing.T) { - var b bytes.Buffer - New().FromString(jsonStr).From("vendor.prices").Writer(&b) - expected := "[2400,2100,1200,400.87,89.9,150.1]\n" - assertInterface(t, expected, b.String(), "failed to get Writer result") -} - -func TestJSONQ_Writer_encoding_error(t *testing.T) { - var b bytes.Buffer - jq := New().FromString(jsonStr) - jq.jsonContent = math.Inf(1) - jq.Writer(&b) - if jq.Error() == nil { - t.Errorf("failed to get Writer encoding error: %v", jq.Error()) - } -} - -func TestJSONQ_Sum_of_array_numeric_values(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.prices") - out := jq.Sum() - expected := `6340.87` - assertJSON(t, out, expected, "Sum expecting sum an array") -} - -func TestJSONQ_Sum_of_array_objects_property_numeric_values(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items") - out := jq.Sum("price") - expected := `7750` - assertJSON(t, out, expected, "Sum expecting sum an array of objects property") -} - -func TestJSONQ_Sum_expecting_error_for_providing_property_of_array(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.prices") - jq.Sum("key") - if jq.Error() == nil { - t.Error("expecting: unnecessary property name for array") - } -} - -func TestJSONQ_Sum_expecting_error_for_not_providing_property_of_array_of_objects(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items") - jq.Sum() - if jq.Error() == nil { - t.Error("expecting: property name can not be empty for object") - } -} - -func TestJSONQ_Sum_expecting_error_for_not_providing_property_of_object(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items.[0]") - jq.Sum() - if jq.Error() == nil { - t.Error("expecting: property name can not be empty for object") - } -} - -func TestJSONQ_Sum_expecting_error_for_providing_invalid_property_of_array_of_objects(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items") - jq.Sum("invalid_property") - if jq.Error() == nil { - t.Error("expecting: property 'invalid_property' does not exist") - } -} - -func TestJSONQ_Sum_expecting_error_for_providing_invalid_property_of_object(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor") - jq.Sum("invalid_property") - if jq.Error() == nil { - t.Error("expecting: property 'invalid_property' does not exist") - } -} - -func TestJSONQ_Sum_expecting_error_for_providing_non_numeric_property_of_array_of_objects(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items") - jq.Sum("name") - if jq.Error() == nil { - t.Error("expecting: property 'MacBook Pro 13 inch retina' is not numeric") - } -} - -func TestJSONQ_Sum_expecting_error_for_providing_non_numeric_property_of_object(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor") - jq.Sum("name") - if jq.Error() == nil { - t.Error("expecting: property 'invalid_property' does not exist") - } -} - -func TestJSONQ_Sum_expecting_result_from_nested_object(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items.[0]") - out := jq.Sum("price") - expected := `1350` - assertJSON(t, out, expected, "Sum expecting result from nested object") -} - -func TestJSONQ_Sum_of_distinct_array_numeric_values(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items").Distinct("price").Limit(3) - out := jq.Sum("price") - expected := `4250` - assertJSON(t, out, expected, "Sum expecting sum a distinct & limited array") -} - -func TestJSONQ_Avg_array(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.prices") - out := jq.Avg() - expected := `1056.8116666666667` - assertJSON(t, out, expected, "Avg expecting average an array") -} - -func TestJSONQ_Avg_array_of_objects(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items") - out := jq.Avg("price") - expected := `1107.142857142857` - assertJSON(t, out, expected, "Avg expecting average an array of objects property") -} - -func TestJSONQ_Min_array(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.prices") - out := jq.Min() - expected := `89.9` - assertJSON(t, out, expected, "Min expecting min an array") -} - -func TestJSONQ_Min_array_of_objects(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items") - out := jq.Min("price") - expected := `850` - assertJSON(t, out, expected, "Min expecting min an array of objects property") -} - -func TestJSONQ_Max_array(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.prices") - out := jq.Max() - expected := `2400` - assertJSON(t, out, expected, "Max expecting max an array") -} - -func TestJSONQ_Max_array_of_objects(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items") - out := jq.Max("price") - expected := `1700` - assertJSON(t, out, expected, "Max expecting max an array of objects property") -} - -// TODO: Need to write some more combined query test -func TestJSONQ_CombinedWhereOrWhere(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Where("id", "=", 1). - OrWhere("name", "=", "Sony VAIO"). - Where("price", "=", 1200) - out := jq.Get() - expected := `[{"id":1,"name":"MacBook Pro 13 inch retina","price":1350},{"id":3,"name":"Sony VAIO","price":1200}]` - assertJSON(t, out, expected, "combined Where with orWhere") -} - -func TestJSONQ_CombinedWhereOrWhere_invalid_key(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Where("id", "=", 1). - OrWhere("invalid_key", "=", "Sony VAIO") - out := jq.Get() - expected := `[{"id":1,"name":"MacBook Pro 13 inch retina","price":1350}]` - assertJSON(t, out, expected, "combined Where with orWhere containing invalid key") -} - -func TestJSONQ_Get_with_Select_method(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Select("id", "name"). - Where("price", "=", 1350) - out := jq.Get() - expected := `[{"id":1,"name":"MacBook Pro 13 inch retina"}]` - assertJSON(t, out, expected, "Select method Using Get") -} - -func TestJSONQ_Get_with_nested_Select_method(t *testing.T) { - jq := New().FromString(jsonStrUsers). - From("users"). - Select("id as uid", "name.first as fname", "name.last") - out := jq.Get() - expected := `[{"fname":"John","last":"Ramboo","uid":1},{"fname":"Ethan","last":"Hunt","uid":2},{"fname":"John","last":"Doe","uid":3}]` - assertJSON(t, out, expected, "nested Select method using alias") -} - -func TestJSONQ_Get_with_nested_invalid_property_in_Select_method_expecting_error(t *testing.T) { - jq := New().FromString(jsonStrUsers). - From("users"). - Select("id as uid", "name.middle") - out := jq.Get() - if jq.Error() == nil { - t.Error("nested property in Select method failed to catch error") - } - expected := `[{"uid":1},{"uid":2},{"uid":3}]` - assertJSON(t, out, expected, "nested Select method using alias") -} - -func TestJSONQ_GetR(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items").Select("name") - result, err := jq.GetR() - if reflect.ValueOf(result).Type().String() != "*gojsonq.Result" && err != nil { - t.Error("failed to match Result type") - } -} - -func TestJSONQ_GetR_error(t *testing.T) { - jq := New().FromString(jsonStr). - From("invalid_path") - result, err := jq.GetR() - if result != nil && err == nil { - t.Error("failed to catch error") - } -} - -func TestJSONQ_Offset_method(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Offset(4) - out := jq.Get() - expected := `[{"id":5,"key":2300,"name":"HP core i5","price":850},{"id":6,"name":"HP core i7","price":950},{"id":null,"name":"HP core i3 SSD","price":850}]` - assertJSON(t, out, expected, "failed to offset records") -} - -func TestJSONQ_Offset_Where_method(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Offset(4).WhereNotNil("id") - out := jq.Get() - expected := `[{"id":5,"key":2300,"name":"HP core i5","price":850},{"id":6,"name":"HP core i7","price":950}]` - assertJSON(t, out, expected, "failed to limit records") -} - -func TestJSONQ_Offset_greater_than_the_original_value_with_Where_method(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Offset(40).WhereNotNil("id") - out := jq.Get() - expected := `[]` - assertJSON(t, out, expected, "failed to offset records when offset value is greater than the length of orignal result") -} - -func TestJSONQ_Limit_method(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Limit(2) - out := jq.Get() - expected := `[{"id":1,"name":"MacBook Pro 13 inch retina","price":1350},{"id":2,"name":"MacBook Pro 15 inch retina","price":1700}]` - assertJSON(t, out, expected, "failed to limit records") -} - -func TestJSONQ_Limit_Where_method(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Limit(2).WhereNotNil("id") - out := jq.Get() - expected := `[{"id":1,"name":"MacBook Pro 13 inch retina","price":1350},{"id":2,"name":"MacBook Pro 15 inch retina","price":1700}]` - assertJSON(t, out, expected, "failed to limit records") -} - -func TestJSONQ_Offset_invalid_number_should_return_error(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Offset(-2) - jq.Get() - if jq.Error() == nil { - t.Error("failed to catch invalid offset error") - } -} -func TestJSONQ_Limit_invalid_number_should_return_error(t *testing.T) { - jq := New().FromString(jsonStr). - From("vendor.items"). - Limit(-2) - jq.Get() - if jq.Error() == nil { - t.Error("failed to catch invalid limit error") - } -} - -func TestJSONQ_WhereLenEqual(t *testing.T) { - jq := New().FromString(jsonStrUsers). - From("users"). - WhereLenEqual("name.first", 4) - expected := `[{"id":1,"name":{"first":"John","last":"Ramboo"}},{"id":3,"name":{"first":"John","last":"Doe"}}]` - out := jq.Get() - assertJSON(t, out, expected, "single WhereLenEqual") -} - -func TestJSONQ_WhereLenNotEqual(t *testing.T) { - jq := New().FromString(jsonStrUsers). - From("users"). - WhereLenNotEqual("name.first", 4) - expected := `[{"id":2,"name":{"first":"Ethan","last":"Hunt"}}]` - out := jq.Get() - assertJSON(t, out, expected, "single WhereLenEqual") -} - -func TestJSONQ_More(t *testing.T) { - jq := New().FromString(jsonStrUsers).From("users") - jq.Where("id", "<", 3) - jq.More() // start query again - jq.Where("id", ">", 1) - expected := `[{"id":2,"name":{"first":"Ethan","last":"Hunt"}}]` - out := jq.Get() - assertJSON(t, out, expected, "More") -} - -// ======================== Benchmark ======================== // - -func Benchmark_Copy(b *testing.B) { - jq := New().FromString(jsonStr) - for n := 0; n < b.N; n++ { - jq.Copy() - } -} - -func Benchmark_Find(b *testing.B) { - jq := New().FromString(jsonStr) - for n := 0; n < b.N; n++ { - jq.Find("name") - } -} - -func Benchmark_Get(b *testing.B) { - jq := New().FromString(jsonStr) - for n := 0; n < b.N; n++ { - jq.Get() - } -} - -func Benchmark_From_Get(b *testing.B) { - jq := New().FromString(jsonStr) - for n := 0; n < b.N; n++ { - jq.From("vendor.items").Get() - } -} - -func Benchmark_From_Where_Get(b *testing.B) { - jq := New().FromString(jsonStr) - for n := 0; n < b.N; n++ { - jq.From("vendor.items").Where("id", "=", 1).Get() - } -} - -func Benchmark_From_Where_Select_Get(b *testing.B) { - jq := New().FromString(jsonStr) - for n := 0; n < b.N; n++ { - jq.From("vendor.items").Where("id", "=", 1).Select("id", "name").Get() - } -} - -func Benchmark_From_Sum(b *testing.B) { - jq := New().FromString(jsonStr) - for n := 0; n < b.N; n++ { - jq.From("vendor.items").Sum("price") - } -} - -func Benchmark_From_Avg(b *testing.B) { - jq := New().FromString(jsonStr) - for n := 0; n < b.N; n++ { - jq.From("vendor.items").Avg("price") - } -} - -func Benchmark_From_Count(b *testing.B) { - jq := New().FromString(jsonStr) - for n := 0; n < b.N; n++ { - jq.From("vendor.items").Count() - } -} - -func Benchmark_From_First(b *testing.B) { - jq := New().FromString(jsonStr) - for n := 0; n < b.N; n++ { - jq.From("vendor.items").First() - } -} - -func Benchmark_From_GroupBy(b *testing.B) { - jq := New().FromString(jsonStr) - for n := 0; n < b.N; n++ { - jq.From("vendor.items").GroupBy("price") - } -} - -func Benchmark_From_SortBy(b *testing.B) { - jq := New().FromString(jsonStr) - for n := 0; n < b.N; n++ { - jq.From("vendor.items").SortBy("price") - } -} - -func Benchmark_From_Where_nested_element_Get(b *testing.B) { - jq := New().FromString(jsonStrUsers) - for n := 0; n < b.N; n++ { - jq.From("users").WhereEqual("name.first", "John").Get() - } -} - -func Benchmark_From_WhereLenEqual_Get(b *testing.B) { - jq := New().FromString(jsonStr) - for n := 0; n < b.N; n++ { - jq.From("vendor.items").WhereLenEqual("name", 10).Get() - } -} diff --git a/json/gojsonq/jsonq_testdata_test.go b/json/gojsonq/jsonq_testdata_test.go deleted file mode 100644 index d7aa211..0000000 --- a/json/gojsonq/jsonq_testdata_test.go +++ /dev/null @@ -1,167 +0,0 @@ -package gojsonq - -import ( - "bytes" - "encoding/json" - "io/ioutil" - "os" - "testing" -) - -// ==================== Test Data=================== -// ==================== DO NOT EDIT=================== -var ( - jsonStr = ` -{ - "name":"computers", - "description":"List of computer products", - "vendor":{ - "name":"Star Trek", - "email":"info@example.com", - "website":"www.example.com", - "items":[ - { - "id":1, - "name":"MacBook Pro 13 inch retina", - "price":1350 - }, - { - "id":2, - "name":"MacBook Pro 15 inch retina", - "price":1700 - }, - { - "id":3, - "name":"Sony VAIO", - "price":1200 - }, - { - "id":4, - "name":"Fujitsu", - "price":850 - }, - { - "id":5, - "name":"HP core i5", - "price":850, - "key": 2300 - }, - { - "id":6, - "name":"HP core i7", - "price":950 - }, - { - "id":null, - "name":"HP core i3 SSD", - "price":850 - } - ], - "prices":[ - 2400, - 2100, - 1200, - 400.87, - 89.90, - 150.10 - ], - "names":[ - "John Doe", - "Jane Doe", - "Tom", - "Jerry", - "Nicolas", - "Abby" - ] - } -} -` - jsonStrUsers = `{ - "users":[ - { - "id":1, - "name":{ - "first":"John", - "last":"Ramboo" - } - }, - { - "id":2, - "name":{ - "first":"Ethan", - "last":"Hunt" - } - }, - { - "id":3, - "name":{ - "first":"John", - "last":"Doe" - } - } - ] -}` -) - -// ================= Test Helpers=========================== - -func createTestFile(t *testing.T, filename string) (string, func()) { - file, err := ioutil.TempFile("", filename) - if err != nil { - t.Errorf("failed to create %s test file %v", filename, err) - } - - // create data.json file from the jsonStr above - if _, err := file.Write([]byte(jsonStr)); err != nil { - t.Errorf("failed to create %s test file %v", filename, err) - } - - return file.Name(), func() { - if err := os.Remove(file.Name()); err != nil { - t.Errorf("failed to remove %s test file %v", filename, err) - } - } -} - -func assertJSON(t *testing.T, v interface{}, expJSON string, tag ...string) { - bb, err := json.Marshal(v) - if err != nil { - t.Errorf("failed to marshal: %v", err) - } - eb := []byte(expJSON) - if !bytes.Equal(bb, eb) { - if len(tag) > 0 { - t.Errorf("Tag: %s\nExpected: %v\nGot: %v", tag[0], expJSON, string(bb)) - } else { - t.Errorf("Expected: %v\nGot: %v", expJSON, string(bb)) - } - } -} - -func assertInterface(t *testing.T, x, y interface{}, tag ...string) { - bbX, err := json.Marshal(x) - if err != nil { - t.Errorf("failed to marshal x: %v", err) - } - - bbY, err := json.Marshal(y) - if err != nil { - t.Errorf("failed to marshal x: %v", err) - } - - if !bytes.Equal(bbX, bbY) { - if len(tag) > 0 { - t.Errorf("Tag: %s\nExpected: %v\nGot: %v", tag[0], x, y) - } else { - t.Errorf("Expected: %v\nGot: %v", x, y) - } - } -} - -// cDecoder will be used as a custom decoder for testing// though it use std lib -type cDecoder struct { -} - -func (c *cDecoder) Decode(data []byte, v interface{}) error { - return json.Unmarshal(data, &v) // let's assume this is a custom unmarshaler -} diff --git a/json/gojsonq/option.go b/json/gojsonq/option.go deleted file mode 100644 index 9a568ec..0000000 --- a/json/gojsonq/option.go +++ /dev/null @@ -1,46 +0,0 @@ -package gojsonq - -import "errors" - -// option describes type for providing configuration options to JSONQ -type option struct { - decoder Decoder - separator string -} - -// OptionFunc represents a contract for option func, it basically set options to jsonq instance options -type OptionFunc func(*JSONQ) error - -// SetDecoder take a custom decoder to decode JSON -// Deprecated - use WithDecoder -func SetDecoder(u Decoder) OptionFunc { - return WithDecoder(u) -} - -// SetSeparator set custom separator for traversing child node, default separator is DOT (.) -// Deprecated - use WithSeparator -func SetSeparator(s string) OptionFunc { - return WithSeparator(s) -} - -// WithDecoder take a custom decoder to decode JSON -func WithDecoder(u Decoder) OptionFunc { - return func(j *JSONQ) error { - if u == nil { - return errors.New("decoder can not be nil") - } - j.option.decoder = u - return nil - } -} - -// WithSeparator set custom separator for traversing child node, default separator is DOT (.) -func WithSeparator(s string) OptionFunc { - return func(j *JSONQ) error { - if s == "" { - return errors.New("separator can not be empty") - } - j.option.separator = s - return nil - } -} diff --git a/json/gojsonq/option_test.go b/json/gojsonq/option_test.go deleted file mode 100644 index 8ccf40e..0000000 --- a/json/gojsonq/option_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package gojsonq - -import ( - "testing" -) - -func TestWithDecoder(t *testing.T) { - jq := New(WithDecoder(&cDecoder{})) - if jq.option.decoder == nil { - t.Error("failed to set decoder as option") - } -} - -func TestWithDecoder_with_nil_expecting_an_error(t *testing.T) { - jq := New(WithDecoder(nil)) - if jq.Error() == nil { - t.Error("failed to catch nil in WithDecoder") - } -} - -func TestWithSeparator(t *testing.T) { - jq := New(WithSeparator("->")) - if jq.option.separator != "->" { - t.Error("failed to set separator as option") - } -} - -func TestWithSeparator_with_nil_expecting_an_error(t *testing.T) { - jq := New(WithSeparator("")) - if jq.Error() == nil { - t.Error("failed to catch nil in WithSeparator") - } -} - -// to increase the code coverage; will remove in major release -func TestSetDecoder(t *testing.T) { - jq := New(SetDecoder(&cDecoder{})) - if jq.option.decoder == nil { - t.Error("failed to set decoder as option") - } -} - -func TestSetDecoder_with_nil_expecting_an_error(t *testing.T) { - jq := New(SetDecoder(nil)) - if jq.Error() == nil { - t.Error("failed to catch nil in SetDecoder") - } -} - -func TestSetSeparator(t *testing.T) { - jq := New(SetSeparator("->")) - if jq.option.separator != "->" { - t.Error("failed to set separator as option") - } -} - -func TestSetSeparator_with_nil_expecting_an_error(t *testing.T) { - jq := New(SetSeparator("")) - if jq.Error() == nil { - t.Error("failed to catch nil in SetSeparator") - } -} diff --git a/json/gojsonq/query.go b/json/gojsonq/query.go deleted file mode 100644 index e43de56..0000000 --- a/json/gojsonq/query.go +++ /dev/null @@ -1,305 +0,0 @@ -package gojsonq - -import ( - "fmt" - "reflect" - "strings" -) - -const ( - operatorEq = "=" - operatorEqEng = "eq" - operatorNotEq = "!=" - operatorNotEqEng = "neq" - operatorNotEqAnother = "<>" - operatorGt = ">" - operatorGtEng = "gt" - operatorLt = "<" - operatorLtEng = "lt" - operatorGtE = ">=" - operatorGtEEng = "gte" - operatorLtE = "<=" - operatorLtEEng = "lte" - operatorStrictContains = "strictContains" - operatorContains = "contains" - operatorEndsWith = "endsWith" - operatorStartsWith = "startsWith" - operatorIn = "in" - operatorNotIn = "notIn" - operatorLenEq = "leneq" - operatorLenNotEq = "lenneq" - operatorLenGt = "lengt" - operatorLenGte = "lengte" - operatorLenLt = "lenlt" - operatorLenLte = "lenlte" -) - -func defaultQueries() map[string]QueryFunc { - return map[string]QueryFunc{ - operatorEq: eq, - operatorEqEng: eq, - operatorNotEq: neq, - operatorNotEqEng: neq, - operatorNotEqAnother: neq, - operatorGt: gt, - operatorGtEng: gt, - operatorLt: lt, - operatorLtEng: lt, - operatorGtE: gte, - operatorGtEEng: gte, - operatorLtE: lte, - operatorLtEEng: lte, - operatorStrictContains: strStrictContains, - operatorContains: strContains, - operatorStartsWith: strStartsWith, - operatorEndsWith: strEndsWith, - operatorIn: in, - operatorNotIn: notIn, - operatorLenEq: lenEq, - operatorLenNotEq: lenNotEq, - operatorLenGt: lenGt, - operatorLenGte: lenGte, - operatorLenLt: lenLt, - operatorLenLte: lenLte, - } -} - -// QueryFunc describes a conditional function which perform comparison -type QueryFunc func(x, y interface{}) (bool, error) - -// eq checks whether x, y are deeply eq -func eq(x, y interface{}) (bool, error) { - // if the y value is numeric (int/int8-int64/float32/float64) then convert to float64 - if fv, ok := toFloat64(y); ok { - y = fv - } - return reflect.DeepEqual(x, y), nil -} - -// neq checks whether x, y are deeply not equal -func neq(x, y interface{}) (bool, error) { - b, err := eq(x, y) - return !b, err -} - -// gt checks whether x is greather than y -func gt(x, y interface{}) (bool, error) { - xv, ok := x.(float64) - if !ok { - return false, fmt.Errorf("%v must be numeric", x) - } - // if the y value is numeric (int/int8-int64/float32/float64) then convert to float64 - if fv, ok := toFloat64(y); ok { - return xv > fv, nil - } - return false, nil -} - -// lt checks whether x is less than y -func lt(x, y interface{}) (bool, error) { - xv, ok := x.(float64) - if !ok { - return false, fmt.Errorf("%v must be numeric", x) - } - // if the y value is numeric (int/int8-int64/float32/float64) then convert to float64 - if fv, ok := toFloat64(y); ok { - return xv < fv, nil - } - return false, nil -} - -// gte checks whether x is greater than or equal to y -func gte(x, y interface{}) (bool, error) { - xv, ok := x.(float64) - if !ok { - return false, fmt.Errorf("%v must be numeric", x) - } - // if the y value is numeric (int/int8-int64/float32/float64) then convert to float64 - if fv, ok := toFloat64(y); ok { - return xv >= fv, nil - } - return false, nil -} - -// lte checks whether x is less than or equal to y -func lte(x, y interface{}) (bool, error) { - xv, ok := x.(float64) - if !ok { - return false, fmt.Errorf("%v must be numeric", x) - } - // if the y value is numeric (int/int8-int64/float32/float64) then convert to float64 - if fv, ok := toFloat64(y); ok { - return xv <= fv, nil - } - return false, nil -} - -// strStrictContains checks if x contains y -// This is case sensitive search -func strStrictContains(x, y interface{}) (bool, error) { - xv, okX := x.(string) - if !okX { - return false, fmt.Errorf("%v must be string", x) - } - yv, okY := y.(string) - if !okY { - return false, fmt.Errorf("%v must be string", y) - } - return strings.Contains(xv, yv), nil -} - -// strContains checks if x contains y -// This is case insensitive search -func strContains(x, y interface{}) (bool, error) { - xv, okX := x.(string) - if !okX { - return false, fmt.Errorf("%v must be string", x) - } - yv, okY := y.(string) - if !okY { - return false, fmt.Errorf("%v must be string", y) - } - return strings.Contains(strings.ToLower(xv), strings.ToLower(yv)), nil -} - -// strStartsWith checks if x starts with y -func strStartsWith(x, y interface{}) (bool, error) { - xv, okX := x.(string) - if !okX { - return false, fmt.Errorf("%v must be string", x) - } - yv, okY := y.(string) - if !okY { - return false, fmt.Errorf("%v must be string", y) - } - return strings.HasPrefix(xv, yv), nil -} - -// strEndsWith checks if x ends with y -func strEndsWith(x, y interface{}) (bool, error) { - xv, okX := x.(string) - if !okX { - return false, fmt.Errorf("%v must be string", x) - } - yv, okY := y.(string) - if !okY { - return false, fmt.Errorf("%v must be string", y) - } - return strings.HasSuffix(xv, yv), nil -} - -// in checks if x exists in y e.g: in("id", []int{1,3,5,8}) -func in(x, y interface{}) (bool, error) { - if yv, ok := y.([]string); ok { - for _, v := range yv { - if ok, _ := eq(x, v); ok { - return true, nil - } - } - } - if yv, ok := y.([]int); ok { - for _, v := range yv { - if ok, _ := eq(x, v); ok { - return true, nil - } - } - } - if yv, ok := y.([]float64); ok { - for _, v := range yv { - if ok, _ := eq(x, v); ok { - return true, nil - } - } - } - return false, nil -} - -// notIn checks if x doesn't exists in y e.g: in("id", []int{1,3,5,8}) -func notIn(x, y interface{}) (bool, error) { - b, err := in(x, y) - return !b, err -} - -// lenEq checks if the string/array/list value is equal -func lenEq(x, y interface{}) (bool, error) { - yv, ok := y.(int) - if !ok { - return false, fmt.Errorf("%v must be integer", y) - } - xv, err := length(x) - if err != nil { - return false, err - } - - return xv == yv, nil -} - -// lenNotEq checks if the string/array/list value is not equal -func lenNotEq(x, y interface{}) (bool, error) { - yv, ok := y.(int) - if !ok { - return false, fmt.Errorf("%v must be integer", y) - } - xv, err := length(x) - if err != nil { - return false, err - } - - return xv != yv, nil -} - -// lenGt checks if the string/array/list value is greater -func lenGt(x, y interface{}) (bool, error) { - yv, ok := y.(int) - if !ok { - return false, fmt.Errorf("%v must be integer", y) - } - xv, err := length(x) - if err != nil { - return false, err - } - - return xv > yv, nil -} - -// lenLt checks if the string/array/list value is less -func lenLt(x, y interface{}) (bool, error) { - yv, ok := y.(int) - if !ok { - return false, fmt.Errorf("%v must be integer", y) - } - xv, err := length(x) - if err != nil { - return false, err - } - - return xv < yv, nil -} - -// lenGte checks if the string/array/list value is greater than equal -func lenGte(x, y interface{}) (bool, error) { - yv, ok := y.(int) - if !ok { - return false, fmt.Errorf("%v must be integer", y) - } - xv, err := length(x) - if err != nil { - return false, err - } - - return xv >= yv, nil -} - -// lenLte checks if the string/array/list value is less than equal -func lenLte(x, y interface{}) (bool, error) { - yv, ok := y.(int) - if !ok { - return false, fmt.Errorf("%v must be integer", y) - } - xv, err := length(x) - if err != nil { - return false, err - } - - return xv <= yv, nil -} diff --git a/json/gojsonq/query_test.go b/json/gojsonq/query_test.go deleted file mode 100644 index 6ed3d76..0000000 --- a/json/gojsonq/query_test.go +++ /dev/null @@ -1,797 +0,0 @@ -package gojsonq - -import ( - "testing" -) - -func Test_eq(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - // our expectation for json unmarshaller is little bit different. here 9 provided by user will be equal to float64 9 - x: 9.0, - y: 9, - expected: true, - }, - { - x: 110, - y: 120, - expected: false, - }, - { - x: 10.09, - y: 10.09, - expected: true, - }, - { - x: 10.09, - y: 10.89, - expected: false, - }, - { - x: "john", - y: "john", - expected: true, - }, - { - x: "tom", - y: "jane", - expected: false, - }, - { - x: "", - y: "", - expected: true, - }, - { - x: nil, - y: nil, - expected: true, - }, - } - - for _, tc := range testCases { - if o, _ := eq(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_neq(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - x: 9.0, // as x is out json unmarshal value which is float64 - y: 9, // our expectation for json unmarshalar is little bit different. here 9 provided by user will be equal to float64 9 - expected: false, - }, - { - x: 110, - y: 120, - expected: true, - }, - { - x: 10.09, - y: 10.09, - expected: false, - }, - { - x: 10.09, - y: 10.89, - expected: true, - }, - { - x: "john", - y: "john", - expected: false, - }, - { - x: "tom", - y: "jane", - expected: true, - }, - { - x: "", - y: "", - expected: false, - }, - { - x: nil, - y: nil, - expected: false, - }, - } - - for _, tc := range testCases { - if o, _ := neq(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_gt(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - x: float64(10), - y: 5, - expected: true, - }, - { - x: float64(10), - y: 15, - expected: false, - }, - { - x: float64(10), - y: "10", - expected: false, - }, - { - x: "101", - y: "101", - expected: false, - }, - } - - for _, tc := range testCases { - if o, _ := gt(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_lt(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - x: float64(10), - y: 5, - expected: false, - }, - { - x: float64(10), - y: 15, - expected: true, - }, - { - x: float64(10), - y: "10", - expected: false, - }, - { - x: "101", - y: "101", - expected: false, - }, - } - - for _, tc := range testCases { - if o, _ := lt(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_gte(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - x: float64(10), - y: 5, - expected: true, - }, - { - x: float64(10), - y: 15, - expected: false, - }, - { - x: float64(18), - y: 18, - expected: true, - }, - { - x: float64(30.9), - y: 30.9, - expected: true, - }, - { - x: float64(10), - y: "10", - expected: false, - }, - { - x: "101", - y: "101", - expected: false, - }, - } - - for _, tc := range testCases { - if o, _ := gte(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_lte(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - x: float64(10), - y: 5, - expected: false, - }, - { - x: float64(10), - y: 15, - expected: true, - }, - { - x: float64(18), - y: 18, - expected: true, - }, - { - x: float64(30.9), - y: 30.9, - expected: true, - }, - { - x: float64(40.9), - y: 30.9, - expected: false, - }, - { - x: float64(10), - y: "10", - expected: false, - }, - { - x: "101", - y: "101", - expected: false, - }, - } - - for _, tc := range testCases { - if o, _ := lte(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_strStrictContains(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - x: float64(10), - y: "10", - expected: false, - }, - { - x: float64(11), - y: float64(11), - expected: false, - }, - { - x: "131", - y: float64(131), - expected: false, - }, - { - x: "458", - y: "458", - expected: true, - }, - { - x: "arch", - y: "arch", - expected: true, - }, - { - x: "Arch", - y: "arch", - expected: false, - }, - } - - for _, tc := range testCases { - if o, _ := strStrictContains(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_strContains(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - x: float64(10), - y: "10", - expected: false, - }, - { - x: float64(11), - y: float64(11), - expected: false, - }, - { - x: "131", - y: float64(131), - expected: false, - }, - { - x: "458", - y: "458", - expected: true, - }, - { - x: "arch", - y: "arch", - expected: true, - }, - { - x: "Arch", - y: "arcH", - expected: true, - }, - } - - for _, tc := range testCases { - if o, _ := strContains(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_strStartsWith(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - x: float64(10), - y: "10", - expected: false, - }, - { - x: float64(11), - y: float64(11), - expected: false, - }, - { - x: "131", - y: float64(131), - expected: false, - }, - { - x: "458", - y: "458", - expected: true, - }, - { - x: "arch", - y: "arch", - expected: true, - }, - { - x: "erik", - y: "er", - expected: true, - }, - } - - for _, tc := range testCases { - if o, _ := strStartsWith(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_strEndsWith(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - x: float64(10), - y: "10", - expected: false, - }, - { - x: float64(11), - y: float64(11), - expected: false, - }, - { - x: "131", - y: float64(131), - expected: false, - }, - { - x: "458", - y: "458", - expected: true, - }, - { - x: "arch", - y: "arch", - expected: true, - }, - { - x: "sky", - y: "ky", - expected: true, - }, - } - - for _, tc := range testCases { - if o, _ := strEndsWith(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_in(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - x: "sky", - y: []string{"river", "sun", "moon"}, - expected: false, - }, - { - x: "sun", - y: []string{"river", "sun", "moon"}, - expected: true, - }, - { - x: float64(10), // ass json numeric value will be treat as float64 - y: []int{11, 12, 14, 18}, - expected: false, - }, - { - x: float64(14), // ass json numeric value will be treat as float64 - y: []int{11, 12, 14, 18}, - expected: true, - }, - { - x: float64(10), - y: []float64{11, 12, 14, 18}, - expected: false, - }, - { - x: float64(14), - y: []float64{11, 12, 14, 18}, - expected: true, - }, - } - - for _, tc := range testCases { - if o, _ := in(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_notIn(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - x: "sky", - y: []string{"river", "sun", "moon"}, - expected: true, - }, - { - x: "sun", - y: []string{"river", "sun", "moon"}, - expected: false, - }, - { - x: float64(10), // ass json numeric value will be treat as float64 - y: []int{11, 12, 14, 18}, - expected: true, - }, - { - x: float64(14), // ass json numeric value will be treat as float64 - y: []int{11, 12, 14, 18}, - expected: false, - }, - { - x: float64(10), - y: []float64{11, 12, 14, 18}, - expected: true, - }, - { - x: float64(14), - y: []float64{11, 12, 14, 18}, - expected: false, - }, - } - - for _, tc := range testCases { - if o, _ := notIn(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_lenEq(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - x: []interface{}{"river", "sun", "moon"}, - y: 3, - expected: true, - }, - { - x: "sun", - y: 3, - expected: true, - }, - { - x: 100, - y: 3, - expected: false, - }, - { - x: 100, - y: 3, - expected: false, - }, - { - x: "moon", - y: 4.0, - expected: false, - }, - } - - for _, tc := range testCases { - if o, _ := lenEq(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_lenNotEq(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - x: []interface{}{"river", "sun", "moon"}, - y: 6, - expected: true, - }, - { - x: "sun", - y: 8, - expected: true, - }, - { - x: "moon", - y: 4, - expected: false, - }, - { - x: 100.6, - y: 4, - expected: false, - }, - { - x: "moon", - y: 4.0, - expected: false, - }, - } - - for _, tc := range testCases { - if o, _ := lenNotEq(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_lenGt(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - x: []interface{}{"river", "sun", "moon"}, - y: 6, - expected: false, - }, - { - x: "sun", - y: 2, - expected: true, - }, - { - x: "moon", - y: 2, - expected: true, - }, - { - x: 100.6, - y: 4, - expected: false, - }, - { - x: "moon", - y: 4.0, - expected: false, - }, - } - - for _, tc := range testCases { - if o, _ := lenGt(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_lenLt(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - x: []interface{}{"river", "sun", "moon"}, - y: 6, - expected: true, - }, - { - x: "sun", - y: 20, - expected: true, - }, - { - x: "moon", - y: 3, - expected: false, - }, - { - x: 100.6, - y: 4, - expected: false, - }, - { - x: "john", - y: 4.0, - expected: false, - }, - } - - for _, tc := range testCases { - if o, _ := lenLt(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_lenGte(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - x: []interface{}{"river", "sun", "moon"}, - y: 3, - expected: true, - }, - { - x: "sun", - y: 2, - expected: true, - }, - { - x: "jane", - y: 5, - expected: false, - }, - { - x: 100.6, - y: 4, - expected: false, - }, - { - x: "moon", - y: 4.0, - expected: false, - }, - } - - for _, tc := range testCases { - if o, _ := lenGte(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_lenLte(t *testing.T) { - testCases := []struct { - x interface{} - y interface{} - expected bool - }{ - { - x: []interface{}{"river", "sun", "moon"}, - y: 3, - expected: true, - }, - { - x: "sun", - y: 12, - expected: true, - }, - { - x: "jane", - y: 2, - expected: false, - }, - { - x: 100.6, - y: 4, - expected: false, - }, - { - x: "moon", - y: 4.0, - expected: false, - }, - } - - for _, tc := range testCases { - if o, _ := lenLte(tc.x, tc.y); o != tc.expected { - t.Errorf("for %v expected: %v got: %v", tc.x, tc.expected, o) - } - } -} - -func Test_loadDefaultQueryMap(t *testing.T) { - if len(defaultQueries()) != 25 { - t.Error("mismatched default query map size") - } -} diff --git a/json/gojsonq/result.go b/json/gojsonq/result.go deleted file mode 100644 index e8385a5..0000000 --- a/json/gojsonq/result.go +++ /dev/null @@ -1,516 +0,0 @@ -package gojsonq - -import ( - "fmt" - "reflect" - "strings" - "time" -) - -const errMessage = "gojsonq: wrong method call for %v" - -// Available named error values -var ( - ErrExpectsPointer = fmt.Errorf("gojsonq: failed to unmarshal, expects pointer") - ErrImmutable = fmt.Errorf("gojsonq: failed to unmarshal, target is not mutable") - ErrTypeMismatch = fmt.Errorf("gojsonq: failed to unmarshal, target type misatched") -) - -// NewResult return an instance of Result -func NewResult(v interface{}) *Result { - return &Result{value: v} -} - -// Result represent custom type -type Result struct { - value interface{} -} - -// Nil check the query has result or not -func (r *Result) Nil() bool { - return r.value == nil -} - -// As sets the value of Result to v; It does not support methods with argument available in Result -func (r *Result) As(v interface{}) error { - if r.value != nil { - rv := reflect.ValueOf(v) - if rv.Kind() != reflect.Ptr || rv.IsNil() { - return ErrExpectsPointer - } - - elm := rv.Elem() - if !elm.CanSet() { - return ErrImmutable - } - - method := rv.Type().String() - methodMap := map[string]string{ - "*string": "String", "*bool": "Bool", "*time.Duration": "Duration", - "*int": "Int", "*int8": "Int8", "*int16": "Int16", "*int32": "Int32", - "*uint": "Uint", "*uint8": "Uint8", "*uint16": "Uint16", "*uint32": "Uint32", - "*float32": "Float32", "*float64": "Float64", - - "*[]string": "StringSlice", "*[]bool": "BoolSlice", "*[]time.Duration": "DurationSlice", - "*[]int": "IntSlice", "*[]int8": "Int8Slice", "*[]int16": "Int16Slice", "*[]int32": "Int32Slice", - "*[]uint": "UintSlice", "*[]uint8": "Uint8Slice", "*[]uint16": "Uint16Slice", "*[]uint32": "Uint32Slice", - "*[]float32": "Float32Slice", "*[]float64": "Float64Slice", - } - - if methodMap[method] == "" { - return fmt.Errorf("gojsonq: type [%T] is not available", v) - } - - vv := reflect.ValueOf(r).MethodByName(methodMap[method]).Call(nil) - if vv != nil { - if vv[1].Interface() != nil { - return ErrTypeMismatch - } - rv.Elem().Set(vv[0]) - } - } - return nil -} - -// Bool assert the result to boolean value -func (r *Result) Bool() (bool, error) { - switch v := r.value.(type) { - case bool: - return v, nil - default: - return false, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Time assert the result to time.Time -func (r *Result) Time(layout string) (time.Time, error) { - switch v := r.value.(type) { - case string: - return time.Parse(layout, v) - default: - return time.Time{}, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Duration assert the result to time.Duration -func (r *Result) Duration() (time.Duration, error) { - switch v := r.value.(type) { - case float64: - return time.Duration(v), nil - case string: - if strings.ContainsAny(v, "nsuµmh") { - return time.ParseDuration(v) - } - return time.ParseDuration(v + "ns") - default: - return time.Duration(0), fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// String assert the result to String -func (r *Result) String() (string, error) { - switch v := r.value.(type) { - case string: - return v, nil - default: - return "", fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Int assert the result to int -func (r *Result) Int() (int, error) { - switch v := r.value.(type) { - case float64: - return int(v), nil - default: - return 0, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Int8 assert the result to int8 -func (r *Result) Int8() (int8, error) { - switch v := r.value.(type) { - case float64: - return int8(v), nil - default: - return 0, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Int16 assert the result to int16 -func (r *Result) Int16() (int16, error) { - switch v := r.value.(type) { - case float64: - return int16(v), nil - default: - return 0, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Int32 assert the result to int32 -func (r *Result) Int32() (int32, error) { - switch v := r.value.(type) { - case float64: - return int32(v), nil - default: - return 0, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Int64 assert the result to int64 -func (r *Result) Int64() (int64, error) { - switch v := r.value.(type) { - case float64: - return int64(v), nil - default: - return 0, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Uint assert the result to uint -func (r *Result) Uint() (uint, error) { - switch v := r.value.(type) { - case float64: - return uint(v), nil - default: - return 0, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Uint8 assert the result to uint8 -func (r *Result) Uint8() (uint8, error) { - switch v := r.value.(type) { - case float64: - return uint8(v), nil - default: - return 0, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Uint16 assert the result to uint16 -func (r *Result) Uint16() (uint16, error) { - switch v := r.value.(type) { - case float64: - return uint16(v), nil - default: - return 0, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Uint32 assert the result to uint32 -func (r *Result) Uint32() (uint32, error) { - switch v := r.value.(type) { - case float64: - return uint32(v), nil - default: - return 0, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Uint64 assert the result to uint64 -func (r *Result) Uint64() (uint64, error) { - switch v := r.value.(type) { - case float64: - return uint64(v), nil - default: - return 0, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Float32 assert the result to float32 -func (r *Result) Float32() (float32, error) { - switch v := r.value.(type) { - case float64: - return float32(v), nil - default: - return 0, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Float64 assert the result to 64 -func (r *Result) Float64() (float64, error) { - switch v := r.value.(type) { - case float64: - return v, nil - default: - return 0, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// TODO: Slice related methods - ideally they should return nil instead of empty struct -// in case of any error or no result. To keep compatibility with older version refactored -// to use make, in order to create slices. - -// BoolSlice assert the result to []bool -func (r *Result) BoolSlice() ([]bool, error) { - switch v := r.value.(type) { - case []interface{}: - var bb = make([]bool, 0) - for _, si := range v { - if s, ok := si.(bool); ok { - bb = append(bb, s) - } - } - return bb, nil - default: - return []bool{}, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// TimeSlice assert the result to []time.Time -func (r *Result) TimeSlice(layout string) ([]time.Time, error) { - switch v := r.value.(type) { - case []interface{}: - var tt = make([]time.Time, 0) - for _, si := range v { - if s, ok := si.(string); ok { - ts, err := time.Parse(layout, s) - if err != nil { - return tt, err - } - tt = append(tt, ts) - } - } - return tt, nil - default: - return []time.Time{}, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// DurationSlice assert the result to []time.Duration -func (r *Result) DurationSlice() ([]time.Duration, error) { - switch v := r.value.(type) { - case []interface{}: - var dd = make([]time.Duration, 0) - for _, si := range v { - if s, ok := si.(string); ok { - var d time.Duration - var err error - if strings.ContainsAny(s, "nsuµmh") { - d, err = time.ParseDuration(s) - } else { - d, err = time.ParseDuration(s + "ns") - } - if err != nil { - return dd, err - } - dd = append(dd, d) - } - - if v, ok := si.(float64); ok { - dd = append(dd, time.Duration(v)) - } - } - return dd, nil - default: - return []time.Duration{}, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// StringSlice assert the result to []string -func (r *Result) StringSlice() ([]string, error) { - switch v := r.value.(type) { - case []interface{}: - var ss = make([]string, 0) - for _, si := range v { - if s, ok := si.(string); ok { - ss = append(ss, s) - } - } - return ss, nil - default: - return []string{}, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// IntSlice assert the result to []int -func (r *Result) IntSlice() ([]int, error) { - switch v := r.value.(type) { - case []interface{}: - var ii = make([]int, 0) - for _, si := range v { - if s, ok := si.(float64); ok { - ii = append(ii, int(s)) - } - } - return ii, nil - default: - return []int{}, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Int8Slice assert the result to []int8 -func (r *Result) Int8Slice() ([]int8, error) { - switch v := r.value.(type) { - case []interface{}: - var ii = make([]int8, 0) - for _, si := range v { - if s, ok := si.(float64); ok { - ii = append(ii, int8(s)) - } - } - return ii, nil - default: - return []int8{}, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Int16Slice assert the result to []int16 -func (r *Result) Int16Slice() ([]int16, error) { - switch v := r.value.(type) { - case []interface{}: - var ii = make([]int16, 0) - for _, si := range v { - if s, ok := si.(float64); ok { - ii = append(ii, int16(s)) - } - } - return ii, nil - default: - return []int16{}, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Int32Slice assert the result to []int32 -func (r *Result) Int32Slice() ([]int32, error) { - switch v := r.value.(type) { - case []interface{}: - var ii = make([]int32, 0) - for _, si := range v { - if s, ok := si.(float64); ok { - ii = append(ii, int32(s)) - } - } - return ii, nil - default: - return []int32{}, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Int64Slice assert the result to []int64 -func (r *Result) Int64Slice() ([]int64, error) { - switch v := r.value.(type) { - case []interface{}: - var ii = make([]int64, 0) - for _, si := range v { - if s, ok := si.(float64); ok { - ii = append(ii, int64(s)) - } - } - return ii, nil - default: - return []int64{}, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// UintSlice assert the result to []uint -func (r *Result) UintSlice() ([]uint, error) { - switch v := r.value.(type) { - case []interface{}: - var uu = make([]uint, 0) - for _, si := range v { - if s, ok := si.(float64); ok { - uu = append(uu, uint(s)) - } - } - return uu, nil - default: - return []uint{}, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Uint8Slice assert the result to []uint8 -func (r *Result) Uint8Slice() ([]uint8, error) { - switch v := r.value.(type) { - case []interface{}: - var uu = make([]uint8, 0) - for _, si := range v { - if s, ok := si.(float64); ok { - uu = append(uu, uint8(s)) - } - } - return uu, nil - default: - return []uint8{}, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Uint16Slice assert the result to []uint16 -func (r *Result) Uint16Slice() ([]uint16, error) { - switch v := r.value.(type) { - case []interface{}: - var uu = make([]uint16, 0) - for _, si := range v { - if s, ok := si.(float64); ok { - uu = append(uu, uint16(s)) - } - } - return uu, nil - default: - return []uint16{}, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Uint32Slice assert the result to []uint32 -func (r *Result) Uint32Slice() ([]uint32, error) { - switch v := r.value.(type) { - case []interface{}: - var uu = make([]uint32, 0) - for _, si := range v { - if s, ok := si.(float64); ok { - uu = append(uu, uint32(s)) - } - } - return uu, nil - default: - return []uint32{}, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Uint64Slice assert the result to []uint64 -func (r *Result) Uint64Slice() ([]uint64, error) { - switch v := r.value.(type) { - case []interface{}: - var uu = make([]uint64, 0) - for _, si := range v { - if s, ok := si.(float64); ok { - uu = append(uu, uint64(s)) - } - } - return uu, nil - default: - return []uint64{}, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Float32Slice assert the result to []float32 -func (r *Result) Float32Slice() ([]float32, error) { - switch v := r.value.(type) { - case []interface{}: - var ff = make([]float32, 0) - for _, si := range v { - if s, ok := si.(float64); ok { - ff = append(ff, float32(s)) - } - } - return ff, nil - default: - return []float32{}, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} - -// Float64Slice assert the result to []float64 -func (r *Result) Float64Slice() ([]float64, error) { - switch v := r.value.(type) { - case []interface{}: - var ff = make([]float64, 0) - for _, si := range v { - if s, ok := si.(float64); ok { - ff = append(ff, s) - } - } - return ff, nil - default: - return []float64{}, fmt.Errorf(errMessage, reflect.ValueOf(r.value).Kind()) - } -} diff --git a/json/gojsonq/result_test.go b/json/gojsonq/result_test.go deleted file mode 100644 index 609e4a7..0000000 --- a/json/gojsonq/result_test.go +++ /dev/null @@ -1,899 +0,0 @@ -package gojsonq - -import ( - "reflect" - "testing" - "time" -) - -func TestNewResult(t *testing.T) { - result := NewResult("gojsonq") - if reflect.ValueOf(result).Type().String() != "*gojsonq.Result" { - t.Error("failed to match gojsonq.Result type") - } -} - -func TestNil(t *testing.T) { - result := NewResult(nil) - if result.Nil() == false { - t.Error("failed to check Nil") - } -} - -func TestAs(t *testing.T) { - type flt float64 - testCases := []struct { - tag string - value interface{} - newExpectedValue func() interface{} - expect func(value, i interface{}) bool - errExpect bool - }{ - { - tag: "float64 as int", // golang unmarshal number to float64 - value: float64(1), - newExpectedValue: func() interface{} { - var a int - return &a - }, - expect: func(value, i interface{}) bool { - val, ok := i.(int) - if !ok { - return false - } - - return val == (value.(int)) - }, - errExpect: false, - }, - { - tag: "float64 as uint", - value: float64(1), - newExpectedValue: func() interface{} { - var a uint - return &a - }, - expect: func(value, i interface{}) bool { - val, ok := i.(uint) - if !ok { - return false - } - - return val == (value.(uint)) - }, - errExpect: false, - }, - { - tag: "float64 assign to non ptr", - value: float64(1), - newExpectedValue: func() interface{} { - var a float64 - return a - }, - expect: func(value, i interface{}) bool { - val, ok := i.(float64) - if !ok { - return false - } - - return val == (value.(float64)) - }, - errExpect: true, - }, - { - tag: "string assign to int", - value: string("*nop"), - newExpectedValue: func() interface{} { - var a float64 - return &a - }, - expect: func(value, i interface{}) bool { - val, ok := i.(string) - if !ok { - return false - } - - return val == (value.(string)) - }, - errExpect: true, - }, - { - tag: "custom type error check", - value: string("*nop"), - newExpectedValue: func() interface{} { - var a flt - return &a - }, - expect: func(value, i interface{}) bool { - val, ok := i.(string) - if !ok { - return false - } - - return val == (value.(string)) - }, - errExpect: true, - }, - } - - for _, tc := range testCases { - expected := tc.newExpectedValue() - err := NewResult(tc.value).As(expected) - if err != nil && !tc.errExpect { - t.Error(tc.tag, err) - } - if tc.expect(tc.value, expected) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.value, reflect.ValueOf(expected).Elem()) - } - } -} - -func TestBool(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect bool - errExpect bool - }{ - - {tag: "bool value as expected", value: true, valExpect: true, errExpect: false}, - {tag: "invalid bool, error expected", value: 123, valExpect: false, errExpect: true}, - } - - for _, tc := range testCases { - v, err := NewResult(tc.value).Bool() - if err != nil && !tc.errExpect { - t.Error("bool:", err) - } - if v != tc.valExpect && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, v) - } - } -} - -func TestTime(t *testing.T) { - layout := "2006-01-02T15:04:05.000Z" - str := "2014-11-12T11:45:26.371Z" - tm, err := time.Parse(layout, str) - if err != nil { - t.Error("failed to parse time:", err) - } - testCases := []struct { - tag string - value interface{} - valExpect time.Time - errExpect bool - }{ - {tag: "time value as expected", value: "2014-11-12T11:45:26.371Z", valExpect: tm, errExpect: false}, - {tag: "invalid time, error expected", value: "2014-11-12", valExpect: time.Time{}, errExpect: true}, - {tag: "invalid time, error expected", value: 12322, valExpect: time.Time{}, errExpect: true}, - } - - for _, tc := range testCases { - v, err := NewResult(tc.value).Time(layout) - if err != nil && !tc.errExpect { - t.Error("time:", err) - } - if !reflect.DeepEqual(v, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, v) - } - } -} - -func TestDuration(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect time.Duration - errExpect bool - }{ - {tag: "duration value as expected", value: "10s", valExpect: time.Duration(10 * time.Second), errExpect: false}, - {tag: "duration value as expected", value: "10m", valExpect: time.Duration(10 * time.Minute), errExpect: false}, - {tag: "duration value as expected", value: float64(10), valExpect: time.Duration(10 * time.Nanosecond), errExpect: false}, // go decode number to float64 - {tag: "invalid duration, error expected", value: "1", valExpect: time.Duration(0), errExpect: true}, - {tag: "invalid duration, error expected", value: 1, valExpect: time.Duration(0), errExpect: true}, - } - - for _, tc := range testCases { - v, err := NewResult(tc.value).Duration() - if err != nil && !tc.errExpect { - t.Error("duration:", err) - } - if !reflect.DeepEqual(v, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, v) - } - } -} - -func TestString(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect string - errExpect bool - }{ - {tag: "string value as expected", value: "hello", valExpect: "hello", errExpect: false}, - {tag: "invalid string, error expected", value: 123, valExpect: "", errExpect: true}, - } - - for _, tc := range testCases { - v, err := NewResult(tc.value).String() - if err != nil && !tc.errExpect { - t.Error("string: ", err) - } - if v != tc.valExpect && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, v) - } - } -} - -func TestInt(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect int - errExpect bool - }{ - {tag: "int value as expected", value: 123.8, valExpect: 123, errExpect: false}, - {tag: "int value as expected", value: 12.3, valExpect: 12, errExpect: false}, - {tag: "invalid int, error expected", value: "123", valExpect: 0, errExpect: true}, - } - - for _, tc := range testCases { - v, err := NewResult(tc.value).Int() - if err != nil && !tc.errExpect { - t.Error("int:", err) - } - if v != tc.valExpect && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, v) - } - } -} - -func TestInt8(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect int8 - errExpect bool - }{ - {tag: "int8 value as expected", value: 123.8, valExpect: int8(123), errExpect: false}, - {tag: "int8 value as expected", value: 12.3, valExpect: int8(12), errExpect: false}, - {tag: "invalid int8, error expected", value: "123", valExpect: 0, errExpect: true}, - } - - for _, tc := range testCases { - v, err := NewResult(tc.value).Int8() - if err != nil && !tc.errExpect { - t.Error("int8:", err) - } - if v != tc.valExpect && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, v) - } - } -} - -func TestInt16(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect int16 - errExpect bool - }{ - {tag: "int16 value as expected", value: 123.8, valExpect: int16(123), errExpect: false}, - {tag: "int16 value as expected", value: 12.3, valExpect: int16(12), errExpect: false}, - {tag: "invalid int16, error expected", value: "123", valExpect: 0, errExpect: true}, - } - - for _, tc := range testCases { - v, err := NewResult(tc.value).Int16() - if err != nil && !tc.errExpect { - t.Error("int16:", err) - } - if v != tc.valExpect && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, v) - } - } -} - -func TestInt32(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect int32 - errExpect bool - }{ - {tag: "int32 value as expected", value: 123.8, valExpect: int32(123), errExpect: false}, - {tag: "int32 value as expected", value: 12.3, valExpect: int32(12), errExpect: false}, - {tag: "invalid int32, error expected", value: "123", valExpect: 0, errExpect: true}, - } - - for _, tc := range testCases { - v, err := NewResult(tc.value).Int32() - if err != nil && !tc.errExpect { - t.Error("int32:", err) - } - if v != tc.valExpect && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, v) - } - } -} - -func TestInt64(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect int64 - errExpect bool - }{ - {tag: "int64 value as expected", value: 123.8, valExpect: int64(123), errExpect: false}, - {tag: "int64 value as expected", value: 12.3, valExpect: int64(12), errExpect: false}, - {tag: "invalid int64, error expected", value: "123", valExpect: 0, errExpect: true}, - } - - for _, tc := range testCases { - v, err := NewResult(tc.value).Int64() - if err != nil && !tc.errExpect { - t.Error("int64:", err) - } - if v != tc.valExpect && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, v) - } - } -} - -func TestUint(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect uint - errExpect bool - }{ - {tag: "uint value as expected", value: 123.8, valExpect: uint(123), errExpect: false}, - {tag: "uint value as expected", value: 12.3, valExpect: uint(12), errExpect: false}, - {tag: "invalid uint, error expected", value: "123", valExpect: 0, errExpect: true}, - } - - for _, tc := range testCases { - v, err := NewResult(tc.value).Uint() - if err != nil && !tc.errExpect { - t.Error("uint:", err) - } - if v != tc.valExpect && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, v) - } - } -} - -func TestUint8(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect uint8 - errExpect bool - }{ - {tag: "uint8 value as expected", value: 123.8, valExpect: uint8(123), errExpect: false}, - {tag: "uint8 value as expected", value: 12.3, valExpect: uint8(12), errExpect: false}, - {tag: "invalid uint8, error expected", value: "123", valExpect: 0, errExpect: true}, - } - - for _, tc := range testCases { - v, err := NewResult(tc.value).Uint8() - if err != nil && !tc.errExpect { - t.Error("uint8:", err) - } - if v != tc.valExpect && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, v) - } - } -} - -func TestUint16(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect uint16 - errExpect bool - }{ - {tag: "uint16 value as expected", value: 123.8, valExpect: uint16(123), errExpect: false}, - {tag: "uint16 value as expected", value: 12.3, valExpect: uint16(12), errExpect: false}, - {tag: "invalid uint16, error expected", value: "123", valExpect: 0, errExpect: true}, - } - - for _, tc := range testCases { - v, err := NewResult(tc.value).Uint16() - if err != nil && !tc.errExpect { - t.Error("uint16:", err) - } - if v != tc.valExpect && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, v) - } - } -} - -func TestUint32(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect uint32 - errExpect bool - }{ - {tag: "uint32 value as expected", value: 123.8, valExpect: uint32(123), errExpect: false}, - {tag: "uint32 value as expected", value: 12.3, valExpect: uint32(12), errExpect: false}, - {tag: "invalid uint32, error expected", value: "123", valExpect: 0, errExpect: true}, - } - - for _, tc := range testCases { - v, err := NewResult(tc.value).Uint32() - if err != nil && !tc.errExpect { - t.Error("uint32:", err) - } - if v != tc.valExpect && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, v) - } - } -} - -func TestUint64(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect uint64 - errExpect bool - }{ - {tag: "uint64 value as expected", value: 123.8, valExpect: uint64(123), errExpect: false}, - {tag: "uint64 value as expected", value: 12.3, valExpect: uint64(12), errExpect: false}, - {tag: "invalid uint64, error expected", value: "123", valExpect: 0, errExpect: true}, - } - - for _, tc := range testCases { - v, err := NewResult(tc.value).Uint64() - if err != nil && !tc.errExpect { - t.Error("uint64:", err) - } - if v != tc.valExpect && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, v) - } - } -} - -func TestFloat32(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect float32 - errExpect bool - }{ - {tag: "float32 value as expected", value: 123.8, valExpect: float32(123.8), errExpect: false}, - {tag: "float32 value as expected", value: 12.3, valExpect: float32(12.3), errExpect: false}, - {tag: "invalid float32, error expected", value: "123", valExpect: 0, errExpect: true}, - } - - for _, tc := range testCases { - v, err := NewResult(tc.value).Float32() - if err != nil && !tc.errExpect { - t.Error("float32:", err) - } - if v != tc.valExpect && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, v) - } - } -} - -func TestFloat64(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect float64 - errExpect bool - }{ - {tag: "float64 value as expected", value: 123.8, valExpect: float64(123.8), errExpect: false}, - {tag: "float64 value as expected", value: 12.3, valExpect: float64(12.3), errExpect: true}, - {tag: "invalid float64, error expected", value: "123", valExpect: 0, errExpect: true}, - } - - for _, tc := range testCases { - v, err := NewResult(tc.value).Float64() - if err != nil && !tc.errExpect { - t.Error("float64:", err) - } - if v != tc.valExpect && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, v) - } - } -} - -func TestBoolSlice(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect []bool - errExpect bool - }{ - {tag: "boolSlice value as expected", value: []interface{}{true, false}, valExpect: []bool{true, false}, errExpect: false}, - {tag: "boolSlice value as expected", value: []interface{}{false, true, true}, valExpect: []bool{false, true, true}, errExpect: false}, - {tag: "invalid boolSlice, error expected", value: []interface{}{1, 3}, valExpect: []bool{}, errExpect: false}, - {tag: "invalid boolSlice, error expected", value: []int{1, 3}, valExpect: []bool{}, errExpect: true}, - } - - for _, tc := range testCases { - vv, err := NewResult(tc.value).BoolSlice() - if err != nil && !tc.errExpect { - t.Error("boolSlice:", err) - } - if !reflect.DeepEqual(vv, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, vv) - } - } -} - -func TestTimelice(t *testing.T) { - layout := "2006-01-02T15:04:05.000Z" - t1, err1 := time.Parse(layout, "2014-11-12T11:45:26.371Z") - if err1 != nil { - t.Error("failed to parse time1:", err1) - } - t2, err2 := time.Parse(layout, "2019-11-12T11:45:26.371Z") - if err2 != nil { - t.Error("failed to parse time2:", err2) - } - testCases := []struct { - tag string - value interface{} - timeLayout string - valExpect []time.Time - errExpect bool - }{ - {tag: "timeSlice value as expected", value: []interface{}{"2014-11-12T11:45:26.371Z", "2019-11-12T11:45:26.371Z"}, timeLayout: layout, valExpect: []time.Time{t1, t2}, errExpect: false}, - {tag: "invalid timeSlice layout, error expected", value: []interface{}{"2014-11-12T11:45:26.371Z", "2019-11-12T11:45:26.371Z"}, timeLayout: "invalid layout", valExpect: []time.Time{}, errExpect: true}, - {tag: "invalid timeSlice, error expected", value: []int{1, 3}, timeLayout: layout, valExpect: []time.Time{}, errExpect: true}, - } - - for _, tc := range testCases { - vv, err := NewResult(tc.value).TimeSlice(tc.timeLayout) - if err != nil && !tc.errExpect { - t.Error("timeSlice:", err) - } - if !reflect.DeepEqual(vv, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, vv) - } - } - -} - -func TestDurationlice(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect []time.Duration - errExpect bool - }{ - {tag: "durationSlice value as expected", value: []interface{}{"1s", "1m"}, valExpect: []time.Duration{1 * time.Second, 1 * time.Minute}, errExpect: false}, - {tag: "durationSlice value as expected", value: []interface{}{"1", "2"}, valExpect: []time.Duration{1 * time.Nanosecond, 2 * time.Nanosecond}, errExpect: false}, - {tag: "durationSlice value as expected", value: []interface{}{float64(2), float64(3)}, valExpect: []time.Duration{2 * time.Nanosecond, 3 * time.Nanosecond}, errExpect: false}, - {tag: "invalid durationSlice, error expected", value: []interface{}{"invalid duration 1", "invalid duration 2"}, valExpect: []time.Duration{}, errExpect: true}, - {tag: "invalid durationSlice, error expected", value: []float64{3, 5}, valExpect: []time.Duration{}, errExpect: true}, - } - - for _, tc := range testCases { - vv, err := NewResult(tc.value).DurationSlice() - if err != nil && !tc.errExpect { - t.Error("durationSlice:", err) - } - if !reflect.DeepEqual(vv, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, vv) - } - } -} - -func TestStringSlice(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect []string - errExpect bool - }{ - {tag: "stringSlice value as expected", value: []interface{}{"hello", "world"}, valExpect: []string{"hello", "world"}, errExpect: false}, - {tag: "stringSlice value as expected", value: []interface{}{"tom", "jerry"}, valExpect: []string{"tom", "jerry"}, errExpect: false}, - {tag: "invalid stringSlice, error expected", value: []interface{}{1, 3}, valExpect: []string{}, errExpect: false}, - {tag: "invalid stringSlice, error expected", value: []int{1, 3}, valExpect: []string{}, errExpect: true}, - } - - for _, tc := range testCases { - vv, err := NewResult(tc.value).StringSlice() - if err != nil && !tc.errExpect { - t.Error("stringSlice:", err) - } - if !reflect.DeepEqual(vv, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, vv) - } - } -} - -func TestIntSlice(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect []int - errExpect bool - }{ - {tag: "intSlice value as expected", value: []interface{}{132.1, 12.99}, valExpect: []int{132, 12}, errExpect: false}, - {tag: "intSlice value as expected", value: []interface{}{float64(131), float64(12)}, valExpect: []int{131, 12}, errExpect: false}, // as golang decode number to float64 - {tag: "invalid intSlice, error expected", value: []interface{}{1, 3}, valExpect: []int{}, errExpect: false}, - {tag: "invalid intSlice, error expected", value: []int{1, 3}, valExpect: []int{}, errExpect: true}, - } - - for _, tc := range testCases { - vv, err := NewResult(tc.value).IntSlice() - if err != nil && !tc.errExpect { - t.Error("intSlice:", err) - } - if !reflect.DeepEqual(vv, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, vv) - } - } -} - -func TestInt8Slice(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect []int8 - errExpect bool - }{ - {tag: "int8Slice value as expected", value: []interface{}{3.1, 12.99}, valExpect: []int8{3, 12}, errExpect: false}, - {tag: "int8Slice value as expected", value: []interface{}{float64(11), float64(12)}, valExpect: []int8{11, 12}, errExpect: false}, // as golang decode number to float64 - {tag: "invalid int8Slice, error expected", value: []interface{}{1, 3}, valExpect: []int8{}, errExpect: false}, - {tag: "invalid int8Slice, error expected", value: []int{1, 3}, valExpect: []int8{}, errExpect: true}, - } - - for _, tc := range testCases { - vv, err := NewResult(tc.value).Int8Slice() - if err != nil && !tc.errExpect { - t.Error("int8Slice:", err) - } - if !reflect.DeepEqual(vv, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, vv) - } - } -} - -func TestInt16Slice(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect []int16 - errExpect bool - }{ - {tag: "int16Slice value as expected", value: []interface{}{3.1, 12.99}, valExpect: []int16{3, 12}, errExpect: false}, - {tag: "int16Slice value as expected", value: []interface{}{float64(11), float64(12)}, valExpect: []int16{11, 12}, errExpect: false}, // as golang decode number to float64 - {tag: "invalid int16Slice, error expected", value: []interface{}{1, 3}, valExpect: []int16{}, errExpect: false}, - {tag: "invalid int16Slice, error expected", value: []int{1, 3}, valExpect: []int16{}, errExpect: true}, - } - - for _, tc := range testCases { - vv, err := NewResult(tc.value).Int16Slice() - if err != nil && !tc.errExpect { - t.Error("int16Slice:", err) - } - if !reflect.DeepEqual(vv, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, vv) - } - } -} - -func TestInt32Slice(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect []int32 - errExpect bool - }{ - {tag: "int32Slice value as expected", value: []interface{}{3.1, 12.99}, valExpect: []int32{3, 12}, errExpect: false}, - {tag: "int32Slice value as expected", value: []interface{}{float64(131), float64(132)}, valExpect: []int32{131, 132}, errExpect: false}, // as golang decode number to float64 - {tag: "invalid int32Slice, error expected", value: []interface{}{1, 3}, valExpect: []int32{}, errExpect: false}, - {tag: "invalid int32Slice, error expected", value: []int{1, 3}, valExpect: []int32{}, errExpect: true}, - } - - for _, tc := range testCases { - vv, err := NewResult(tc.value).Int32Slice() - if err != nil && !tc.errExpect { - t.Error("int32Slice:", err) - } - if !reflect.DeepEqual(vv, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, vv) - } - } -} - -func TestInt64Slice(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect []int64 - errExpect bool - }{ - {tag: "int64Slice value as expected", value: []interface{}{3.1, 12.99}, valExpect: []int64{3, 12}, errExpect: false}, - {tag: "int64Slice value as expected", value: []interface{}{float64(131), float64(132)}, valExpect: []int64{131, 132}, errExpect: false}, // as golang decode number to float64 - {tag: "invalid int64Slice, error expected", value: []interface{}{1, 3}, valExpect: []int64{}, errExpect: false}, - {tag: "invalid int64Slice, error expected", value: []int{1, 3}, valExpect: []int64{}, errExpect: true}, - } - - for _, tc := range testCases { - vv, err := NewResult(tc.value).Int64Slice() - if err != nil && !tc.errExpect { - t.Error("int64Slice:", err) - } - if !reflect.DeepEqual(vv, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, vv) - } - } -} - -func TestUintSlice(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect []uint - errExpect bool - }{ - {tag: "uintSlice value as expected", value: []interface{}{3.1, 12.99}, valExpect: []uint{3, 12}, errExpect: false}, - {tag: "uintSlice value as expected", value: []interface{}{float64(131), float64(132)}, valExpect: []uint{131, 132}, errExpect: false}, // as golang decode number to float64 - {tag: "invalid uintSlice, error expected", value: []interface{}{1, 3}, valExpect: []uint{}, errExpect: false}, - {tag: "invalid uintSlice, error expected", value: []int{1, 3}, valExpect: []uint{}, errExpect: true}, - } - - for _, tc := range testCases { - vv, err := NewResult(tc.value).UintSlice() - if err != nil && !tc.errExpect { - t.Error("uintSlice:", err) - } - if !reflect.DeepEqual(vv, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, vv) - } - } -} - -func TestUint8Slice(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect []uint8 - errExpect bool - }{ - {tag: "uint8Slice value as expected", value: []interface{}{3.1, 12.99}, valExpect: []uint8{3, 12}, errExpect: false}, - {tag: "uint8Slice value as expected", value: []interface{}{float64(131), float64(132)}, valExpect: []uint8{131, 132}, errExpect: false}, // as golang decode number to float64 - {tag: "invalid uint8Slice, error expected", value: []interface{}{1, 3}, valExpect: []uint8{}, errExpect: false}, - {tag: "invalid uint8Slice, error expected", value: []int{1, 3}, valExpect: []uint8{}, errExpect: true}, - } - - for _, tc := range testCases { - vv, err := NewResult(tc.value).Uint8Slice() - if err != nil && !tc.errExpect { - t.Error("uint8Slice:", err) - } - if !reflect.DeepEqual(vv, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, vv) - } - } -} - -func TestUint16Slice(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect []uint16 - errExpect bool - }{ - {tag: "uint16Slice value as expected", value: []interface{}{3.1, 12.99}, valExpect: []uint16{3, 12}, errExpect: false}, - {tag: "uint16Slice value as expected", value: []interface{}{float64(131), float64(132)}, valExpect: []uint16{131, 132}, errExpect: false}, // as golang decode number to float64 - {tag: "invalid uint16Slice, error expected", value: []interface{}{1, 3}, valExpect: []uint16{}, errExpect: false}, - {tag: "invalid uint16Slice, error expected", value: []int{1, 3}, valExpect: []uint16{}, errExpect: true}, - } - - for _, tc := range testCases { - vv, err := NewResult(tc.value).Uint16Slice() - if err != nil && !tc.errExpect { - t.Error("uint16Slice:", err) - } - if !reflect.DeepEqual(vv, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, vv) - } - } -} - -func TestUint32Slice(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect []uint32 - errExpect bool - }{ - {tag: "uint32Slice value as expected", value: []interface{}{3.1, 12.99}, valExpect: []uint32{3, 12}, errExpect: false}, - {tag: "uint32Slice value as expected", value: []interface{}{float64(131), float64(132)}, valExpect: []uint32{131, 132}, errExpect: false}, // as golang decode number to float64 - {tag: "invalid uint32Slice, error expected", value: []interface{}{1, 3}, valExpect: []uint32{}, errExpect: false}, - {tag: "invalid uint32Slice, error expected", value: []int{1, 3}, valExpect: []uint32{}, errExpect: true}, - } - - for _, tc := range testCases { - vv, err := NewResult(tc.value).Uint32Slice() - if err != nil && !tc.errExpect { - t.Error("uint32Slice:", err) - } - if !reflect.DeepEqual(vv, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, vv) - } - } -} - -func TestUint64Slice(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect []uint64 - errExpect bool - }{ - {tag: "uint64Slice value as expected", value: []interface{}{3.1, 12.99}, valExpect: []uint64{3, 12}, errExpect: false}, - {tag: "uint64Slice value as expected", value: []interface{}{float64(131), float64(132)}, valExpect: []uint64{131, 132}, errExpect: false}, // as golang decode number to float64 - {tag: "invalid uint64Slice, error expected", value: []interface{}{1, 3}, valExpect: []uint64{}, errExpect: false}, - {tag: "invalid uint64Slice, error expected", value: []int{1, 3}, valExpect: []uint64{}, errExpect: true}, - } - - for _, tc := range testCases { - vv, err := NewResult(tc.value).Uint64Slice() - if err != nil && !tc.errExpect { - t.Error("uint64Slice:", err) - } - if !reflect.DeepEqual(vv, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, vv) - } - } -} - -func TestFloat32Slice(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect []float32 - errExpect bool - }{ - {tag: "float32Slice value as expected", value: []interface{}{3.1, 12.99}, valExpect: []float32{3.1, 12.99}, errExpect: false}, - {tag: "float32Slice value as expected", value: []interface{}{float64(131), float64(132)}, valExpect: []float32{131, 132}, errExpect: false}, // as golang decode number to float64 - {tag: "invalid float32Slice, error expected", value: []interface{}{1, 3}, valExpect: []float32{}, errExpect: false}, - {tag: "invalid float32Slice, error expected", value: []int{1, 3}, valExpect: []float32{}, errExpect: true}, - } - - for _, tc := range testCases { - vv, err := NewResult(tc.value).Float32Slice() - if err != nil && !tc.errExpect { - t.Error("float32Slice:", err) - } - if !reflect.DeepEqual(vv, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, vv) - } - } -} - -func TestFloat64Slice(t *testing.T) { - testCases := []struct { - tag string - value interface{} - valExpect []float64 - errExpect bool - }{ - {tag: "float64Slice value as expected", value: []interface{}{3.1, 12.99}, valExpect: []float64{3.1, 12.99}, errExpect: false}, - {tag: "float64Slice value as expected", value: []interface{}{float64(131), float64(132)}, valExpect: []float64{131, 132}, errExpect: false}, // as golang decode number to float64 - {tag: "invalid float64Slice, error expected", value: []interface{}{1, 3}, valExpect: []float64{}, errExpect: false}, - {tag: "invalid float64Slice, error expected", value: []int{1, 3}, valExpect: []float64{}, errExpect: true}, - } - - for _, tc := range testCases { - vv, err := NewResult(tc.value).Float64Slice() - if err != nil && !tc.errExpect { - t.Error("float64Slice:", err) - } - if !reflect.DeepEqual(vv, tc.valExpect) && !tc.errExpect { - t.Errorf("tag: %s\nexpected: %v got %v", tc.tag, tc.valExpect, vv) - } - } -} diff --git a/json/json_test.go b/json/json_test.go deleted file mode 100644 index 1a50077..0000000 --- a/json/json_test.go +++ /dev/null @@ -1,121 +0,0 @@ -/* -Copyright 2021 The tKeel Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package json - -import ( - "bytes" - "github.com/tkeel-io/tdtl/json/gjson" - "github.com/tkeel-io/tdtl/json/gojsonq" - "github.com/tkeel-io/tdtl/json/jsonparser" - "testing" -) - -var json = `{ - "name": {"first": "Tom", "last": "Anderson", "age": 44}, - "age":37.1, - "children": ["Sara","Alex","Jack"], - "movie": "Deer Hunter", - "friends": [ - {"first": "Dale", "last": "Murphy", "age": 44}, - {"first": "Roger", "last": "Craig", "age": 68}, - {"first": "Jane", "last": "Murphy", "age": 47} - ] -}` -var jsonByt = []byte(json) - -func BenchmarkGet_gjson(b *testing.B) { - for i := 0; i < b.N; i++ { - gjson.Get(json, "age") - } -} - -func BenchmarkGet_jsonparser(b *testing.B) { - for i := 0; i < b.N; i++ { - jsonparser.Get(jsonByt, "age") - } -} - -func BenchmarkGet_gojsonq(b *testing.B) { - for i := 0; i < b.N; i++ { - j := gojsonq.New().FromString(json) - j.Find("age") - } -} - -func BenchmarkGet_gjson2(b *testing.B) { - for i := 0; i < b.N; i++ { - gjson.Get(json, "age") - gjson.Get(json, "movie") - } -} - -func BenchmarkGet_jsonparser2(b *testing.B) { - for i := 0; i < b.N; i++ { - jsonparser.Get(jsonByt, "age") - jsonparser.Get(jsonByt, "movie") - } -} - -func BenchmarkGet_gojsonq2(b *testing.B) { - for i := 0; i < b.N; i++ { - j := gojsonq.New().FromString(json) - j.Find("age") - j.Find("movie") - } -} - -func BenchmarkGet_gjson3(b *testing.B) { - for i := 0; i < b.N; i++ { - gjson.Get(json, "age") - gjson.Get(json, "age") - gjson.Get(json, "age") - } -} - -func BenchmarkGet_jsonparser3(b *testing.B) { - for i := 0; i < b.N; i++ { - jsonparser.Get(jsonByt, "age") - jsonparser.Get(jsonByt, "age") - jsonparser.Get(jsonByt, "age") - } -} - -func BenchmarkGet_jsonparser_getunsafestring3(b *testing.B) { - for i := 0; i < b.N; i++ { - jsonparser.GetUnsafeString(jsonByt, "age") - jsonparser.GetUnsafeString(jsonByt, "age") - jsonparser.GetUnsafeString(jsonByt, "age") - } -} - -func BenchmarkGet_gojsonq3(b *testing.B) { - for i := 0; i < b.N; i++ { - j := gojsonq.New().FromString(json) - j.Find("age") - j.Find("age") - j.Find("age") - } -} - -func BenchmarkGet_gojsonq3_reader(b *testing.B) { - r := bytes.NewBuffer(jsonByt) - for i := 0; i < b.N; i++ { - j := gojsonq.New().Reader(r) - j.Find("age") - j.Find("age") - j.Find("age") - } -} diff --git a/json/jsonparser/.github/PULL_REQUEST_TEMPLATE.md b/json/jsonparser/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 089fdf2..0000000 --- a/json/jsonparser/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,13 +0,0 @@ -**Description**: What this PR does - -**Benchmark before change**: - -**Benchmark after change**: - - -For running benchmarks use: -``` -go test -test.benchmem -bench JsonParser ./benchmark/ -benchtime 5s -v -# OR -make bench (runs inside docker) -``` \ No newline at end of file diff --git a/json/jsonparser/.gitignore b/json/jsonparser/.gitignore deleted file mode 100644 index 9de1b0f..0000000 --- a/json/jsonparser/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ - -*.test - -*.out - -*.mprof - -vendor/github.com/buger/goterm/ -prof.cpu -prof.mem diff --git a/json/jsonparser/.travis.yml b/json/jsonparser/.travis.yml deleted file mode 100644 index 8e29f13..0000000 --- a/json/jsonparser/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: go -go: - - 1.7.x - - 1.8.x - - 1.9.x - - 1.10.x - - 1.11.x -script: go test -v ./. diff --git a/json/types.go b/json/types.go deleted file mode 100644 index d49abaf..0000000 --- a/json/types.go +++ /dev/null @@ -1,16 +0,0 @@ -/* -Copyright 2021 The tKeel Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package json diff --git a/parse.go b/parse.go index e133a68..f8a8f11 100644 --- a/parse.go +++ b/parse.go @@ -295,6 +295,54 @@ func (l *TDTLListener) ExitIdentifierWithTOPICITEM(c *parser.IdentifierWithTOPIC //fmt.Println("IdentifierWithTOPICITEM", c.GetText()) } + +// + +func (l *TDTLListener) ExitFilter_condition(c *parser.Filter_conditionContext) { + //fmt.Println("ExitFliter_condition", c.GetText(), c.AllAND()) + level := len(c.AllAND()) + left := l.pop() + for level > 0 { + right := l.pop() + left = &BinaryExpr{ + Op: parser.TDTLLexerAND, + LHS: left, + RHS: right, + } + level-- + } + l.push(left) +} + +func (l *TDTLListener) ExitFilter_condition_or(c *parser.Filter_condition_orContext) { + //fmt.Println("ExitFilter_condition_or", c.GetText(), c.AllOR()) + level := len(c.AllOR()) + left := l.pop() + for level > 0 { + right := l.pop() + left = &BinaryExpr{ + Op: parser.TDTLLexerOR, + LHS: left, + RHS: right, + } + level-- + } + l.push(left) +} + +func (l *TDTLListener) ExitFilter_condition_not(c *parser.Filter_condition_notContext) { + //fmt.Println("ExitFilter_condition_not", c.GetText()) + if c.NOT() != nil { + right := l.pop() + l.push(&BinaryExpr{ + Op: parser.TDTLLexerNOT, + LHS: nil, + RHS: right, + }) + } +} + + func (l *TDTLListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, line, column int, msg string, e antlr.RecognitionException) { //fmt.Println("SyntaxError", recognizer, offendingSymbol, line, column, msg, e) l.errors = append(l.errors, fmt.Sprintf("[%d:%d]%s", line, column, msg)) diff --git a/parser/tdtl_lexer.go b/parser/tdtl_lexer.go index e8c7662..62ac343 100644 --- a/parser/tdtl_lexer.go +++ b/parser/tdtl_lexer.go @@ -72,36 +72,36 @@ var serializedLexerAtn = []uint16{ 87, 45, 89, 46, 91, 47, 93, 48, 95, 49, 97, 2, 99, 2, 101, 2, 103, 2, 105, 2, 107, 2, 109, 2, 111, 2, 113, 2, 115, 2, 117, 2, 119, 2, 121, 2, 123, 2, 125, 2, 127, 2, 129, 2, 131, 2, 133, 2, 135, 2, 137, 2, 139, 2, 141, - 2, 143, 2, 145, 2, 147, 2, 149, 2, 3, 2, 36, 7, 2, 37, 37, 50, 59, 67, - 92, 97, 97, 99, 124, 8, 2, 37, 38, 47, 47, 50, 59, 66, 92, 97, 97, 99, - 124, 3, 2, 51, 59, 3, 2, 50, 59, 4, 2, 45, 45, 47, 47, 8, 2, 37, 38, 47, - 47, 49, 59, 66, 92, 97, 97, 99, 124, 3, 2, 41, 41, 5, 2, 11, 12, 15, 15, - 34, 34, 4, 2, 67, 67, 99, 99, 4, 2, 68, 68, 100, 100, 4, 2, 69, 69, 101, - 101, 4, 2, 70, 70, 102, 102, 4, 2, 71, 71, 103, 103, 4, 2, 72, 72, 104, - 104, 4, 2, 73, 73, 105, 105, 4, 2, 74, 74, 106, 106, 4, 2, 75, 75, 107, - 107, 4, 2, 76, 76, 108, 108, 4, 2, 77, 77, 109, 109, 4, 2, 78, 78, 110, - 110, 4, 2, 79, 79, 111, 111, 4, 2, 80, 80, 112, 112, 4, 2, 81, 81, 113, - 113, 4, 2, 82, 82, 114, 114, 4, 2, 83, 83, 115, 115, 4, 2, 84, 84, 116, - 116, 4, 2, 85, 85, 117, 117, 4, 2, 86, 86, 118, 118, 4, 2, 87, 87, 119, - 119, 4, 2, 88, 88, 120, 120, 4, 2, 89, 89, 121, 121, 4, 2, 90, 90, 122, - 122, 4, 2, 91, 91, 123, 123, 4, 2, 92, 92, 124, 124, 2, 489, 2, 3, 3, 2, - 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, - 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, - 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 23, 3, 2, 2, 2, 2, 25, 3, 2, 2, 2, 2, 27, - 3, 2, 2, 2, 2, 29, 3, 2, 2, 2, 2, 31, 3, 2, 2, 2, 2, 33, 3, 2, 2, 2, 2, - 35, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 2, 39, 3, 2, 2, 2, 2, 41, 3, 2, 2, 2, - 2, 43, 3, 2, 2, 2, 2, 45, 3, 2, 2, 2, 2, 47, 3, 2, 2, 2, 2, 49, 3, 2, 2, - 2, 2, 51, 3, 2, 2, 2, 2, 53, 3, 2, 2, 2, 2, 55, 3, 2, 2, 2, 2, 57, 3, 2, - 2, 2, 2, 59, 3, 2, 2, 2, 2, 61, 3, 2, 2, 2, 2, 63, 3, 2, 2, 2, 2, 65, 3, - 2, 2, 2, 2, 67, 3, 2, 2, 2, 2, 69, 3, 2, 2, 2, 2, 71, 3, 2, 2, 2, 2, 73, - 3, 2, 2, 2, 2, 75, 3, 2, 2, 2, 2, 77, 3, 2, 2, 2, 2, 79, 3, 2, 2, 2, 2, - 81, 3, 2, 2, 2, 2, 83, 3, 2, 2, 2, 2, 85, 3, 2, 2, 2, 2, 87, 3, 2, 2, 2, - 2, 89, 3, 2, 2, 2, 2, 91, 3, 2, 2, 2, 2, 93, 3, 2, 2, 2, 2, 95, 3, 2, 2, - 2, 3, 151, 3, 2, 2, 2, 5, 153, 3, 2, 2, 2, 7, 155, 3, 2, 2, 2, 9, 157, - 3, 2, 2, 2, 11, 159, 3, 2, 2, 2, 13, 161, 3, 2, 2, 2, 15, 163, 3, 2, 2, - 2, 17, 165, 3, 2, 2, 2, 19, 168, 3, 2, 2, 2, 21, 172, 3, 2, 2, 2, 23, 179, - 3, 2, 2, 2, 25, 184, 3, 2, 2, 2, 27, 189, 3, 2, 2, 2, 29, 195, 3, 2, 2, - 2, 31, 202, 3, 2, 2, 2, 33, 209, 3, 2, 2, 2, 35, 219, 3, 2, 2, 2, 37, 221, + 2, 143, 2, 145, 2, 147, 2, 149, 2, 3, 2, 36, 6, 2, 37, 37, 67, 92, 97, + 97, 99, 124, 8, 2, 37, 38, 47, 47, 50, 59, 66, 92, 97, 97, 99, 124, 3, + 2, 51, 59, 3, 2, 50, 59, 4, 2, 45, 45, 47, 47, 8, 2, 37, 38, 47, 47, 49, + 59, 66, 92, 97, 97, 99, 124, 3, 2, 41, 41, 5, 2, 11, 12, 15, 15, 34, 34, + 4, 2, 67, 67, 99, 99, 4, 2, 68, 68, 100, 100, 4, 2, 69, 69, 101, 101, 4, + 2, 70, 70, 102, 102, 4, 2, 71, 71, 103, 103, 4, 2, 72, 72, 104, 104, 4, + 2, 73, 73, 105, 105, 4, 2, 74, 74, 106, 106, 4, 2, 75, 75, 107, 107, 4, + 2, 76, 76, 108, 108, 4, 2, 77, 77, 109, 109, 4, 2, 78, 78, 110, 110, 4, + 2, 79, 79, 111, 111, 4, 2, 80, 80, 112, 112, 4, 2, 81, 81, 113, 113, 4, + 2, 82, 82, 114, 114, 4, 2, 83, 83, 115, 115, 4, 2, 84, 84, 116, 116, 4, + 2, 85, 85, 117, 117, 4, 2, 86, 86, 118, 118, 4, 2, 87, 87, 119, 119, 4, + 2, 88, 88, 120, 120, 4, 2, 89, 89, 121, 121, 4, 2, 90, 90, 122, 122, 4, + 2, 91, 91, 123, 123, 4, 2, 92, 92, 124, 124, 2, 489, 2, 3, 3, 2, 2, 2, + 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, + 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, + 2, 2, 21, 3, 2, 2, 2, 2, 23, 3, 2, 2, 2, 2, 25, 3, 2, 2, 2, 2, 27, 3, 2, + 2, 2, 2, 29, 3, 2, 2, 2, 2, 31, 3, 2, 2, 2, 2, 33, 3, 2, 2, 2, 2, 35, 3, + 2, 2, 2, 2, 37, 3, 2, 2, 2, 2, 39, 3, 2, 2, 2, 2, 41, 3, 2, 2, 2, 2, 43, + 3, 2, 2, 2, 2, 45, 3, 2, 2, 2, 2, 47, 3, 2, 2, 2, 2, 49, 3, 2, 2, 2, 2, + 51, 3, 2, 2, 2, 2, 53, 3, 2, 2, 2, 2, 55, 3, 2, 2, 2, 2, 57, 3, 2, 2, 2, + 2, 59, 3, 2, 2, 2, 2, 61, 3, 2, 2, 2, 2, 63, 3, 2, 2, 2, 2, 65, 3, 2, 2, + 2, 2, 67, 3, 2, 2, 2, 2, 69, 3, 2, 2, 2, 2, 71, 3, 2, 2, 2, 2, 73, 3, 2, + 2, 2, 2, 75, 3, 2, 2, 2, 2, 77, 3, 2, 2, 2, 2, 79, 3, 2, 2, 2, 2, 81, 3, + 2, 2, 2, 2, 83, 3, 2, 2, 2, 2, 85, 3, 2, 2, 2, 2, 87, 3, 2, 2, 2, 2, 89, + 3, 2, 2, 2, 2, 91, 3, 2, 2, 2, 2, 93, 3, 2, 2, 2, 2, 95, 3, 2, 2, 2, 3, + 151, 3, 2, 2, 2, 5, 153, 3, 2, 2, 2, 7, 155, 3, 2, 2, 2, 9, 157, 3, 2, + 2, 2, 11, 159, 3, 2, 2, 2, 13, 161, 3, 2, 2, 2, 15, 163, 3, 2, 2, 2, 17, + 165, 3, 2, 2, 2, 19, 168, 3, 2, 2, 2, 21, 172, 3, 2, 2, 2, 23, 179, 3, + 2, 2, 2, 25, 184, 3, 2, 2, 2, 27, 189, 3, 2, 2, 2, 29, 195, 3, 2, 2, 2, + 31, 202, 3, 2, 2, 2, 33, 209, 3, 2, 2, 2, 35, 219, 3, 2, 2, 2, 37, 221, 3, 2, 2, 2, 39, 232, 3, 2, 2, 2, 41, 240, 3, 2, 2, 2, 43, 246, 3, 2, 2, 2, 45, 254, 3, 2, 2, 2, 47, 263, 3, 2, 2, 2, 49, 270, 3, 2, 2, 2, 51, 272, 3, 2, 2, 2, 53, 277, 3, 2, 2, 2, 55, 282, 3, 2, 2, 2, 57, 290, 3, 2, 2, diff --git a/parser_test.go b/parser_test.go index 01c5f84..8cf227a 100644 --- a/parser_test.go +++ b/parser_test.go @@ -104,23 +104,23 @@ func TestJson(t *testing.T) { name string context Context args string - want interface{} + want Node }{ - {"", NewJSONContext(JSONRaw.JSON), `age`, IntNode(37)}, + //{"", NewJSONContext(JSONRaw.JSON), `age`, IntNode(37)}, {"", NewJSONContext(JSONRaw.JSON), `age + 1`, IntNode(38)}, {"", NewJSONContext(JSONRaw.JSON), `(age + 1) * 2`, IntNode(76)}, {"", NewJSONContext(JSONRaw.JSON), `(1 + 1) * age`, IntNode(74)}, - {"", NewJSONContext(JSONRaw.JSON), `name.first`, "Tom"}, - {"1", NewJSONContext(JSONRaw.JSON), `movie.1111`, "[{\"1111\": \"Tom\", \"last\": \"Anderson\"}]"}, - {"1", NewJSONContext(JSONRaw.JSON), `movie.1111[0].1111`, "Tom"}, - {"1", NewJSONContext(JSONRaw.JSON), `movie.1111[#].1111`, "[\"Tom\"]"}, - {"1", NewJSONContext(JSONRaw.JSON), `movie.1111[#]`, 1}, + {"", NewJSONContext(JSONRaw.JSON), `name.first`, StringNode("Tom")}, + {"1", NewJSONContext(JSONRaw.JSON), `movie.1111`, New("[{\"1111\": \"Tom\", \"last\": \"Anderson\"},{\"first\": \"Neo\"}]")}, + {"1", NewJSONContext(JSONRaw.JSON), `movie.1111[0].1111`, StringNode("Tom")}, + {"1", NewJSONContext(JSONRaw.JSON), `movie.1111[#].1111`, New("[\"Tom\"]")}, + {"1", NewJSONContext(JSONRaw.JSON), `movie.1111[#]`, IntNode(2)}, } for idx, tt := range tests { Convey(fmt.Sprintf("Test Float [%d]%s", idx, tt.name), t, func() { expr, err := ParseExpr(tt.args) So(err, ShouldBeNil) - So(eval(tt.context, expr), ShouldEqual, tt.want) + So(string(eval(tt.context, expr).Raw()), ShouldEqual, string(tt.want.Raw())) }) } } diff --git a/json/gjson/LICENSE b/pkg/json/gjson/LICENSE similarity index 100% rename from json/gjson/LICENSE rename to pkg/json/gjson/LICENSE diff --git a/json/gjson/README.md b/pkg/json/gjson/README.md similarity index 100% rename from json/gjson/README.md rename to pkg/json/gjson/README.md diff --git a/pkg/json/gjson/SYNTAX.md b/pkg/json/gjson/SYNTAX.md new file mode 100644 index 0000000..c8e578f --- /dev/null +++ b/pkg/json/gjson/SYNTAX.md @@ -0,0 +1,227 @@ +# GJSON Path Syntax + +A GJSON Path is a text string syntax that describes a search pattern for quickly retreiving values from a JSON payload. + +This document is designed to explain the structure of a GJSON Path through examples. + +- [Path structure](#path-structure) +- [Basic](#basic) +- [Wildcards](#wildcards) +- [Escape Character](#escape-character) +- [Arrays](#arrays) +- [Queries](#queries) +- [Dot vs Pipe](#dot-vs-pipe) +- [Modifiers](#modifiers) + +The definitive implemenation is [github.com/tidwall/gjson](https://github.com/tidwall/gjson). + + +## Path structure + +A GJSON Path is intended to be easily expressed as a series of components seperated by a `.` character. + +Along with `.` character, there are a few more that have special meaning, including `|`, `#`, `@`, `\`, `*`, and `?`. + +## Example + +Given this JSON + +```json +{ + "name": {"first": "Tom", "last": "Anderson"}, + "age":37, + "children": ["Sara","Alex","Jack"], + "fav.movie": "Deer Hunter", + "friends": [ + {"first": "Dale", "last": "Murphy", "age": 44}, + {"first": "Roger", "last": "Craig", "age": 68}, + {"first": "Jane", "last": "Murphy", "age": 47} + ] +} +``` + +The following GJSON Paths evaluate to the accompanying values. + +### Basic + +In many cases you'll just want to retreive values by object name or array index. + +```go +name.last "Anderson" +name.first "Tom" +age 37 +children ["Sara","Alex","Jack"] +children.0 "Sara" +children.1 "Alex" +friends.1 {"first": "Roger", "last": "Craig", "age": 68} +friends.1.first "Roger" +``` + +### Wildcards + +A key may contain the special wildcard characters `*` and `?`. +The `*` will match on any zero+ characters, and `?` matches on any one character. + +```go +child*.2 "Jack" +c?ildren.0 "Sara" +``` + +### Escape character + +Special purpose characters, such as `.`, `*`, and `?` can be escaped with `\`. + +```go +fav\.movie "Deer Hunter" +``` + + +### Arrays + +The `#` character allows for digging into JSON Arrays. + +To get the length of an array you'll just use the `#` all by itself. + +```go +friends.# 3 +friends.#.age [44,68,47] +``` + +### Queries + +You can also query an array for the first match by using `#[...]`, or find all matches with `#[...]#`. +Queries support the `==`, `!=`, `<`, `<=`, `>`, `>=` comparison operators, +and the simple pattern matching `%` (like) and `!%` (not like) operators. + +```go +friends.#[last=="Murphy"].first "Dale" +friends.#[last=="Murphy"]#.first ["Dale","Jane"] +friends.#[age>45]#.last ["Craig","Murphy"] +friends.#[first%"D*"].last "Murphy" +friends.#[first!%"D*"].last "Craig" +``` + +To query for a non-object value in an array, you can forgo the string to the right of the operator. +```go +children.#[!%"*a*"] "Alex" +children.#[%"*a*"]# ["Sara","Jack"] +``` + + + +### Dot vs Pipe + +The `.` is standard separator, but it's also possible to use a `|`. +In most cases they both end up returning the same results. +The cases where`|` differs from `.` is when it's used after the `#` for [Arrays](#arrays) and [Queries](#queries). + +Here are some examples + +```go +friends.0.first "Dale" +friends|0.first "Dale" +friends.0|first "Dale" +friends|0|first "Dale" +friends|# 3 +friends.# 3 +friends.#[last="Murphy"]# [{"first": "Dale", "last": "Murphy", "age": 44},{"first": "Jane", "last": "Murphy", "age": 47}] +friends.#[last="Murphy"]#.first ["Dale","Jane"] +friends.#[last="Murphy"]#|first +friends.#[last="Murphy"]#.0 [] +friends.#[last="Murphy"]#|0 {"first": "Dale", "last": "Murphy", "age": 44} +friends.#[last="Murphy"]#.# [] +friends.#[last="Murphy"]#|# 2 +``` + +Let's break down a few of these. + +The path `friends.#[last="Murphy"]#` all by itself results in + +```json +[{"first": "Dale", "last": "Murphy", "age": 44},{"first": "Jane", "last": "Murphy", "age": 47}] +``` + +The `.first` suffix will process the `first` path on each array element *before* returning the results. Which becomes + +```json +["Dale","Jane"] +``` + +But the `|first` suffix actually processes the `first` path *after* the previous result. +Since the previous result is an array, not an object, it's not possible to process +because `first` does not exist. + +Yet, `|0` suffix returns + +```json +{"first": "Dale", "last": "Murphy", "age": 44} +``` + +Because `0` is the first index of the previous result. + +### Modifiers + +A modifier is a path component that performs custom processing on the JSON. + +For example, using the built-in `@reverse` modifier on the above JSON payload will reverse the `children` array: + +```go +children.@reverse ["Jack","Alex","Sara"] +children.@reverse.0 "Jack" +``` + +There are currently three built-in modifiers: + +- `@reverse`: Reverse an array or the members of an object. +- `@ugly`: Remove all whitespace from JSON. +- `@pretty`: Make the JSON more human readable. + +#### Modifier arguments + +A modifier may accept an optional argument. The argument can be a valid JSON payload or just characters. + +For example, the `@pretty` modifier takes a json object as its argument. + +``` +@pretty:{"sortKeys":true} +``` + +Which makes the json pretty and orders all of its keys. + +```json +{ + "age":37, + "children": ["Sara","Alex","Jack"], + "fav.movie": "Deer Hunter", + "friends": [ + {"age": 44, "first": "Dale", "last": "Murphy"}, + {"age": 68, "first": "Roger", "last": "Craig"}, + {"age": 47, "first": "Jane", "last": "Murphy"} + ], + "name": {"first": "Tom", "last": "Anderson"} +} +``` + +*The full list of `@pretty` options are `sortKeys`, `indent`, `prefix`, and `width`. +Please see [Pretty Options](https://github.com/tidwall/pretty#customized-output) for more information.* + +#### Custom modifiers + +You can also add custom modifiers. + +For example, here we create a modifier which makes the entire JSON payload upper or lower case. + +```go +gjson.AddModifier("case", func(json, arg string) string { + if arg == "upper" { + return strings.ToUpper(json) + } + if arg == "lower" { + return strings.ToLower(json) + } + return json +}) +"children.@case:upper" ["SARA","ALEX","JACK"] +"children.@case:lower.@reverse" ["jack","alex","sara"] +``` + diff --git a/json/gjson/gjson.go b/pkg/json/gjson/gjson.go similarity index 100% rename from json/gjson/gjson.go rename to pkg/json/gjson/gjson.go diff --git a/json/gjson/gjson_gae.go b/pkg/json/gjson/gjson_gae.go similarity index 100% rename from json/gjson/gjson_gae.go rename to pkg/json/gjson/gjson_gae.go diff --git a/json/gjson/gjson_ngae.go b/pkg/json/gjson/gjson_ngae.go similarity index 97% rename from json/gjson/gjson_ngae.go rename to pkg/json/gjson/gjson_ngae.go index 2899700..8ae9437 100644 --- a/json/gjson/gjson_ngae.go +++ b/pkg/json/gjson/gjson_ngae.go @@ -1,5 +1,5 @@ -//go:build !appengine && !js -// +build !appengine,!js +//+build !appengine +//+build !js package gjson diff --git a/json/gjson/gjson_test.go b/pkg/json/gjson/gjson_test.go similarity index 99% rename from json/gjson/gjson_test.go rename to pkg/json/gjson/gjson_test.go index 0fea7d0..da3caf4 100644 --- a/json/gjson/gjson_test.go +++ b/pkg/json/gjson/gjson_test.go @@ -1480,8 +1480,8 @@ func TestArrayValues(t *testing.T) { `Index:0}`, `gjson.result{Type:2, raw:"0", Str:"", Num:0, Index:0}`, }, "\n") - if output != expect { - t.Fatalf("expected '%v', got '%v'", expect, output) + if output != expect+"\n" { + t.Fatalf("expected: \n'%v'\n got: \n'%v'\n", expect, output) } } diff --git a/pkg/json/gjson/logo.png b/pkg/json/gjson/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..17a8bbe9d651e5d79cbd59a3645a152443440ad6 GIT binary patch literal 15936 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

Bg_)L$48&o}ack zt!HAQladuqH6nxcdZjxF6vTK*7%AV0IOX}JZ^u_OkMumMel#gIzv1VJUb|?$j71?Q zE%B-a&vFk0&Z20HLn$L5hH9dQ4Ef5bKHOX_RfA5_`gJvR%bW4}0DCGg0Va&YC>upU z1&8GC>xLY}LkxejF;yq2sOY~5Cqo4UPpDVfIk~O=P(Gb%k`up^XsPRv9)fXL9y}y5 z_nYE{jUJUly7pJ?$r_eiajL0B@N?t6efF1pS9lk2UGDMWIj>@eS3OH*kme)>qMs4c zXm_q!8hu`I8Dhv5J1?UvlYLJ*X6D(~=y^sb&mT@}>JxIp-$sTvzSdP`p2 zJSTpI%GX_eG4+h<`8LXOC{XRWB^Zd0<_&>b2eoF`!(!~j1?f~Xrey`D;63!OSo@1j zhVkNITfkbp3}EkdgcmMq1ZjCrPrG8@X$!y3nZFDG<8?k82gdmVJ<<=koN(R0uQJ&q&;Hm9we)+ z>EiW_<2L!S=W{bXQR{t-Xk&^=i3!xJ1GH*$ft^*`kFY2U5$7}?R(xpv1OE!wXhM-U z{|-m&g!{Wci5J~AR7xhAcz2%T1X}X;Lm5kqn$n>CdZG%!jlgyEgu}8cSE=Tna>KaW zE{i{1$MRasX61aWzh$Kz+gg_XXfmLI)gJjN8%-etjvKrZa0TIVu@FNzi_DBbgG}?X zc%`<^VRHLdaERnl?k6tkmy~V-6wDDz--2n{wg8GA9pEX|*>WDA)uFS%@&MN%-fruuv;9cz5hkb^M5ny{e|a>hsQrwX-D^ zNBO*f*^1b~?2NHQ2g~@Hiq$_V;Z8|Q@sz(OJ-_;@xOeF3^57a-&;bjXTR0^(9&O1`Qs5mDXKaZ{gYh~^a4>N0el@Q8ji@;G0VwtnJC=gh zkvhS5%hY0~-FeS=8*`P?hYjBB(TJ(h3vvLJ;q{z?7Jf~hQZOlh z8(M!wZ6}#fur(A{mkQvWp^(z{kJ9Qcf&hRH6HK!c5b1?W28wck^aQ{Ju^0{PD&(m< zLbu{`I17`!l>tz)uDi~j;Q*7#M!X;*rP#E#K`0MHVO=C_Y>Kpq3I%tz)KS21z(70w zu+S+efLBlwQ<3knLW3-aM&dW%uuv1r?kQqDWsn|wHc35K!jBl%t~9oLn(8w!(iF2Y zj)Ca3zC|-dT69O*c44Drzb zTWRcGB4pjuzq;tNLKJ7H7&3CTH`^5&fs^&Csh+4TdNT~n>o18P123FK1<6Ap7>9OE zH?~wjjt8TicxY-E;Ld@{^P%j9BpeC^HJlw8iW2I<*Vc#1VH5Y&k@$T23>riN5a^fE z1xtK3We+L~pTm`C@mKj&g@flbKtRjuc?i2+3nW^d516YT2Qx*|&}0%IIZ{BK)n@-A z7W$f>T8g%2qR?15Ezb7#Al1jSng=Um;<;^4CZ)UF4*B#s16}WxXDR&e;IFDemyJBP zQD>pCTMx2W)Iwg$ckc4ElkYeRp?hVt(&_O^6u-=WO4y!T>T70vx)M)1f17nmsqw{$ zZhYP=C<2j}m^Lg-ex&8RKf~0WrfrL!~NLQ^Xk?)No>Z1xiNn~MBa)Y#@)-369lm6 zDH#U3Pv&kw{mt3+_t3uW8=*{`^%?Rl0~ff@XZrjZDZCjasI5PtLP6)zBrRO$pYlfV z4DCdS&(8)uR*23AuP?jhTi@#6yL=G*b~7!5d3?2d_2DzzR3V+SVCZe@ULo|*Qz`CV z*j}+=>D|^uy$<{s0tgCLYba;t*4jWsj}CfRI?T4cS90us!fy8Lz;(qp7V}H&`+loKEpPZr6wt`2EqWMWj z6-EjjNWU@D@IQdNmlvT5>qUq{`9m - // 18 . } - // 19 } -} +//func Example_TQL() { +// tqlString := `insert into entity3 select entity1.*,entity1.property1 as property1, entity2.property2.name as property2, entity1.property1 + entity2.property3 as property3` +// expr, _ := Parse(tqlString) +// Dump(expr) +// //OUTPUT: +// // +// //0 Root { +// // 1 . Select { +// // 2 . . Field (property1) { +// // 3 . . . "ref:&{entity1.property1}" +// // 4 . . } +// // 5 . . Field (property2) { +// // 6 . . . "ref:&{entity2.property2.name}" +// // 7 . . } +// // 8 . . Field (property3) { +// // 9 . . . Op [ADD] { +// // 10 . . . . "ref:&{entity1.property1}" +// // 11 . . . . "ref:&{entity2.property3}" +// // 12 . . . } +// // 13 . . } +// // 14 . } +// // 15 . Topic [] {} +// // 16 . Where { +// // 17 . . +// // 18 . } +// // 19 } +//} //func TestExec3(t *testing.T) { // tqlString := `insert into entity3 select entity1.property1 as property1, entity2.property2.name as property2, entity1.property1 + entity2.property3 as property3` @@ -114,7 +115,7 @@ func Example_TQL() { //} func TestString(t *testing.T) { - tqlText := "insert into 4c1e33a1-6899-4643-a6b3-46cf37950b7f select 54cf69fc-78c3-4f79-9f6b-5d5e5bd8d3c0.sysField._spacePath + '/4c1e33a1-6899-4643-a6b3-46cf37950b7f' as sysField._spacePath" + tqlText := "insert into SS4c1e33a1-6899-4643-a6b3-46cf37950b7f select 54cf69fc-78c3-4f79-9f6b-5d5e5bd8d3c0.sysField._spacePath + '/4c1e33a1-6899-4643-a6b3-46cf37950b7f' as sysField._spacePath" tqlIns, err := NewTDTL(tqlText, nil) assert.Nil(t, err) t.Log(tqlIns.Target()) diff --git a/test.go b/test.go index 7bca8b8..7ab3a64 100644 --- a/test.go +++ b/test.go @@ -32,7 +32,7 @@ var ( "age":37, "temperature":50, "children": ["Sara","Alex","Jack"], "fav.movie": "Deer Hunter", - "movie": {"1111": [{"1111": "Tom", "last": "Anderson"}], "last": "Anderson"}, + "movie": {"1111": [{"1111": "Tom", "last": "Anderson"},{"first": "Neo"}], "last": "Anderson"}, "friends": [ {"first": "Dale", "last": "Murphy", "age": 44}, {"first": "Roger", "last": "Craig", "age": 68}, diff --git a/types.go b/types.go index 063f7c5..ad41d34 100644 --- a/types.go +++ b/types.go @@ -17,202 +17,8 @@ package tdtl import ( "fmt" - "strconv" - "strings" ) -var ( - UNDEFINED_RESULT = &DefaultNode{typ: Undefined} - NULL_RESULT = &DefaultNode{typ: Undefined} -) - -// Type node type -type Type int - -const ( - // Undefine is Not a value - // This isn't explicitly representable in JSON except by omitting the value. - Undefined Type = iota - // Null is a null json value - Null - // Bool is a json boolean - Bool - // Number is json number, include Int and Float - Number - // Int is json number, a discrete Int - Int - // Float is json number - Float - // String is a json string - String - // JSON is a raw block of JSON - JSON -) - -// String returns a string representation of the type. -func (t Type) String() string { - switch t { - default: - return "Undefined" - case Null: - return "Null" - case Bool: - return "Bool" - case Int: - return "Int" - case Float: - return "Float" - case String: - return "String" - case JSON: - return "JSON" - } -} - -//Node interface -type Node interface { - Type() Type - To(Type) Node - String() string -} - -//DefaultNode interface -type DefaultNode struct { - // Type is the json type - typ Type - // raw is the raw json - raw string -} - -func (r DefaultNode) Type() Type { return r.typ } -func (r DefaultNode) To(Type) Node { - return r -} -func (r DefaultNode) String() string { - return r.raw -} - -type BoolNode bool - -func (r BoolNode) Type() Type { return Bool } -func (r BoolNode) To(typ Type) Node { - switch typ { - case Bool: - return r - case String: - return StringNode(fmt.Sprintf("%t", r)) - } - return UNDEFINED_RESULT -} -func (r BoolNode) String() string { - return fmt.Sprintf("%t", r) -} - -type IntNode int64 - -func (r IntNode) Type() Type { return Int } -func (r IntNode) To(typ Type) Node { - switch typ { - case Number, Int: - return r - case Float: - return FloatNode(r) - case String: - return StringNode(fmt.Sprintf("%d", r)) - } - return UNDEFINED_RESULT -} -func (r IntNode) String() string { - return fmt.Sprintf("%d", r) -} - -type FloatNode float64 - -func (r FloatNode) Type() Type { return Float } -func (r FloatNode) To(typ Type) Node { - switch typ { - case Number, Float: - return r - case Int: - return IntNode(r) - case String: - return StringNode(fmt.Sprintf("%f", r)) - } - return UNDEFINED_RESULT -} -func (r FloatNode) String() string { - return fmt.Sprintf("%f", r) -} - -type StringNode string - -func (r StringNode) Type() Type { return String } -func (r StringNode) To(typ Type) Node { - switch typ { - case String: - return r - case Bool: - b, err := strconv.ParseBool(string(r)) - if err != nil { - return UNDEFINED_RESULT - } - return BoolNode(b) - case Number: - if strings.Index(string(r), ".") == -1 { - return r.To(Int) - } - return r.To(Float) - case Int: - b, err := strconv.ParseInt(string(r), 10, 64) - if err != nil { - return UNDEFINED_RESULT - } - return IntNode(b) - case Float: - b, err := strconv.ParseFloat(string(r), 64) - if err != nil { - return UNDEFINED_RESULT - } - return FloatNode(b) - } - return UNDEFINED_RESULT -} -func (r StringNode) String() string { - return string(r) -} - -// JSONNode maybe Object or Array -type JSONNode string - -func (r JSONNode) Type() Type { return JSON } -func (r JSONNode) To(typ Type) Node { - return UNDEFINED_RESULT -} -func (r JSONNode) Update(key string, value Node) (val string, err error) { - switch value := value.(type) { - case FloatNode, IntNode, BoolNode: - v := value.To(String) - switch v := v.(type) { - case StringNode: - val, err = updateJSON(r, key, v) - } - case StringNode: - val, err = updateJSON(r, key, "\""+value+"\"") - case JSONNode: - if key == "" { - val = string(value) - } else { - val, err = updateJSON(r, key, StringNode(value)) - } - default: - val, err = "", fmt.Errorf("unknown type") - } - return -} -func (r JSONNode) String() string { - return string(r) -} - //Expr type Expr interface { expr() @@ -229,13 +35,12 @@ func (*SwitchExpr) expr() {} func (CaseListExpr) expr() {} func (*CaseExpr) expr() {} -func (DefaultNode) expr() {} -func (BoolNode) expr() {} -func (IntNode) expr() {} -func (FloatNode) expr() {} -func (StringNode) expr() {} -func (*CallExpr) expr() {} -func (JSONNode) expr() {} +func (BoolNode) expr() {} +func (IntNode) expr() {} +func (FloatNode) expr() {} +func (StringNode) expr() {} +func (*CallExpr) expr() {} +func (JSONNode) expr() {} //BinaryExpr type BinaryExpr struct { diff --git a/types_test.go b/types_test.go index 5ee42ff..49a4ec1 100644 --- a/types_test.go +++ b/types_test.go @@ -1,9 +1,49 @@ +/* +Copyright 2021 The tKeel Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package tdtl import ( + "github.com/stretchr/testify/assert" "testing" ) +func TestType(t *testing.T) { + { + first := StringNode("Collect") + last := StringNode("JS") + fullname := first + last + assert.Equal(t, fullname.String(), "CollectJS", "The two words should be the same.") + } + + { + a := FloatNode(1.1) + b := FloatNode(0.1) + c := a / b + assert.Equal(t, c.String(), "11.000000", "The two words should be the same.") + } + + { + a := IntNode(11) + b := IntNode(4) + c := a % b + assert.Equal(t, c.String(), "3", "The two words should be the same.") + } +} + func BenchmarkStringNodeToNumber(b *testing.B) { s := StringNode("123.4") for i := 0; i < b.N; i++ { diff --git a/utils.go b/utils.go index e43de49..23c9861 100644 --- a/utils.go +++ b/utils.go @@ -17,10 +17,7 @@ package tdtl import ( "fmt" - "strings" - "github.com/antlr/antlr4/runtime/Go/antlr" - "github.com/tkeel-io/tdtl/json/jsonparser" "github.com/tkeel-io/tdtl/parser" ) @@ -78,12 +75,6 @@ func parse(expr string) (*parser.TDTLParser, *TDTLListener) { return parse, &listener } -func updateJSON(node JSONNode, xpath string, setValue StringNode) (string, error) { - keys := strings.Split(xpath, ".") - value, err := jsonparser.Set([]byte(node), []byte(setValue), keys...) - return string(value), err -} - func ParseFunc(x Expr) []*CallExpr { ret := &callList{make([]*CallExpr, 0)} ret.walkFunc(x)