diff --git a/cli/encoder.go b/cli/encoder.go index 8841baac..b54ff50a 100644 --- a/cli/encoder.go +++ b/cli/encoder.go @@ -9,6 +9,8 @@ import ( "sort" "strconv" "unicode/utf8" + + "github.com/wader/gojq" ) type encoder struct { @@ -66,6 +68,10 @@ func (e *encoder) encode(v any) error { if err := e.encodeObject(v); err != nil { return err } + case gojq.JQValue: + if err := e.encode(v.JQValueToGoJQ()); err != nil { + return err + } default: panic(fmt.Sprintf("invalid type: %[1]T (%[1]v)", v)) } diff --git a/debug.go b/debug.go index ad3d7216..10d022c8 100644 --- a/debug.go +++ b/debug.go @@ -198,6 +198,8 @@ func debugValue(v any) string { return fmt.Sprintf("gojq.Iter(%#v)", v) case []pathValue: return fmt.Sprintf("[]gojq.pathValue(%v)", v) + case JQValue: + return fmt.Sprintf("gojq.JQValue(%s)", Preview(v)) case [2]int: return fmt.Sprintf("[%d,%d]", v[0], v[1]) case [3]int: diff --git a/encoder.go b/encoder.go index 3233e8a9..e3c4f0f2 100644 --- a/encoder.go +++ b/encoder.go @@ -68,6 +68,8 @@ func (e *encoder) encode(v any) { e.encodeArray(v) case map[string]any: e.encodeObject(v) + case JQValue: + e.encode(v.JQValueToGoJQ()) default: panic(fmt.Sprintf("invalid type: %[1]T (%[1]v)", v)) } diff --git a/execute.go b/execute.go index f1248244..c88720db 100644 --- a/execute.go +++ b/execute.go @@ -61,6 +61,9 @@ loop: m := make(map[string]any, n) for i := 0; i < n; i++ { v, k := env.pop(), env.pop() + if jv, ok := k.(JQValue); ok { + k = jv.JQValueToString() + } s, ok := k.(string) if !ok { err = &objectKeyNotStringError{k} @@ -141,7 +144,9 @@ loop: pc = code.v.(int) goto loop case opjumpifnot: - if v := env.pop(); v == nil || v == false { + v := env.pop() + b, bOk := toBoolean(v) + if isNull(v) || (bOk && !b) { pc = code.v.(int) goto loop } @@ -151,6 +156,9 @@ loop: } p, v := code.v, env.pop() if code.op == opindexarray && v != nil { + if jqv, ok := v.(JQValue); ok { + v = jqv.JQValueToGoJQ() + } if _, ok := v.([]any); !ok { err = &expectedArrayError{v} break loop @@ -312,6 +320,32 @@ loop: continue } break loop + case JQValue: + if !env.paths.empty() && env.expdepth == 0 && !env.pathIntact(v) { + err = &invalidPathIterError{v} + break loop + } + xsv := v.JQValueEach() + if e, ok := xsv.(error); ok { + err = e + break loop + } + switch xsv := xsv.(type) { + case []PathValue: + // convert from external PathValue to internal pathValue to make it easier to follow upstream + xs = make([]pathValue, len(xsv)) + if len(xsv) == 0 { + break loop + } + for i, pv := range xsv { + xs[i] = pathValue{path: pv.Path, value: pv.Value} + } + case nil: + break loop + default: + err = &iteratorError{xsv} + break loop + } default: err = &iteratorError{v} env.push(emptyIter{}) @@ -423,6 +457,9 @@ func (env *env) pathIntact(v any) bool { if w, ok := w.(float64); ok { return v == w || math.IsNaN(v) && math.IsNaN(w) } + case JQValue: + // TODO: JQValue: should understand this better + return true } return v == w } diff --git a/func.go b/func.go index 8a6dcffe..9fdab460 100644 --- a/func.go +++ b/func.go @@ -328,13 +328,15 @@ func funcLength(v any) any { return len(v) case map[string]any: return len(v) + case JQValue: + return v.JQValueLength() default: return &func0TypeError{"length", v} } } func funcUtf8ByteLength(v any) any { - s, ok := v.(string) + s, ok := toString(v) if !ok { return &func0TypeError{"utf8bytelength", v} } @@ -355,6 +357,8 @@ func funcKeys(v any) any { w[i] = k } return w + case JQValue: + return v.JQValueKeys() default: return &func0TypeError{"keys", v} } @@ -381,6 +385,8 @@ func values(v any) ([]any, bool) { vs[i] = v[k] } return vs, true + case JQValue: + return values(v.JQValueToGoJQ()) default: return nil, false } @@ -399,6 +405,8 @@ func funcHas(v, x any) any { } case nil: return false + case JQValue: + return v.JQValueHas(x) } return &func1TypeError{"has", v, x} } @@ -417,12 +425,48 @@ func funcToEntries(v any) any { w[i] = map[string]any{"key": k, "value": v[k]} } return w + case JQValue: + // to_entries/0 used to be implemented in jq but was made internal for + // performance. To preserve the JQValue keys order we have to implement + // it ourself, otherwise keys will be sorted. + if v.JQValueType() == JQTypeObject { + lv := v.JQValueLength() + if err, ok := lv.(error); ok { + return err + } + l, ok := toInt(lv) + if !ok { + return fmt.Errorf("invalid int length: %v", lv) + } + ev := v.JQValueEach() + e, ok := ev.([]PathValue) + if !ok { + return &func0TypeError{"to_entries", v} + } + + w := make([]any, l) + for i, pv := range e { + k, ok := pv.Path.(string) + if !ok { + return &func0TypeError{"to_entries", v} + } + + w[i] = map[string]any{"key": k, "value": pv.Value} + } + + return w + } + return funcToEntries(v.JQValueToGoJQ()) default: return &func0TypeError{"to_entries", v} } } func funcFromEntries(v any) any { + if jqv, ok := v.(JQValue); ok { + v = jqv.JQValueToGoJQ() + } + vs, ok := v.([]any) if !ok { return &func0TypeError{"from_entries", v} @@ -438,6 +482,13 @@ func funcFromEntries(v any) any { ) for _, k := range [4]string{"key", "Key", "name", "Name"} { if k := v[k]; k != nil && k != false { + if jqvk, ok := k.(JQValue); ok { + k = jqvk.JQValueToGoJQ() + if k == false { + continue + } + } + if key, ok = k.(string); !ok { return &func0WrapError{"from_entries", vs, &objectKeyNotStringError{k}} } @@ -462,6 +513,7 @@ func funcFromEntries(v any) any { func funcAdd(v any) any { vs, ok := values(v) + if !ok { return &func0TypeError{"add", v} } @@ -531,6 +583,8 @@ func funcToNumber(v any) any { return &func0WrapError{"tonumber", v, errors.New("invalid number")} } return toNumber(v) + case JQValue: + return v.JQValueToNumber() default: return &func0TypeError{"tonumber", v} } @@ -541,7 +595,7 @@ func toNumber(v string) any { } func funcToString(v any) any { - if s, ok := v.(string); ok { + if s, ok := toString(v); ok { return s } return funcToJSON(v) @@ -552,10 +606,11 @@ func funcType(v any) any { } func funcReverse(v any) any { - vs, ok := v.([]any) + vs, ok := toArray(v) if !ok { return &func0TypeError{"reverse", v} } + ws := make([]any, len(vs)) for i, v := range vs { ws[len(ws)-i-1] = v @@ -658,21 +713,23 @@ func indexFunc(name string, v, x any, f func(_, _ []any) any) any { return f(v, []any{x}) } case string: - if x, ok := x.(string); ok { + if x, ok := toString(x); ok { return f(explode(v), explode(x)) } - return &func1TypeError{name, v, x} + return func1TypeError{name, v, x} + case JQValue: + return indexFunc(name, v.JQValueToGoJQ(), x, f) default: return &func1TypeError{name, v, x} } } func funcStartsWith(v, x any) any { - s, ok := v.(string) + s, ok := toString(v) if !ok { return &func1TypeError{"startswith", v, x} } - t, ok := x.(string) + t, ok := toString(x) if !ok { return &func1TypeError{"startswith", v, x} } @@ -680,11 +737,11 @@ func funcStartsWith(v, x any) any { } func funcEndsWith(v, x any) any { - s, ok := v.(string) + s, ok := toString(v) if !ok { return &func1TypeError{"endswith", v, x} } - t, ok := x.(string) + t, ok := toString(x) if !ok { return &func1TypeError{"endswith", v, x} } @@ -692,11 +749,11 @@ func funcEndsWith(v, x any) any { } func funcLtrimstr(v, x any) any { - s, ok := v.(string) + s, ok := toString(v) if !ok { return &func1TypeError{"ltrimstr", v, x} } - t, ok := x.(string) + t, ok := toString(x) if !ok { return &func1TypeError{"ltrimstr", v, x} } @@ -704,11 +761,11 @@ func funcLtrimstr(v, x any) any { } func funcRtrimstr(v, x any) any { - s, ok := v.(string) + s, ok := toString(v) if !ok { return &func1TypeError{"rtrimstr", v, x} } - t, ok := x.(string) + t, ok := toString(x) if !ok { return &func1TypeError{"rtrimstr", v, x} } @@ -716,11 +773,11 @@ func funcRtrimstr(v, x any) any { } func funcExplode(v any) any { - s, ok := v.(string) + x, ok := toString(v) if !ok { return &func0TypeError{"explode", v} } - return explode(s) + return explode(x) } func explode(s string) []any { @@ -734,7 +791,7 @@ func explode(s string) []any { } func funcImplode(v any) any { - vs, ok := v.([]any) + vs, ok := toArray(v) if !ok { return &func0TypeError{"implode", v} } @@ -755,11 +812,11 @@ func funcImplode(v any) any { } func funcSplit(v any, args []any) any { - s, ok := v.(string) + s, ok := toString(v) if !ok { return &func0TypeError{"split", v} } - x, ok := args[0].(string) + x, ok := toString(args[0]) if !ok { return &func0TypeError{"split", x} } @@ -769,7 +826,7 @@ func funcSplit(v any, args []any) any { } else { var flags string if args[1] != nil { - v, ok := args[1].(string) + v, ok := toString(args[1]) if !ok { return &func0TypeError{"split", args[1]} } @@ -819,7 +876,7 @@ func funcToJSON(v any) any { } func funcFromJSON(v any) any { - s, ok := v.(string) + s, ok := toString(v) if !ok { return &func0TypeError{"fromjson", v} } @@ -836,14 +893,14 @@ func funcFromJSON(v any) any { } func funcFormat(v, x any) any { - s, ok := x.(string) + sx, ok := toString(x) if !ok { return &func0TypeError{"format", x} } - format := "@" + s - f := formatToFunc(format) + fmt := "@" + sx + f := formatToFunc(fmt) if f == nil { - return &formatNotFoundError{format} + return &formatNotFoundError{fmt} } return internalFuncs[f.Name].callback(v, nil) } @@ -916,7 +973,7 @@ var shEscaper = strings.NewReplacer( ) func funcToSh(v any) any { - if _, ok := v.([]any); !ok { + if _, ok := toArray(v); !ok { v = []any{v} } return formatJoin("sh", v, " ", func(s string) string { @@ -925,12 +982,16 @@ func funcToSh(v any) any { } func formatJoin(typ string, v any, sep string, escape func(string) string) any { - vs, ok := v.([]any) + vs, ok := toArray(v) if !ok { return &func0TypeError{"@" + typ, v} } ss := make([]string, len(vs)) for i, v := range vs { + if jqv, ok := v.(JQValue); ok { + v = jqv.JQValueToGoJQ() + } + switch v := v.(type) { case []any, map[string]any: return &formatRowError{typ, v} @@ -971,6 +1032,7 @@ func funcToBase64d(v any) any { } func funcIndex2(_, v, x any) any { + switch x := x.(type) { case string: switch v := v.(type) { @@ -978,6 +1040,8 @@ func funcIndex2(_, v, x any) any { return nil case map[string]any: return v[x] + case JQValue: + return v.JQValueKey(x) default: return &expectedObjectError{v} } @@ -990,6 +1054,22 @@ func funcIndex2(_, v, x any) any { return index(v, i) case string: return indexString(v, i) + case JQValue: + lv := v.JQValueSliceLen() + l, ok := lv.(int) + if !ok { + return lv + } + i := clampIndex(i, -1, l) + + // TODO: JQValue -2 outside < 0, -1 outside > len + // TODO: redo this, nice to know actual index? + if i < 0 { + i = -2 + } else if i >= l { + i = -1 + } + return v.JQValueIndex(i) default: return &expectedArrayError{v} } @@ -1015,6 +1095,8 @@ func funcIndex2(_, v, x any) any { return &expectedStartEndError{x} } return funcSlice(nil, v, end, start) + case JQValue: + return funcIndex2(nil, v, x.JQValueToGoJQ()) default: switch v.(type) { case []any: @@ -1056,6 +1138,8 @@ func funcSlice(_, v, e, s any) (r any) { return slice(v, e, s) case string: return sliceString(v, e, s) + case JQValue: + return sliceJQValue(v, e, s) default: return &expectedArrayError{v} } @@ -1124,6 +1208,33 @@ func sliceString(v string, e, s any) any { return v[start:end] } +func sliceJQValue(v JQValue, e, s any) any { + lv := v.JQValueSliceLen() + l, ok := lv.(int) + if !ok { + return lv + } + + var start, end int + if s != nil { + if i, ok := toInt(s); ok { + start = clampIndex(i, 0, l) + } else { + return &arrayIndexNotNumberError{s} + } + } + if e != nil { + if i, ok := toInt(e); ok { + end = clampIndex(i, start, l) + } else { + return &arrayIndexNotNumberError{e} + } + } else { + end = l + } + return v.JQValueSlice(start, end) +} + func clampIndex(i, min, max int) int { if i < 0 { i += max @@ -1159,7 +1270,7 @@ func funcFlatten(v any, args []any) any { func flatten(xs, vs []any, depth float64) []any { for _, v := range vs { - if vs, ok := v.([]any); ok && depth != 0 { + if vs, ok := toArray(v); ok && depth != 0 { xs = flatten(xs, vs, depth-1) } else { xs = append(xs, v) @@ -1182,7 +1293,12 @@ func (iter *rangeIter) Next() (any, bool) { } func funcRange(_ any, xs []any) any { - for _, x := range xs { + for i, x := range xs { + if jqv, ok := x.(JQValue); ok { + x = jqv.JQValueToGoJQ() + xs[i] = x + } + switch x.(type) { case int, float64, *big.Int: default: @@ -1193,7 +1309,7 @@ func funcRange(_ any, xs []any) any { } func funcMin(v any) any { - vs, ok := v.([]any) + vs, ok := toArray(v) if !ok { return &func0TypeError{"min", v} } @@ -1201,11 +1317,11 @@ func funcMin(v any) any { } func funcMinBy(v, x any) any { - vs, ok := v.([]any) + vs, ok := toArray(v) if !ok { return &func1TypeError{"min_by", v, x} } - xs, ok := x.([]any) + xs, ok := toArray(x) if !ok { return &func1TypeError{"min_by", v, x} } @@ -1216,7 +1332,7 @@ func funcMinBy(v, x any) any { } func funcMax(v any) any { - vs, ok := v.([]any) + vs, ok := toArray(v) if !ok { return &func0TypeError{"max", v} } @@ -1224,11 +1340,11 @@ func funcMax(v any) any { } func funcMaxBy(v, x any) any { - vs, ok := v.([]any) + vs, ok := toArray(v) if !ok { return &func1TypeError{"max_by", v, x} } - xs, ok := x.([]any) + xs, ok := toArray(x) if !ok { return &func1TypeError{"max_by", v, x} } @@ -1255,18 +1371,23 @@ type sortItem struct { value, key any } -func sortItems(name string, v, x any) ([]*sortItem, error) { - vs, ok := v.([]any) +func sortItems(name string, v, x any) ([]*sortItem, any) { + var ok bool + var vs []any + + vs, ok = toArray(v) if !ok { if strings.HasSuffix(name, "_by") { return nil, &func1TypeError{name, v, x} } return nil, &func0TypeError{name, v} } - xs, ok := x.([]any) + + xs, ok := toArray(x) if !ok { return nil, &func1TypeError{name, v, x} } + if len(vs) != len(xs) { return nil, &func1WrapError{name, v, x, &lengthMismatchError{}} } @@ -1348,12 +1469,16 @@ func funcJoin(v, x any) any { if len(vs) == 0 { return "" } - sep, ok := x.(string) + sep, ok := toString(x) if len(vs) > 1 && !ok { return &func1TypeError{"join", v, x} } ss := make([]string, len(vs)) for i, v := range vs { + if jqv, ok := v.(JQValue); ok { + v = jqv.JQValueToGoJQ() + } + switch v := v.(type) { case nil: case string: @@ -1517,7 +1642,7 @@ func funcSetpathWithAllocator(v any, args []any) any { } func setpath(v, p, n any, a allocator) any { - path, ok := p.([]any) + path, ok := toArray(p) if !ok { return &func1TypeError{"setpath", v, p} } @@ -1538,7 +1663,7 @@ func funcDelpathsWithAllocator(v any, args []any) any { } func delpaths(v, p any, a allocator) any { - paths, ok := p.([]any) + paths, ok := toArray(p) if !ok { return &func1TypeError{"delpaths", v, p} } @@ -1552,7 +1677,7 @@ func delpaths(v, p any, a allocator) any { var err error u := v for _, q := range paths { - path, ok := q.([]any) + path, ok := toArray(q) if !ok { return &func1WrapError{"delpaths", v, p, &expectedArrayError{q}} } @@ -1568,7 +1693,23 @@ func update(v any, path []any, n any, a allocator) (any, error) { if len(path) == 0 { return n, nil } - switch p := path[0].(type) { + + if jqv, ok := v.(JQValue); ok { + v = jqv.JQValueToGoJQ() + if err, ok := v.(error); ok { + return nil, err + } + } + + p0 := path[0] + if jqv, ok := p0.(JQValue); ok { + p0 = jqv.JQValueToGoJQ() + if err, ok := v.(error); ok { + return nil, err + } + } + + switch p := p0.(type) { case string: switch v := v.(type) { case nil: @@ -1761,14 +1902,14 @@ func deleteEmpty(v any) any { } func funcGetpath(v, p any) any { - path, ok := p.([]any) + path, ok := toArray(p) if !ok { return &func1TypeError{"getpath", v, p} } u := v for _, x := range path { switch v.(type) { - case nil, []any, map[string]any: + case nil, []any, map[string]any, JQValue: v = funcIndex2(nil, v, x) if err, ok := v.(error); ok { return &func1WrapError{"getpath", u, p, err} @@ -1781,7 +1922,7 @@ func funcGetpath(v, p any) any { } func funcTranspose(v any) any { - vss, ok := v.([]any) + vss, ok := toArray(v) if !ok { return &func0TypeError{"transpose", v} } @@ -1790,7 +1931,7 @@ func funcTranspose(v any) any { } var l int for _, vs := range vss { - vs, ok := vs.([]any) + vs, ok := toArray(vs) if !ok { return &func0TypeError{"transpose", v} } @@ -1806,7 +1947,8 @@ func funcTranspose(v any) any { xs[i] = s } for i, vs := range vss { - for j, v := range vs.([]any) { + vs, _ := toArray(vs) + for j, v := range vs { wss[j][i] = v } } @@ -1814,7 +1956,7 @@ func funcTranspose(v any) any { } func funcBsearch(v, t any) any { - vs, ok := v.([]any) + vs, ok := toArray(v) if !ok { return &func1TypeError{"bsearch", v, t} } @@ -1856,7 +1998,7 @@ func epochToArray(v float64, loc *time.Location) []any { } func funcMktime(v any) any { - a, ok := v.([]any) + a, ok := toArray(v) if !ok { return &func0TypeError{"mktime", v} } @@ -1875,11 +2017,11 @@ func funcStrftime(v, x any) any { if w, ok := toFloat(v); ok { v = epochToArray(w, time.UTC) } - a, ok := v.([]any) + a, ok := toArray(v) if !ok { return &func1TypeError{"strftime", v, x} } - format, ok := x.(string) + format, ok := toString(x) if !ok { return &func1TypeError{"strftime", v, x} } @@ -1894,11 +2036,11 @@ func funcStrflocaltime(v, x any) any { if w, ok := toFloat(v); ok { v = epochToArray(w, time.Local) } - a, ok := v.([]any) + a, ok := toArray(v) if !ok { return &func1TypeError{"strflocaltime", v, x} } - format, ok := x.(string) + format, ok := toString(x) if !ok { return &func1TypeError{"strflocaltime", v, x} } @@ -1910,11 +2052,11 @@ func funcStrflocaltime(v, x any) any { } func funcStrptime(v, x any) any { - s, ok := v.(string) + s, ok := toString(v) if !ok { return &func1TypeError{"strptime", v, x} } - format, ok := x.(string) + format, ok := toString(x) if !ok { return &func1TypeError{"strptime", v, x} } @@ -1979,17 +2121,17 @@ func funcMatch(v, re, fs, testing any) any { } var flags string if fs != nil { - v, ok := fs.(string) + v, ok := toString(fs) if !ok { return &func2TypeError{name, v, re, fs} } flags = v } - s, ok := v.(string) + s, ok := toString(v) if !ok { return &func2TypeError{name, v, re, fs} } - restr, ok := re.(string) + restr, ok := toString(re) if !ok { return &func2TypeError{name, v, re, fs} } @@ -2106,6 +2248,50 @@ func funcHaltError(v any, args []any) any { return &haltError{v, code} } +func toString(x any) (string, bool) { + switch x := x.(type) { + case string: + return x, true + case JQValue: + return toString(x.JQValueToGoJQ()) + default: + return "", false + } +} + +func toArray(x any) ([]any, bool) { + switch x := x.(type) { + case []any: + return x, true + case JQValue: + return toArray(x.JQValueToGoJQ()) + default: + return nil, false + } +} + +func toBoolean(x any) (bool, bool) { + switch x := x.(type) { + case bool: + return x, true + case JQValue: + return toBoolean(x.JQValueToGoJQ()) + default: + return false, false + } +} + +func isNull(x any) bool { + switch x := x.(type) { + case nil: + return true + case JQValue: + return isNull(x.JQValueToGoJQ()) + default: + return false + } +} + func toInt(x any) (int, bool) { switch x := x.(type) { case int: @@ -2122,6 +2308,8 @@ func toInt(x any) (int, bool) { return math.MaxInt, true } return math.MinInt, true + case JQValue: + return toInt(x.JQValueToGoJQ()) default: return 0, false } @@ -2145,6 +2333,8 @@ func toFloat(x any) (float64, bool) { return x, true case *big.Int: return bigToFloat(x), true + case JQValue: + return toFloat(x.JQValueToGoJQ()) default: return 0.0, false } diff --git a/gojq.go b/gojq.go index e078c809..fe807b87 100644 --- a/gojq.go +++ b/gojq.go @@ -3,3 +3,67 @@ // // [Usage as a library]: https://github.com/itchyny/gojq#usage-as-a-library package gojq + +// TODO: use in TypeOf? +const ( + JQTypeArray = "array" + JQTypeBoolean = "boolean" + JQTypeNull = "null" + JQTypeNumber = "number" + JQTypeObject = "object" + JQTypeString = "string" +) + +// JQValue represents something that can be a jq value +// All functions with return type any can return error on error +// array = []any +// boolean = bool +// null = nil +// number = int | float64 | *big.Int +// object = map[string]any +// string = string +// value = number | boolean | string | array | object | null +type JQValue interface { + // JQValueLength is length of value, ex: value | length + JQValueLength() any // int + + // JQValueSliceLen slice length + JQValueSliceLen() any // int + + // JQValueIndex look up index for value, ex: value[index] + // index -1 outside after slice, -2 outside before slice + JQValueIndex(index int) any // value + + // JQValueSlice slices value, ex: value[start:end] + // start and end indexes are translated and clamped using JQValueSliceLen + JQValueSlice(start int, end int) any // []any + + // JQValueKey look up key value of value: ex: value[name] + JQValueKey(name string) any // value + + // JQValueEach each of value, ex: value[] + JQValueEach() any // []PathValue + + // JQValueKeys keys for value, ex: value | keys + JQValueKeys() any // []string + + // JQValueHas check if value has key, ex: value | has(key) + JQValueHas(key any) any // bool + + // JQValueType type of value, ex: value | type + JQValueType() string // a JQType* constant + + // JQValueToNumber is value represented as a number, ex: value | tonumber + JQValueToNumber() any // number + + // JQValueToString is value represented as a string, ex: value | tostring + JQValueToString() any // string + + // JQValue value represented as a gojq compatible value + JQValueToGoJQ() any // value +} + +// PathValue is a part of a jq path expression +type PathValue struct { + Path, Value any +} diff --git a/normalize.go b/normalize.go index f9a6cde1..4131ffa0 100644 --- a/normalize.go +++ b/normalize.go @@ -7,6 +7,14 @@ import ( "strings" ) +func ValidNumber(s string) bool { + return newLexer(s).validNumber() +} + +func NormalizeNumber(v json.Number) any { + return normalizeNumber(v) +} + func normalizeNumber(v json.Number) any { if i, err := v.Int64(); err == nil && math.MinInt <= i && i <= math.MaxInt { return int(i) @@ -25,6 +33,10 @@ func normalizeNumber(v json.Number) any { return math.Inf(1) } +func NormalizeNumbers(v any) any { + return normalizeNumbers(v) +} + func normalizeNumbers(v any) any { switch v := v.(type) { case json.Number: diff --git a/operator.go b/operator.go index 0c1f57bb..648e5831 100644 --- a/operator.go +++ b/operator.go @@ -317,6 +317,14 @@ func binopTypeSwitch( callbackArrays func(_, _ []any) any, callbackMaps func(_, _ map[string]any) any, fallback func(_, _ any) any) any { + + if lj, ok := l.(JQValue); ok { + l = lj.JQValueToGoJQ() + } + if rj, ok := r.(JQValue); ok { + r = rj.JQValueToGoJQ() + } + switch l := l.(type) { case int: switch r := r.(type) { @@ -385,6 +393,8 @@ func funcOpPlus(v any) any { return v case *big.Int: return v + case JQValue: + return funcOpPlus(v.JQValueToGoJQ()) default: return &unaryTypeError{"plus", v} } @@ -398,6 +408,8 @@ func funcOpNegate(v any) any { return -v case *big.Int: return new(big.Int).Neg(v) + case JQValue: + return funcOpNegate(v.JQValueToGoJQ()) default: return &unaryTypeError{"negate", v} } @@ -450,6 +462,13 @@ func funcOpAdd(_, l, r any) any { if r == nil { return l } + + if isNull(l) { + return r + } else if isNull(r) { + return l + } + return &binopTypeError{"add", l, r} }, ) @@ -619,7 +638,9 @@ func funcOpMod(_, l, r any) any { } func funcOpAlt(_, l, r any) any { - if l == nil || l == false { + if isNull(l) { + return r + } else if lb, ok := toBoolean(l); ok && !lb { return r } return l diff --git a/preview_test.go b/preview_test.go index 5b6c878f..d1d81839 100644 --- a/preview_test.go +++ b/preview_test.go @@ -6,7 +6,7 @@ import ( "math/big" "testing" - "github.com/itchyny/gojq" + "github.com/wader/gojq" ) func TestPreview(t *testing.T) { diff --git a/type.go b/type.go index bb388e20..a8a6f29f 100644 --- a/type.go +++ b/type.go @@ -8,9 +8,10 @@ import ( // TypeOf returns the jq-flavored type name of v. // // This method is used by built-in type/0 function, and accepts only limited -// types (nil, bool, int, float64, *big.Int, string, []any, and map[string]any). +// types (nil, bool, int, float64, *big.Int, string, []any, +// and map[string]any). func TypeOf(v any) string { - switch v.(type) { + switch v := v.(type) { case nil: return "null" case bool: @@ -23,6 +24,8 @@ func TypeOf(v any) string { return "array" case map[string]any: return "object" + case JQValue: + return v.JQValueType() default: panic(fmt.Sprintf("invalid type: %[1]T (%[1]v)", v)) } diff --git a/type_test.go b/type_test.go index ef917a02..0ee14660 100644 --- a/type_test.go +++ b/type_test.go @@ -6,7 +6,7 @@ import ( "math/big" "testing" - "github.com/itchyny/gojq" + "github.com/wader/gojq" ) func TestTypeOf(t *testing.T) {