forked from mirror/go-json
Merge pull request #250 from goccy/feature/json-path
Support JSON Path for decoding
This commit is contained in:
commit
9b472af6fa
2
Makefile
2
Makefile
|
@ -22,7 +22,7 @@ cover-html: cover
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint: golangci-lint
|
lint: golangci-lint
|
||||||
golangci-lint run
|
$(BIN_DIR)/golangci-lint run
|
||||||
|
|
||||||
golangci-lint: | $(BIN_DIR)
|
golangci-lint: | $(BIN_DIR)
|
||||||
@{ \
|
@{ \
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package benchmark
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
gojson "github.com/goccy/go-json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Benchmark_Decode_SmallStruct_UnmarshalPath_GoJson(b *testing.B) {
|
||||||
|
path, err := gojson.CreatePath("$.st")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
var v int
|
||||||
|
if err := path.Unmarshal(SmallFixture, &v); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if v != 1 {
|
||||||
|
b.Fatal("failed to unmarshal path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
decode.go
28
decode.go
|
@ -83,6 +83,34 @@ func unmarshalContext(ctx context.Context, data []byte, v interface{}, optFuncs
|
||||||
return validateEndBuf(src, cursor)
|
return validateEndBuf(src, cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
pathDecoder = decoder.NewPathDecoder()
|
||||||
|
)
|
||||||
|
|
||||||
|
func extractFromPath(path *Path, data []byte, optFuncs ...DecodeOptionFunc) ([][]byte, error) {
|
||||||
|
src := make([]byte, len(data)+1) // append nul byte to the end
|
||||||
|
copy(src, data)
|
||||||
|
|
||||||
|
ctx := decoder.TakeRuntimeContext()
|
||||||
|
ctx.Buf = src
|
||||||
|
ctx.Option.Flags = 0
|
||||||
|
ctx.Option.Flags |= decoder.PathOption
|
||||||
|
ctx.Option.Path = path.path
|
||||||
|
for _, optFunc := range optFuncs {
|
||||||
|
optFunc(ctx.Option)
|
||||||
|
}
|
||||||
|
paths, cursor, err := pathDecoder.DecodePath(ctx, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
decoder.ReleaseRuntimeContext(ctx)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
decoder.ReleaseRuntimeContext(ctx)
|
||||||
|
if err := validateEndBuf(src, cursor); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return paths, nil
|
||||||
|
}
|
||||||
|
|
||||||
func unmarshalNoEscape(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error {
|
func unmarshalNoEscape(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error {
|
||||||
src := make([]byte, len(data)+1) // append nul byte to the end
|
src := make([]byte, len(data)+1) // append nul byte to the end
|
||||||
copy(src, data)
|
copy(src, data)
|
||||||
|
|
2
error.go
2
error.go
|
@ -37,3 +37,5 @@ type UnmarshalTypeError = errors.UnmarshalTypeError
|
||||||
type UnsupportedTypeError = errors.UnsupportedTypeError
|
type UnsupportedTypeError = errors.UnsupportedTypeError
|
||||||
|
|
||||||
type UnsupportedValueError = errors.UnsupportedValueError
|
type UnsupportedValueError = errors.UnsupportedValueError
|
||||||
|
|
||||||
|
type PathError = errors.PathError
|
||||||
|
|
|
@ -35,3 +35,7 @@ func (d *anonymousFieldDecoder) Decode(ctx *RuntimeContext, cursor, depth int64,
|
||||||
p = *(*unsafe.Pointer)(p)
|
p = *(*unsafe.Pointer)(p)
|
||||||
return d.dec.Decode(ctx, cursor, depth, unsafe.Pointer(uintptr(p)+d.offset))
|
return d.dec.Decode(ctx, cursor, depth, unsafe.Pointer(uintptr(p)+d.offset))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *anonymousFieldDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
return d.dec.DecodePath(ctx, cursor, depth)
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package decoder
|
package decoder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/goccy/go-json/internal/errors"
|
"github.com/goccy/go-json/internal/errors"
|
||||||
|
@ -167,3 +168,7 @@ func (d *arrayDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *arrayDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
return nil, 0, fmt.Errorf("json: array decoder does not support decode path")
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,438 @@
|
||||||
|
package decoder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
nilValue = reflect.ValueOf(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func AssignValue(src, dst reflect.Value) error {
|
||||||
|
if dst.Type().Kind() != reflect.Ptr {
|
||||||
|
return fmt.Errorf("invalid dst type. required pointer type: %T", dst.Type())
|
||||||
|
}
|
||||||
|
casted, err := castValue(dst.Elem().Type(), src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dst.Elem().Set(casted)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func castValue(t reflect.Type, v reflect.Value) (reflect.Value, error) {
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Int:
|
||||||
|
vv, err := castInt(v)
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(int(vv.Int())), nil
|
||||||
|
case reflect.Int8:
|
||||||
|
vv, err := castInt(v)
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(int8(vv.Int())), nil
|
||||||
|
case reflect.Int16:
|
||||||
|
vv, err := castInt(v)
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(int16(vv.Int())), nil
|
||||||
|
case reflect.Int32:
|
||||||
|
vv, err := castInt(v)
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(int32(vv.Int())), nil
|
||||||
|
case reflect.Int64:
|
||||||
|
return castInt(v)
|
||||||
|
case reflect.Uint:
|
||||||
|
vv, err := castUint(v)
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(uint(vv.Uint())), nil
|
||||||
|
case reflect.Uint8:
|
||||||
|
vv, err := castUint(v)
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(uint8(vv.Uint())), nil
|
||||||
|
case reflect.Uint16:
|
||||||
|
vv, err := castUint(v)
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(uint16(vv.Uint())), nil
|
||||||
|
case reflect.Uint32:
|
||||||
|
vv, err := castUint(v)
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(uint32(vv.Uint())), nil
|
||||||
|
case reflect.Uint64:
|
||||||
|
return castUint(v)
|
||||||
|
case reflect.Uintptr:
|
||||||
|
vv, err := castUint(v)
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(uintptr(vv.Uint())), nil
|
||||||
|
case reflect.String:
|
||||||
|
return castString(v)
|
||||||
|
case reflect.Bool:
|
||||||
|
return castBool(v)
|
||||||
|
case reflect.Float32:
|
||||||
|
vv, err := castFloat(v)
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(float32(vv.Float())), nil
|
||||||
|
case reflect.Float64:
|
||||||
|
return castFloat(v)
|
||||||
|
case reflect.Array:
|
||||||
|
return castArray(t, v)
|
||||||
|
case reflect.Slice:
|
||||||
|
return castSlice(t, v)
|
||||||
|
case reflect.Map:
|
||||||
|
return castMap(t, v)
|
||||||
|
case reflect.Struct:
|
||||||
|
return castStruct(t, v)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func castInt(v reflect.Value) (reflect.Value, error) {
|
||||||
|
switch v.Type().Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return v, nil
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return reflect.ValueOf(int64(v.Uint())), nil
|
||||||
|
case reflect.String:
|
||||||
|
i64, err := strconv.ParseInt(v.String(), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(i64), nil
|
||||||
|
case reflect.Bool:
|
||||||
|
if v.Bool() {
|
||||||
|
return reflect.ValueOf(int64(1)), nil
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(int64(0)), nil
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return reflect.ValueOf(int64(v.Float())), nil
|
||||||
|
case reflect.Array:
|
||||||
|
if v.Len() > 0 {
|
||||||
|
return castInt(v.Index(0))
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to int64 from empty array")
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.Len() > 0 {
|
||||||
|
return castInt(v.Index(0))
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to int64 from empty slice")
|
||||||
|
case reflect.Interface:
|
||||||
|
return castInt(reflect.ValueOf(v.Interface()))
|
||||||
|
case reflect.Map:
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to int64 from map")
|
||||||
|
case reflect.Struct:
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to int64 from struct")
|
||||||
|
case reflect.Ptr:
|
||||||
|
return castInt(v.Elem())
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to int64 from %s", v.Type().Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
func castUint(v reflect.Value) (reflect.Value, error) {
|
||||||
|
switch v.Type().Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return reflect.ValueOf(uint64(v.Int())), nil
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return v, nil
|
||||||
|
case reflect.String:
|
||||||
|
u64, err := strconv.ParseUint(v.String(), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(u64), nil
|
||||||
|
case reflect.Bool:
|
||||||
|
if v.Bool() {
|
||||||
|
return reflect.ValueOf(uint64(1)), nil
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(uint64(0)), nil
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return reflect.ValueOf(uint64(v.Float())), nil
|
||||||
|
case reflect.Array:
|
||||||
|
if v.Len() > 0 {
|
||||||
|
return castUint(v.Index(0))
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to uint64 from empty array")
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.Len() > 0 {
|
||||||
|
return castUint(v.Index(0))
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to uint64 from empty slice")
|
||||||
|
case reflect.Interface:
|
||||||
|
return castUint(reflect.ValueOf(v.Interface()))
|
||||||
|
case reflect.Map:
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to uint64 from map")
|
||||||
|
case reflect.Struct:
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to uint64 from struct")
|
||||||
|
case reflect.Ptr:
|
||||||
|
return castUint(v.Elem())
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to uint64 from %s", v.Type().Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
func castString(v reflect.Value) (reflect.Value, error) {
|
||||||
|
switch v.Type().Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return reflect.ValueOf(fmt.Sprint(v.Int())), nil
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return reflect.ValueOf(fmt.Sprint(v.Uint())), nil
|
||||||
|
case reflect.String:
|
||||||
|
return v, nil
|
||||||
|
case reflect.Bool:
|
||||||
|
if v.Bool() {
|
||||||
|
return reflect.ValueOf("true"), nil
|
||||||
|
}
|
||||||
|
return reflect.ValueOf("false"), nil
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return reflect.ValueOf(fmt.Sprint(v.Float())), nil
|
||||||
|
case reflect.Array:
|
||||||
|
if v.Len() > 0 {
|
||||||
|
return castString(v.Index(0))
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to string from empty array")
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.Len() > 0 {
|
||||||
|
return castString(v.Index(0))
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to string from empty slice")
|
||||||
|
case reflect.Interface:
|
||||||
|
return castString(reflect.ValueOf(v.Interface()))
|
||||||
|
case reflect.Map:
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to string from map")
|
||||||
|
case reflect.Struct:
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to string from struct")
|
||||||
|
case reflect.Ptr:
|
||||||
|
return castString(v.Elem())
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to string from %s", v.Type().Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
func castBool(v reflect.Value) (reflect.Value, error) {
|
||||||
|
switch v.Type().Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
switch v.Int() {
|
||||||
|
case 0:
|
||||||
|
return reflect.ValueOf(false), nil
|
||||||
|
case 1:
|
||||||
|
return reflect.ValueOf(true), nil
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to bool from %d", v.Int())
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
switch v.Uint() {
|
||||||
|
case 0:
|
||||||
|
return reflect.ValueOf(false), nil
|
||||||
|
case 1:
|
||||||
|
return reflect.ValueOf(true), nil
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to bool from %d", v.Uint())
|
||||||
|
case reflect.String:
|
||||||
|
b, err := strconv.ParseBool(v.String())
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(b), nil
|
||||||
|
case reflect.Bool:
|
||||||
|
return v, nil
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
switch v.Float() {
|
||||||
|
case 0:
|
||||||
|
return reflect.ValueOf(false), nil
|
||||||
|
case 1:
|
||||||
|
return reflect.ValueOf(true), nil
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to bool from %f", v.Float())
|
||||||
|
case reflect.Array:
|
||||||
|
if v.Len() > 0 {
|
||||||
|
return castBool(v.Index(0))
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to string from empty array")
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.Len() > 0 {
|
||||||
|
return castBool(v.Index(0))
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to string from empty slice")
|
||||||
|
case reflect.Interface:
|
||||||
|
return castBool(reflect.ValueOf(v.Interface()))
|
||||||
|
case reflect.Map:
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to string from map")
|
||||||
|
case reflect.Struct:
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to string from struct")
|
||||||
|
case reflect.Ptr:
|
||||||
|
return castBool(v.Elem())
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to bool from %s", v.Type().Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
func castFloat(v reflect.Value) (reflect.Value, error) {
|
||||||
|
switch v.Type().Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return reflect.ValueOf(float64(v.Int())), nil
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return reflect.ValueOf(float64(v.Uint())), nil
|
||||||
|
case reflect.String:
|
||||||
|
f64, err := strconv.ParseFloat(v.String(), 64)
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(f64), nil
|
||||||
|
case reflect.Bool:
|
||||||
|
if v.Bool() {
|
||||||
|
return reflect.ValueOf(float64(1)), nil
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(float64(0)), nil
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v, nil
|
||||||
|
case reflect.Array:
|
||||||
|
if v.Len() > 0 {
|
||||||
|
return castFloat(v.Index(0))
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to float64 from empty array")
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.Len() > 0 {
|
||||||
|
return castFloat(v.Index(0))
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to float64 from empty slice")
|
||||||
|
case reflect.Interface:
|
||||||
|
return castFloat(reflect.ValueOf(v.Interface()))
|
||||||
|
case reflect.Map:
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to float64 from map")
|
||||||
|
case reflect.Struct:
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to float64 from struct")
|
||||||
|
case reflect.Ptr:
|
||||||
|
return castFloat(v.Elem())
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to float64 from %s", v.Type().Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
func castArray(t reflect.Type, v reflect.Value) (reflect.Value, error) {
|
||||||
|
kind := v.Type().Kind()
|
||||||
|
if kind == reflect.Interface {
|
||||||
|
return castArray(t, reflect.ValueOf(v.Interface()))
|
||||||
|
}
|
||||||
|
if kind != reflect.Slice && kind != reflect.Array {
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to array from %s", kind)
|
||||||
|
}
|
||||||
|
if t.Elem() == v.Type().Elem() {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
if t.Len() != v.Len() {
|
||||||
|
return nilValue, fmt.Errorf("failed to cast [%d]array from slice of %d length", t.Len(), v.Len())
|
||||||
|
}
|
||||||
|
ret := reflect.New(t).Elem()
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
vv, err := castValue(t.Elem(), v.Index(i))
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
ret.Index(i).Set(vv)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func castSlice(t reflect.Type, v reflect.Value) (reflect.Value, error) {
|
||||||
|
kind := v.Type().Kind()
|
||||||
|
if kind == reflect.Interface {
|
||||||
|
return castSlice(t, reflect.ValueOf(v.Interface()))
|
||||||
|
}
|
||||||
|
if kind != reflect.Slice && kind != reflect.Array {
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to slice from %s", kind)
|
||||||
|
}
|
||||||
|
if t.Elem() == v.Type().Elem() {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
ret := reflect.MakeSlice(t, v.Len(), v.Len())
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
vv, err := castValue(t.Elem(), v.Index(i))
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
ret.Index(i).Set(vv)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func castMap(t reflect.Type, v reflect.Value) (reflect.Value, error) {
|
||||||
|
ret := reflect.MakeMap(t)
|
||||||
|
switch v.Type().Kind() {
|
||||||
|
case reflect.Map:
|
||||||
|
iter := v.MapRange()
|
||||||
|
for iter.Next() {
|
||||||
|
key, err := castValue(t.Key(), iter.Key())
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
value, err := castValue(t.Elem(), iter.Value())
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
ret.SetMapIndex(key, value)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
case reflect.Interface:
|
||||||
|
return castMap(t, reflect.ValueOf(v.Interface()))
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.Len() > 0 {
|
||||||
|
return castMap(t, v.Index(0))
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to map from empty slice")
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to map from %s", v.Type().Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
func castStruct(t reflect.Type, v reflect.Value) (reflect.Value, error) {
|
||||||
|
ret := reflect.New(t).Elem()
|
||||||
|
switch v.Type().Kind() {
|
||||||
|
case reflect.Map:
|
||||||
|
iter := v.MapRange()
|
||||||
|
for iter.Next() {
|
||||||
|
key := iter.Key()
|
||||||
|
k, err := castString(key)
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
fieldName := k.String()
|
||||||
|
field, ok := t.FieldByName(fieldName)
|
||||||
|
if ok {
|
||||||
|
value, err := castValue(field.Type, iter.Value())
|
||||||
|
if err != nil {
|
||||||
|
return nilValue, err
|
||||||
|
}
|
||||||
|
ret.FieldByName(fieldName).Set(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
case reflect.Struct:
|
||||||
|
for i := 0; i < v.Type().NumField(); i++ {
|
||||||
|
name := v.Type().Field(i).Name
|
||||||
|
ret.FieldByName(name).Set(v.FieldByName(name))
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
case reflect.Interface:
|
||||||
|
return castStruct(t, reflect.ValueOf(v.Interface()))
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.Len() > 0 {
|
||||||
|
return castStruct(t, v.Index(0))
|
||||||
|
}
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to struct from empty slice")
|
||||||
|
default:
|
||||||
|
return nilValue, fmt.Errorf("failed to cast to struct from %s", v.Type().Kind())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package decoder
|
package decoder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/goccy/go-json/internal/errors"
|
"github.com/goccy/go-json/internal/errors"
|
||||||
|
@ -76,3 +77,7 @@ func (d *boolDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.
|
||||||
}
|
}
|
||||||
return 0, errors.ErrUnexpectedEndOfJSON("bool", cursor)
|
return 0, errors.ErrUnexpectedEndOfJSON("bool", cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *boolDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
return nil, 0, fmt.Errorf("json: bool decoder does not support decode path")
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package decoder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/goccy/go-json/internal/errors"
|
"github.com/goccy/go-json/internal/errors"
|
||||||
|
@ -78,6 +79,10 @@ func (d *bytesDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe
|
||||||
return cursor, nil
|
return cursor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *bytesDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
return nil, 0, fmt.Errorf("json: []byte decoder does not support decode path")
|
||||||
|
}
|
||||||
|
|
||||||
func (d *bytesDecoder) decodeStreamBinary(s *Stream, depth int64, p unsafe.Pointer) ([]byte, error) {
|
func (d *bytesDecoder) decodeStreamBinary(s *Stream, depth int64, p unsafe.Pointer) ([]byte, error) {
|
||||||
c := s.skipWhiteSpace()
|
c := s.skipWhiteSpace()
|
||||||
if c == '[' {
|
if c == '[' {
|
||||||
|
|
|
@ -156,3 +156,15 @@ func (d *floatDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe
|
||||||
d.op(p, f64)
|
d.op(p, f64)
|
||||||
return cursor, nil
|
return cursor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *floatDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
buf := ctx.Buf
|
||||||
|
bytes, c, err := d.decodeByte(buf, cursor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if bytes == nil {
|
||||||
|
return [][]byte{nullbytes}, c, nil
|
||||||
|
}
|
||||||
|
return [][]byte{bytes}, c, nil
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package decoder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/goccy/go-json/internal/errors"
|
"github.com/goccy/go-json/internal/errors"
|
||||||
|
@ -139,3 +140,7 @@ func (d *funcDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.
|
||||||
}
|
}
|
||||||
return cursor, errors.ErrInvalidBeginningOfValue(buf[cursor], cursor)
|
return cursor, errors.ErrInvalidBeginningOfValue(buf[cursor], cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *funcDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
return nil, 0, fmt.Errorf("json: func decoder does not support decode path")
|
||||||
|
}
|
||||||
|
|
|
@ -240,3 +240,7 @@ func (d *intDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.P
|
||||||
d.op(p, i64)
|
d.op(p, i64)
|
||||||
return cursor, nil
|
return cursor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *intDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
return nil, 0, fmt.Errorf("json: int decoder does not support decode path")
|
||||||
|
}
|
||||||
|
|
|
@ -94,6 +94,7 @@ func (d *interfaceDecoder) numDecoder(s *Stream) Decoder {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
emptyInterfaceType = runtime.Type2RType(reflect.TypeOf((*interface{})(nil)).Elem())
|
emptyInterfaceType = runtime.Type2RType(reflect.TypeOf((*interface{})(nil)).Elem())
|
||||||
|
EmptyInterfaceType = emptyInterfaceType
|
||||||
interfaceMapType = runtime.Type2RType(
|
interfaceMapType = runtime.Type2RType(
|
||||||
reflect.TypeOf((*map[string]interface{})(nil)).Elem(),
|
reflect.TypeOf((*map[string]interface{})(nil)).Elem(),
|
||||||
)
|
)
|
||||||
|
@ -456,3 +457,72 @@ func (d *interfaceDecoder) decodeEmptyInterface(ctx *RuntimeContext, cursor, dep
|
||||||
}
|
}
|
||||||
return cursor, errors.ErrInvalidBeginningOfValue(buf[cursor], cursor)
|
return cursor, errors.ErrInvalidBeginningOfValue(buf[cursor], cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewPathDecoder() Decoder {
|
||||||
|
ifaceDecoder := &interfaceDecoder{
|
||||||
|
typ: emptyInterfaceType,
|
||||||
|
structName: "",
|
||||||
|
fieldName: "",
|
||||||
|
floatDecoder: newFloatDecoder("", "", func(p unsafe.Pointer, v float64) {
|
||||||
|
*(*interface{})(p) = v
|
||||||
|
}),
|
||||||
|
numberDecoder: newNumberDecoder("", "", func(p unsafe.Pointer, v json.Number) {
|
||||||
|
*(*interface{})(p) = v
|
||||||
|
}),
|
||||||
|
stringDecoder: newStringDecoder("", ""),
|
||||||
|
}
|
||||||
|
ifaceDecoder.sliceDecoder = newSliceDecoder(
|
||||||
|
ifaceDecoder,
|
||||||
|
emptyInterfaceType,
|
||||||
|
emptyInterfaceType.Size(),
|
||||||
|
"", "",
|
||||||
|
)
|
||||||
|
ifaceDecoder.mapDecoder = newMapDecoder(
|
||||||
|
interfaceMapType,
|
||||||
|
stringType,
|
||||||
|
ifaceDecoder.stringDecoder,
|
||||||
|
interfaceMapType.Elem(),
|
||||||
|
ifaceDecoder,
|
||||||
|
"", "",
|
||||||
|
)
|
||||||
|
return ifaceDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
truebytes = []byte("true")
|
||||||
|
falsebytes = []byte("false")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *interfaceDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
buf := ctx.Buf
|
||||||
|
cursor = skipWhiteSpace(buf, cursor)
|
||||||
|
switch buf[cursor] {
|
||||||
|
case '{':
|
||||||
|
return d.mapDecoder.DecodePath(ctx, cursor, depth)
|
||||||
|
case '[':
|
||||||
|
return d.sliceDecoder.DecodePath(ctx, cursor, depth)
|
||||||
|
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||||
|
return d.floatDecoder.DecodePath(ctx, cursor, depth)
|
||||||
|
case '"':
|
||||||
|
return d.stringDecoder.DecodePath(ctx, cursor, depth)
|
||||||
|
case 't':
|
||||||
|
if err := validateTrue(buf, cursor); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
cursor += 4
|
||||||
|
return [][]byte{truebytes}, cursor, nil
|
||||||
|
case 'f':
|
||||||
|
if err := validateFalse(buf, cursor); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
cursor += 5
|
||||||
|
return [][]byte{falsebytes}, cursor, nil
|
||||||
|
case 'n':
|
||||||
|
if err := validateNull(buf, cursor); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
cursor += 4
|
||||||
|
return [][]byte{nullbytes}, cursor, nil
|
||||||
|
}
|
||||||
|
return nil, cursor, errors.ErrInvalidBeginningOfValue(buf[cursor], cursor)
|
||||||
|
}
|
||||||
|
|
|
@ -43,3 +43,13 @@ func (d *invalidDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsa
|
||||||
Field: d.fieldName,
|
Field: d.fieldName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *invalidDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
return nil, 0, &errors.UnmarshalTypeError{
|
||||||
|
Value: "object",
|
||||||
|
Type: runtime.RType2Type(d.typ),
|
||||||
|
Offset: cursor,
|
||||||
|
Struct: d.structName,
|
||||||
|
Field: d.fieldName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -185,3 +185,95 @@ func (d *mapDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.P
|
||||||
cursor++
|
cursor++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *mapDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
buf := ctx.Buf
|
||||||
|
depth++
|
||||||
|
if depth > maxDecodeNestingDepth {
|
||||||
|
return nil, 0, errors.ErrExceededMaxDepth(buf[cursor], cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = skipWhiteSpace(buf, cursor)
|
||||||
|
buflen := int64(len(buf))
|
||||||
|
if buflen < 2 {
|
||||||
|
return nil, 0, errors.ErrExpected("{} for map", cursor)
|
||||||
|
}
|
||||||
|
switch buf[cursor] {
|
||||||
|
case 'n':
|
||||||
|
if err := validateNull(buf, cursor); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
cursor += 4
|
||||||
|
return [][]byte{nullbytes}, cursor, nil
|
||||||
|
case '{':
|
||||||
|
default:
|
||||||
|
return nil, 0, errors.ErrExpected("{ character for map value", cursor)
|
||||||
|
}
|
||||||
|
cursor++
|
||||||
|
cursor = skipWhiteSpace(buf, cursor)
|
||||||
|
if buf[cursor] == '}' {
|
||||||
|
cursor++
|
||||||
|
return nil, cursor, nil
|
||||||
|
}
|
||||||
|
keyDecoder, ok := d.keyDecoder.(*stringDecoder)
|
||||||
|
if !ok {
|
||||||
|
return nil, 0, &errors.UnmarshalTypeError{
|
||||||
|
Value: "string",
|
||||||
|
Type: reflect.TypeOf(""),
|
||||||
|
Offset: cursor,
|
||||||
|
Struct: d.structName,
|
||||||
|
Field: d.fieldName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret := [][]byte{}
|
||||||
|
for {
|
||||||
|
key, keyCursor, err := keyDecoder.decodeByte(buf, cursor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
cursor = skipWhiteSpace(buf, keyCursor)
|
||||||
|
if buf[cursor] != ':' {
|
||||||
|
return nil, 0, errors.ErrExpected("colon after object key", cursor)
|
||||||
|
}
|
||||||
|
cursor++
|
||||||
|
child, found, err := ctx.Option.Path.Field(string(key))
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
if child != nil {
|
||||||
|
oldPath := ctx.Option.Path.node
|
||||||
|
ctx.Option.Path.node = child
|
||||||
|
paths, c, err := d.valueDecoder.DecodePath(ctx, cursor, depth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
ctx.Option.Path.node = oldPath
|
||||||
|
ret = append(ret, paths...)
|
||||||
|
cursor = c
|
||||||
|
} else {
|
||||||
|
start := cursor
|
||||||
|
end, err := skipValue(buf, cursor, depth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
ret = append(ret, buf[start:end])
|
||||||
|
cursor = end
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c, err := skipValue(buf, cursor, depth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
cursor = skipWhiteSpace(buf, c)
|
||||||
|
}
|
||||||
|
if buf[cursor] == '}' {
|
||||||
|
cursor++
|
||||||
|
return ret, cursor, nil
|
||||||
|
}
|
||||||
|
if buf[cursor] != ',' {
|
||||||
|
return nil, 0, errors.ErrExpected("comma after object value", cursor)
|
||||||
|
}
|
||||||
|
cursor++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -51,6 +51,17 @@ func (d *numberDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsaf
|
||||||
return cursor, nil
|
return cursor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *numberDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
bytes, c, err := d.decodeByte(ctx.Buf, cursor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if bytes == nil {
|
||||||
|
return [][]byte{nullbytes}, c, nil
|
||||||
|
}
|
||||||
|
return [][]byte{bytes}, c, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *numberDecoder) decodeStreamByte(s *Stream) ([]byte, error) {
|
func (d *numberDecoder) decodeStreamByte(s *Stream) ([]byte, error) {
|
||||||
start := s.cursor
|
start := s.cursor
|
||||||
for {
|
for {
|
||||||
|
|
|
@ -7,9 +7,11 @@ type OptionFlags uint8
|
||||||
const (
|
const (
|
||||||
FirstWinOption OptionFlags = 1 << iota
|
FirstWinOption OptionFlags = 1 << iota
|
||||||
ContextOption
|
ContextOption
|
||||||
|
PathOption
|
||||||
)
|
)
|
||||||
|
|
||||||
type Option struct {
|
type Option struct {
|
||||||
Flags OptionFlags
|
Flags OptionFlags
|
||||||
Context context.Context
|
Context context.Context
|
||||||
|
Path *Path
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,653 @@
|
||||||
|
package decoder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/goccy/go-json/internal/errors"
|
||||||
|
"github.com/goccy/go-json/internal/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PathString string
|
||||||
|
|
||||||
|
func (s PathString) Build() (*Path, error) {
|
||||||
|
builder := new(PathBuilder)
|
||||||
|
return builder.Build([]rune(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
type PathBuilder struct {
|
||||||
|
root PathNode
|
||||||
|
node PathNode
|
||||||
|
singleQuotePathSelector bool
|
||||||
|
doubleQuotePathSelector bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PathBuilder) Build(buf []rune) (*Path, error) {
|
||||||
|
node, err := b.build(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Path{
|
||||||
|
node: node,
|
||||||
|
SingleQuotePathSelector: b.singleQuotePathSelector,
|
||||||
|
DoubleQuotePathSelector: b.doubleQuotePathSelector,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PathBuilder) build(buf []rune) (PathNode, error) {
|
||||||
|
if len(buf) == 0 {
|
||||||
|
return nil, errors.ErrEmptyPath()
|
||||||
|
}
|
||||||
|
if buf[0] != '$' {
|
||||||
|
return nil, errors.ErrInvalidPath("JSON Path must start with a $ character")
|
||||||
|
}
|
||||||
|
if len(buf) == 1 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
buf = buf[1:]
|
||||||
|
offset, err := b.buildNext(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(buf) > offset {
|
||||||
|
return nil, errors.ErrInvalidPath("remain invalid path %q", buf[offset:])
|
||||||
|
}
|
||||||
|
return b.root, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PathBuilder) buildNextCharIfExists(buf []rune, cursor int) (int, error) {
|
||||||
|
if len(buf) > cursor+1 {
|
||||||
|
offset, err := b.buildNext(buf[cursor+1:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cursor + 1 + offset, nil
|
||||||
|
}
|
||||||
|
return cursor, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PathBuilder) buildNext(buf []rune) (int, error) {
|
||||||
|
switch buf[0] {
|
||||||
|
case '.':
|
||||||
|
if len(buf) == 1 {
|
||||||
|
return 0, errors.ErrInvalidPath("JSON Path ends with dot character")
|
||||||
|
}
|
||||||
|
offset, err := b.buildSelector(buf[1:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return offset + 1, nil
|
||||||
|
case '[':
|
||||||
|
if len(buf) == 1 {
|
||||||
|
return 0, errors.ErrInvalidPath("JSON Path ends with left bracket character")
|
||||||
|
}
|
||||||
|
offset, err := b.buildIndex(buf[1:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return offset + 1, nil
|
||||||
|
default:
|
||||||
|
return 0, errors.ErrInvalidPath("expect dot or left bracket character. but found %c character", buf[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PathBuilder) buildSelector(buf []rune) (int, error) {
|
||||||
|
switch buf[0] {
|
||||||
|
case '.':
|
||||||
|
if len(buf) == 1 {
|
||||||
|
return 0, errors.ErrInvalidPath("JSON Path ends with double dot character")
|
||||||
|
}
|
||||||
|
offset, err := b.buildPathRecursive(buf[1:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return 1 + offset, nil
|
||||||
|
case '[', ']', '$', '*':
|
||||||
|
return 0, errors.ErrInvalidPath("found invalid path character %c after dot", buf[0])
|
||||||
|
}
|
||||||
|
for cursor := 0; cursor < len(buf); cursor++ {
|
||||||
|
switch buf[cursor] {
|
||||||
|
case '$', '*', ']':
|
||||||
|
return 0, errors.ErrInvalidPath("found %c character in field selector context", buf[cursor])
|
||||||
|
case '.':
|
||||||
|
if cursor+1 >= len(buf) {
|
||||||
|
return 0, errors.ErrInvalidPath("JSON Path ends with dot character")
|
||||||
|
}
|
||||||
|
selector := buf[:cursor]
|
||||||
|
b.addSelectorNode(string(selector))
|
||||||
|
offset, err := b.buildSelector(buf[cursor+1:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cursor + 1 + offset, nil
|
||||||
|
case '[':
|
||||||
|
if cursor+1 >= len(buf) {
|
||||||
|
return 0, errors.ErrInvalidPath("JSON Path ends with left bracket character")
|
||||||
|
}
|
||||||
|
selector := buf[:cursor]
|
||||||
|
b.addSelectorNode(string(selector))
|
||||||
|
offset, err := b.buildIndex(buf[cursor+1:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cursor + 1 + offset, nil
|
||||||
|
case '"':
|
||||||
|
if cursor+1 >= len(buf) {
|
||||||
|
return 0, errors.ErrInvalidPath("JSON Path ends with double quote character")
|
||||||
|
}
|
||||||
|
offset, err := b.buildQuoteSelector(buf[cursor+1:], DoubleQuotePathSelector)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cursor + 1 + offset, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.addSelectorNode(string(buf))
|
||||||
|
return len(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PathBuilder) buildQuoteSelector(buf []rune, sel QuotePathSelector) (int, error) {
|
||||||
|
switch buf[0] {
|
||||||
|
case '[', ']', '$', '.', '*', '\'', '"':
|
||||||
|
return 0, errors.ErrInvalidPath("found invalid path character %c after quote", buf[0])
|
||||||
|
}
|
||||||
|
for cursor := 0; cursor < len(buf); cursor++ {
|
||||||
|
switch buf[cursor] {
|
||||||
|
case '\'':
|
||||||
|
if sel != SingleQuotePathSelector {
|
||||||
|
return 0, errors.ErrInvalidPath("found double quote character in field selector with single quote context")
|
||||||
|
}
|
||||||
|
if len(buf) <= cursor+1 {
|
||||||
|
return 0, errors.ErrInvalidPath("JSON Path ends with single quote character in field selector context")
|
||||||
|
}
|
||||||
|
if buf[cursor+1] != ']' {
|
||||||
|
return 0, errors.ErrInvalidPath("expect right bracket for field selector with single quote but found %c", buf[cursor+1])
|
||||||
|
}
|
||||||
|
selector := buf[:cursor]
|
||||||
|
b.addSelectorNode(string(selector))
|
||||||
|
return b.buildNextCharIfExists(buf, cursor+1)
|
||||||
|
case '"':
|
||||||
|
if sel != DoubleQuotePathSelector {
|
||||||
|
return 0, errors.ErrInvalidPath("found single quote character in field selector with double quote context")
|
||||||
|
}
|
||||||
|
selector := buf[:cursor]
|
||||||
|
b.addSelectorNode(string(selector))
|
||||||
|
return b.buildNextCharIfExists(buf, cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, errors.ErrInvalidPath("couldn't find quote character in selector quote path context")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PathBuilder) buildPathRecursive(buf []rune) (int, error) {
|
||||||
|
switch buf[0] {
|
||||||
|
case '.', '[', ']', '$', '*':
|
||||||
|
return 0, errors.ErrInvalidPath("found invalid path character %c after double dot", buf[0])
|
||||||
|
}
|
||||||
|
for cursor := 0; cursor < len(buf); cursor++ {
|
||||||
|
switch buf[cursor] {
|
||||||
|
case '$', '*', ']':
|
||||||
|
return 0, errors.ErrInvalidPath("found %c character in field selector context", buf[cursor])
|
||||||
|
case '.':
|
||||||
|
if cursor+1 >= len(buf) {
|
||||||
|
return 0, errors.ErrInvalidPath("JSON Path ends with dot character")
|
||||||
|
}
|
||||||
|
selector := buf[:cursor]
|
||||||
|
b.addRecursiveNode(string(selector))
|
||||||
|
offset, err := b.buildSelector(buf[cursor+1:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cursor + 1 + offset, nil
|
||||||
|
case '[':
|
||||||
|
if cursor+1 >= len(buf) {
|
||||||
|
return 0, errors.ErrInvalidPath("JSON Path ends with left bracket character")
|
||||||
|
}
|
||||||
|
selector := buf[:cursor]
|
||||||
|
b.addRecursiveNode(string(selector))
|
||||||
|
offset, err := b.buildIndex(buf[cursor+1:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return cursor + 1 + offset, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.addRecursiveNode(string(buf))
|
||||||
|
return len(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PathBuilder) buildIndex(buf []rune) (int, error) {
|
||||||
|
switch buf[0] {
|
||||||
|
case '.', '[', ']', '$':
|
||||||
|
return 0, errors.ErrInvalidPath("found invalid path character %c after left bracket", buf[0])
|
||||||
|
case '\'':
|
||||||
|
if len(buf) == 1 {
|
||||||
|
return 0, errors.ErrInvalidPath("JSON Path ends with single quote character")
|
||||||
|
}
|
||||||
|
offset, err := b.buildQuoteSelector(buf[1:], SingleQuotePathSelector)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return 1 + offset, nil
|
||||||
|
case '*':
|
||||||
|
if len(buf) == 1 {
|
||||||
|
return 0, errors.ErrInvalidPath("JSON Path ends with star character")
|
||||||
|
}
|
||||||
|
if buf[1] != ']' {
|
||||||
|
return 0, errors.ErrInvalidPath("expect right bracket character for index all path but found %c character", buf[1])
|
||||||
|
}
|
||||||
|
b.addIndexAllNode()
|
||||||
|
offset := len("*]")
|
||||||
|
if len(buf) > 2 {
|
||||||
|
buildOffset, err := b.buildNext(buf[2:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return offset + buildOffset, nil
|
||||||
|
}
|
||||||
|
return offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for cursor := 0; cursor < len(buf); cursor++ {
|
||||||
|
switch buf[cursor] {
|
||||||
|
case ']':
|
||||||
|
index, err := strconv.ParseInt(string(buf[:cursor]), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.ErrInvalidPath("%q is unexpected index path", buf[:cursor])
|
||||||
|
}
|
||||||
|
b.addIndexNode(int(index))
|
||||||
|
return b.buildNextCharIfExists(buf, cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, errors.ErrInvalidPath("couldn't find right bracket character in index path context")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PathBuilder) addIndexAllNode() {
|
||||||
|
node := newPathIndexAllNode()
|
||||||
|
if b.root == nil {
|
||||||
|
b.root = node
|
||||||
|
b.node = node
|
||||||
|
} else {
|
||||||
|
b.node = b.node.chain(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PathBuilder) addRecursiveNode(selector string) {
|
||||||
|
node := newPathRecursiveNode(selector)
|
||||||
|
if b.root == nil {
|
||||||
|
b.root = node
|
||||||
|
b.node = node
|
||||||
|
} else {
|
||||||
|
b.node = b.node.chain(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PathBuilder) addSelectorNode(name string) {
|
||||||
|
node := newPathSelectorNode(name)
|
||||||
|
if b.root == nil {
|
||||||
|
b.root = node
|
||||||
|
b.node = node
|
||||||
|
} else {
|
||||||
|
b.node = b.node.chain(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PathBuilder) addIndexNode(idx int) {
|
||||||
|
node := newPathIndexNode(idx)
|
||||||
|
if b.root == nil {
|
||||||
|
b.root = node
|
||||||
|
b.node = node
|
||||||
|
} else {
|
||||||
|
b.node = b.node.chain(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type QuotePathSelector int
|
||||||
|
|
||||||
|
const (
|
||||||
|
SingleQuotePathSelector QuotePathSelector = 1
|
||||||
|
DoubleQuotePathSelector QuotePathSelector = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type Path struct {
|
||||||
|
node PathNode
|
||||||
|
SingleQuotePathSelector bool
|
||||||
|
DoubleQuotePathSelector bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) Field(sel string) (PathNode, bool, error) {
|
||||||
|
return p.node.Field(sel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) Get(src, dst reflect.Value) error {
|
||||||
|
return p.node.Get(src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PathNode interface {
|
||||||
|
fmt.Stringer
|
||||||
|
Index(idx int) (PathNode, bool, error)
|
||||||
|
Field(fieldName string) (PathNode, bool, error)
|
||||||
|
Get(src, dst reflect.Value) error
|
||||||
|
chain(PathNode) PathNode
|
||||||
|
target() bool
|
||||||
|
single() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type BasePathNode struct {
|
||||||
|
child PathNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *BasePathNode) chain(node PathNode) PathNode {
|
||||||
|
n.child = node
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *BasePathNode) target() bool {
|
||||||
|
return n.child == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *BasePathNode) single() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type PathSelectorNode struct {
|
||||||
|
*BasePathNode
|
||||||
|
selector string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPathSelectorNode(selector string) *PathSelectorNode {
|
||||||
|
return &PathSelectorNode{
|
||||||
|
BasePathNode: &BasePathNode{},
|
||||||
|
selector: selector,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *PathSelectorNode) Index(idx int) (PathNode, bool, error) {
|
||||||
|
return nil, false, &errors.PathError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *PathSelectorNode) Field(fieldName string) (PathNode, bool, error) {
|
||||||
|
if n.selector == fieldName {
|
||||||
|
return n.child, true, nil
|
||||||
|
}
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *PathSelectorNode) Get(src, dst reflect.Value) error {
|
||||||
|
switch src.Type().Kind() {
|
||||||
|
case reflect.Map:
|
||||||
|
iter := src.MapRange()
|
||||||
|
for iter.Next() {
|
||||||
|
key, ok := iter.Key().Interface().(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid map key type %T", src.Type().Key())
|
||||||
|
}
|
||||||
|
child, found, err := n.Field(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
if child != nil {
|
||||||
|
return child.Get(iter.Value(), dst)
|
||||||
|
}
|
||||||
|
return AssignValue(iter.Value(), dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
typ := src.Type()
|
||||||
|
for i := 0; i < typ.Len(); i++ {
|
||||||
|
tag := runtime.StructTagFromField(typ.Field(i))
|
||||||
|
child, found, err := n.Field(tag.Key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
if child != nil {
|
||||||
|
return child.Get(src.Field(i), dst)
|
||||||
|
}
|
||||||
|
return AssignValue(src.Field(i), dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Ptr:
|
||||||
|
return n.Get(src.Elem(), dst)
|
||||||
|
case reflect.Interface:
|
||||||
|
return n.Get(reflect.ValueOf(src.Interface()), dst)
|
||||||
|
case reflect.Float64, reflect.String, reflect.Bool:
|
||||||
|
return AssignValue(src, dst)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to get %s value from %s", n.selector, src.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *PathSelectorNode) String() string {
|
||||||
|
s := fmt.Sprintf(".%s", n.selector)
|
||||||
|
if n.child != nil {
|
||||||
|
s += n.child.String()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type PathIndexNode struct {
|
||||||
|
*BasePathNode
|
||||||
|
selector int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPathIndexNode(selector int) *PathIndexNode {
|
||||||
|
return &PathIndexNode{
|
||||||
|
BasePathNode: &BasePathNode{},
|
||||||
|
selector: selector,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *PathIndexNode) Index(idx int) (PathNode, bool, error) {
|
||||||
|
if n.selector == idx {
|
||||||
|
return n.child, true, nil
|
||||||
|
}
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *PathIndexNode) Field(fieldName string) (PathNode, bool, error) {
|
||||||
|
return nil, false, &errors.PathError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *PathIndexNode) Get(src, dst reflect.Value) error {
|
||||||
|
switch src.Type().Kind() {
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
if src.Len() > n.selector {
|
||||||
|
if n.child != nil {
|
||||||
|
return n.child.Get(src.Index(n.selector), dst)
|
||||||
|
}
|
||||||
|
return AssignValue(src.Index(n.selector), dst)
|
||||||
|
}
|
||||||
|
case reflect.Ptr:
|
||||||
|
return n.Get(src.Elem(), dst)
|
||||||
|
case reflect.Interface:
|
||||||
|
return n.Get(reflect.ValueOf(src.Interface()), dst)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to get [%d] value from %s", n.selector, src.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *PathIndexNode) String() string {
|
||||||
|
s := fmt.Sprintf("[%d]", n.selector)
|
||||||
|
if n.child != nil {
|
||||||
|
s += n.child.String()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type PathIndexAllNode struct {
|
||||||
|
*BasePathNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPathIndexAllNode() *PathIndexAllNode {
|
||||||
|
return &PathIndexAllNode{
|
||||||
|
BasePathNode: &BasePathNode{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *PathIndexAllNode) Index(idx int) (PathNode, bool, error) {
|
||||||
|
return n.child, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *PathIndexAllNode) Field(fieldName string) (PathNode, bool, error) {
|
||||||
|
return nil, false, &errors.PathError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *PathIndexAllNode) Get(src, dst reflect.Value) error {
|
||||||
|
switch src.Type().Kind() {
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
var arr []interface{}
|
||||||
|
for i := 0; i < src.Len(); i++ {
|
||||||
|
var v interface{}
|
||||||
|
rv := reflect.ValueOf(&v)
|
||||||
|
if n.child != nil {
|
||||||
|
if err := n.child.Get(src.Index(i), rv); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := AssignValue(src.Index(i), rv); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arr = append(arr, v)
|
||||||
|
}
|
||||||
|
if err := AssignValue(reflect.ValueOf(arr), dst); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case reflect.Ptr:
|
||||||
|
return n.Get(src.Elem(), dst)
|
||||||
|
case reflect.Interface:
|
||||||
|
return n.Get(reflect.ValueOf(src.Interface()), dst)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to get all value from %s", src.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *PathIndexAllNode) String() string {
|
||||||
|
s := "[*]"
|
||||||
|
if n.child != nil {
|
||||||
|
s += n.child.String()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type PathRecursiveNode struct {
|
||||||
|
*BasePathNode
|
||||||
|
selector string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPathRecursiveNode(selector string) *PathRecursiveNode {
|
||||||
|
node := newPathSelectorNode(selector)
|
||||||
|
return &PathRecursiveNode{
|
||||||
|
BasePathNode: &BasePathNode{
|
||||||
|
child: node,
|
||||||
|
},
|
||||||
|
selector: selector,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *PathRecursiveNode) Field(fieldName string) (PathNode, bool, error) {
|
||||||
|
if n.selector == fieldName {
|
||||||
|
return n.child, true, nil
|
||||||
|
}
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *PathRecursiveNode) Index(_ int) (PathNode, bool, error) {
|
||||||
|
return n, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func valueToSliceValue(v interface{}) []interface{} {
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
ret := []interface{}{}
|
||||||
|
if rv.Type().Kind() == reflect.Slice || rv.Type().Kind() == reflect.Array {
|
||||||
|
for i := 0; i < rv.Len(); i++ {
|
||||||
|
ret = append(ret, rv.Index(i).Interface())
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
return []interface{}{v}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *PathRecursiveNode) Get(src, dst reflect.Value) error {
|
||||||
|
if n.child == nil {
|
||||||
|
return fmt.Errorf("failed to get by recursive path ..%s", n.selector)
|
||||||
|
}
|
||||||
|
var arr []interface{}
|
||||||
|
switch src.Type().Kind() {
|
||||||
|
case reflect.Map:
|
||||||
|
iter := src.MapRange()
|
||||||
|
for iter.Next() {
|
||||||
|
key, ok := iter.Key().Interface().(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid map key type %T", src.Type().Key())
|
||||||
|
}
|
||||||
|
child, found, err := n.Field(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
var v interface{}
|
||||||
|
rv := reflect.ValueOf(&v)
|
||||||
|
_ = child.Get(iter.Value(), rv)
|
||||||
|
arr = append(arr, valueToSliceValue(v)...)
|
||||||
|
} else {
|
||||||
|
var v interface{}
|
||||||
|
rv := reflect.ValueOf(&v)
|
||||||
|
_ = n.Get(iter.Value(), rv)
|
||||||
|
if v != nil {
|
||||||
|
arr = append(arr, valueToSliceValue(v)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = AssignValue(reflect.ValueOf(arr), dst)
|
||||||
|
return nil
|
||||||
|
case reflect.Struct:
|
||||||
|
typ := src.Type()
|
||||||
|
for i := 0; i < typ.Len(); i++ {
|
||||||
|
tag := runtime.StructTagFromField(typ.Field(i))
|
||||||
|
child, found, err := n.Field(tag.Key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
var v interface{}
|
||||||
|
rv := reflect.ValueOf(&v)
|
||||||
|
_ = child.Get(src.Field(i), rv)
|
||||||
|
arr = append(arr, valueToSliceValue(v)...)
|
||||||
|
} else {
|
||||||
|
var v interface{}
|
||||||
|
rv := reflect.ValueOf(&v)
|
||||||
|
_ = n.Get(src.Field(i), rv)
|
||||||
|
if v != nil {
|
||||||
|
arr = append(arr, valueToSliceValue(v)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = AssignValue(reflect.ValueOf(arr), dst)
|
||||||
|
return nil
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
for i := 0; i < src.Len(); i++ {
|
||||||
|
var v interface{}
|
||||||
|
rv := reflect.ValueOf(&v)
|
||||||
|
_ = n.Get(src.Index(i), rv)
|
||||||
|
if v != nil {
|
||||||
|
arr = append(arr, valueToSliceValue(v)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = AssignValue(reflect.ValueOf(arr), dst)
|
||||||
|
return nil
|
||||||
|
case reflect.Ptr:
|
||||||
|
return n.Get(src.Elem(), dst)
|
||||||
|
case reflect.Interface:
|
||||||
|
return n.Get(reflect.ValueOf(src.Interface()), dst)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to get %s value from %s", n.selector, src.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *PathRecursiveNode) String() string {
|
||||||
|
s := fmt.Sprintf("..%s", n.selector)
|
||||||
|
if n.child != nil {
|
||||||
|
s += n.child.String()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package decoder
|
package decoder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/goccy/go-json/internal/runtime"
|
"github.com/goccy/go-json/internal/runtime"
|
||||||
|
@ -34,6 +35,10 @@ func (d *ptrDecoder) contentDecoder() Decoder {
|
||||||
//go:linkname unsafe_New reflect.unsafe_New
|
//go:linkname unsafe_New reflect.unsafe_New
|
||||||
func unsafe_New(*runtime.Type) unsafe.Pointer
|
func unsafe_New(*runtime.Type) unsafe.Pointer
|
||||||
|
|
||||||
|
func UnsafeNew(t *runtime.Type) unsafe.Pointer {
|
||||||
|
return unsafe_New(t)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *ptrDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error {
|
func (d *ptrDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error {
|
||||||
if s.skipWhiteSpace() == nul {
|
if s.skipWhiteSpace() == nul {
|
||||||
s.read()
|
s.read()
|
||||||
|
@ -85,3 +90,7 @@ func (d *ptrDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.P
|
||||||
cursor = c
|
cursor = c
|
||||||
return cursor, nil
|
return cursor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *ptrDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
return nil, 0, fmt.Errorf("json: ptr decoder does not support decode path")
|
||||||
|
}
|
||||||
|
|
|
@ -299,3 +299,80 @@ func (d *sliceDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *sliceDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
buf := ctx.Buf
|
||||||
|
depth++
|
||||||
|
if depth > maxDecodeNestingDepth {
|
||||||
|
return nil, 0, errors.ErrExceededMaxDepth(buf[cursor], cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := [][]byte{}
|
||||||
|
for {
|
||||||
|
switch buf[cursor] {
|
||||||
|
case ' ', '\n', '\t', '\r':
|
||||||
|
cursor++
|
||||||
|
continue
|
||||||
|
case 'n':
|
||||||
|
if err := validateNull(buf, cursor); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
cursor += 4
|
||||||
|
return [][]byte{nullbytes}, cursor, nil
|
||||||
|
case '[':
|
||||||
|
cursor++
|
||||||
|
cursor = skipWhiteSpace(buf, cursor)
|
||||||
|
if buf[cursor] == ']' {
|
||||||
|
cursor++
|
||||||
|
return ret, cursor, nil
|
||||||
|
}
|
||||||
|
idx := 0
|
||||||
|
for {
|
||||||
|
child, found, err := ctx.Option.Path.node.Index(idx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
if child != nil {
|
||||||
|
oldPath := ctx.Option.Path.node
|
||||||
|
ctx.Option.Path.node = child
|
||||||
|
paths, c, err := d.valueDecoder.DecodePath(ctx, cursor, depth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
ctx.Option.Path.node = oldPath
|
||||||
|
ret = append(ret, paths...)
|
||||||
|
cursor = c
|
||||||
|
} else {
|
||||||
|
start := cursor
|
||||||
|
end, err := skipValue(buf, cursor, depth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
ret = append(ret, buf[start:end])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c, err := skipValue(buf, cursor, depth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
cursor = skipWhiteSpace(buf, c)
|
||||||
|
}
|
||||||
|
switch buf[cursor] {
|
||||||
|
case ']':
|
||||||
|
cursor++
|
||||||
|
return ret, cursor, nil
|
||||||
|
case ',':
|
||||||
|
idx++
|
||||||
|
default:
|
||||||
|
return nil, 0, errors.ErrInvalidCharacter(buf[cursor], "slice", cursor)
|
||||||
|
}
|
||||||
|
cursor++
|
||||||
|
}
|
||||||
|
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||||
|
return nil, 0, d.errNumber(cursor)
|
||||||
|
default:
|
||||||
|
return nil, 0, errors.ErrUnexpectedEndOfJSON("slice", cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -60,6 +60,17 @@ func (d *stringDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsaf
|
||||||
return cursor, nil
|
return cursor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *stringDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
bytes, c, err := d.decodeByte(ctx.Buf, cursor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if bytes == nil {
|
||||||
|
return [][]byte{nullbytes}, c, nil
|
||||||
|
}
|
||||||
|
return [][]byte{bytes}, c, nil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
hexToInt = [256]int{
|
hexToInt = [256]int{
|
||||||
'0': 0,
|
'0': 0,
|
||||||
|
|
|
@ -817,3 +817,7 @@ func (d *structDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsaf
|
||||||
cursor++
|
cursor++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *structDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
return nil, 0, fmt.Errorf("json: struct decoder does not support decode path")
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
type Decoder interface {
|
type Decoder interface {
|
||||||
Decode(*RuntimeContext, int64, int64, unsafe.Pointer) (int64, error)
|
Decode(*RuntimeContext, int64, int64, unsafe.Pointer) (int64, error)
|
||||||
|
DecodePath(*RuntimeContext, int64, int64) ([][]byte, int64, error)
|
||||||
DecodeStream(*Stream, int64, unsafe.Pointer) error
|
DecodeStream(*Stream, int64, unsafe.Pointer) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -188,3 +188,7 @@ func (d *uintDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.
|
||||||
d.op(p, u64)
|
d.op(p, u64)
|
||||||
return cursor, nil
|
return cursor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *uintDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
return nil, 0, fmt.Errorf("json: uint decoder does not support decode path")
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package decoder
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/goccy/go-json/internal/errors"
|
"github.com/goccy/go-json/internal/errors"
|
||||||
|
@ -97,3 +98,7 @@ func (d *unmarshalJSONDecoder) Decode(ctx *RuntimeContext, cursor, depth int64,
|
||||||
}
|
}
|
||||||
return end, nil
|
return end, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *unmarshalJSONDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
return nil, 0, fmt.Errorf("json: unmarshal json decoder does not support decode path")
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package decoder
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding"
|
"encoding"
|
||||||
|
"fmt"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf16"
|
"unicode/utf16"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
@ -142,6 +143,10 @@ func (d *unmarshalTextDecoder) Decode(ctx *RuntimeContext, cursor, depth int64,
|
||||||
return end, nil
|
return end, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *unmarshalTextDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
return nil, 0, fmt.Errorf("json: unmarshal text decoder does not support decode path")
|
||||||
|
}
|
||||||
|
|
||||||
func unquoteBytes(s []byte) (t []byte, ok bool) {
|
func unquoteBytes(s []byte) (t []byte, ok bool) {
|
||||||
length := len(s)
|
length := len(s)
|
||||||
if length < 2 || s[0] != '"' || s[length-1] != '"' {
|
if length < 2 || s[0] != '"' || s[length-1] != '"' {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package decoder
|
package decoder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
@ -66,3 +67,7 @@ func (d *wrappedStringDecoder) Decode(ctx *RuntimeContext, cursor, depth int64,
|
||||||
ctx.Buf = oldBuf
|
ctx.Buf = oldBuf
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *wrappedStringDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
||||||
|
return nil, 0, fmt.Errorf("json: wrapped string decoder does not support decode path")
|
||||||
|
}
|
||||||
|
|
|
@ -162,3 +162,22 @@ func ErrInvalidBeginningOfValue(c byte, cursor int64) *SyntaxError {
|
||||||
Offset: cursor,
|
Offset: cursor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PathError struct {
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PathError) Error() string {
|
||||||
|
return fmt.Sprintf("json: invalid path format: %s", e.msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrInvalidPath(msg string, args ...interface{}) *PathError {
|
||||||
|
if len(args) != 0 {
|
||||||
|
return &PathError{msg: fmt.Sprintf(msg, args...)}
|
||||||
|
}
|
||||||
|
return &PathError{msg: msg}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrEmptyPath() *PathError {
|
||||||
|
return &PathError{msg: "path is empty"}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/goccy/go-json/internal/decoder"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreatePath creates JSON Path.
|
||||||
|
//
|
||||||
|
// JSON Path rule
|
||||||
|
// $ : root object or element. The JSON Path format must start with this operator, which refers to the outermost level of the JSON-formatted string.
|
||||||
|
// . : child operator. You can identify child values using dot-notation.
|
||||||
|
// .. : recursive descent.
|
||||||
|
// [] : subscript operator. If the JSON object is an array, you can use brackets to specify the array index.
|
||||||
|
// [*] : all objects/elements for array.
|
||||||
|
//
|
||||||
|
// Reserved words must be properly escaped when included in Path.
|
||||||
|
// Escale Rule
|
||||||
|
// single quote style escape: e.g.) `$['a.b'].c`
|
||||||
|
// double quote style escape: e.g.) `$."a.b".c`
|
||||||
|
func CreatePath(p string) (*Path, error) {
|
||||||
|
path, err := decoder.PathString(p).Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Path{path: path}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path represents JSON Path.
|
||||||
|
type Path struct {
|
||||||
|
path *decoder.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsedSingleQuotePathSelector whether single quote-based escaping was done when building the JSON Path.
|
||||||
|
func (p *Path) UsedSingleQuotePathSelector() bool {
|
||||||
|
return p.path.SingleQuotePathSelector
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsedSingleQuotePathSelector whether double quote-based escaping was done when building the JSON Path.
|
||||||
|
func (p *Path) UsedDoubleQuotePathSelector() bool {
|
||||||
|
return p.path.DoubleQuotePathSelector
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract extracts a specific JSON string.
|
||||||
|
func (p *Path) Extract(data []byte, optFuncs ...DecodeOptionFunc) ([][]byte, error) {
|
||||||
|
return extractFromPath(p, data, optFuncs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal extract and decode the value of the part corresponding to JSON Path from the input data.
|
||||||
|
func (p *Path) Unmarshal(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error {
|
||||||
|
contents, err := extractFromPath(p, data, optFuncs...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
results := make([]interface{}, 0, len(contents))
|
||||||
|
for _, content := range contents {
|
||||||
|
var result interface{}
|
||||||
|
if err := Unmarshal(content, &result); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
results = append(results, result)
|
||||||
|
}
|
||||||
|
if err := decoder.AssignValue(reflect.ValueOf(results), reflect.ValueOf(v)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get extract and substitute the value of the part corresponding to JSON Path from the input value.
|
||||||
|
func (p *Path) Get(src, dst interface{}) error {
|
||||||
|
return p.path.Get(reflect.ValueOf(src), reflect.ValueOf(dst))
|
||||||
|
}
|
|
@ -0,0 +1,234 @@
|
||||||
|
package json_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/goccy/go-json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExtractPath(t *testing.T) {
|
||||||
|
src := []byte(`{"a":{"b":10,"c":true},"b":"text"}`)
|
||||||
|
t.Run("$.a.b", func(t *testing.T) {
|
||||||
|
path, err := json.CreatePath("$.a.b")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
contents, err := path.Extract(src)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(contents) != 1 {
|
||||||
|
t.Fatal("failed to extract")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(contents[0], []byte("10")) {
|
||||||
|
t.Fatal("failed to extract")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("$.b", func(t *testing.T) {
|
||||||
|
path, err := json.CreatePath("$.b")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
contents, err := path.Extract(src)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(contents) != 1 {
|
||||||
|
t.Fatal("failed to extract")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(contents[0], []byte(`"text"`)) {
|
||||||
|
t.Fatal("failed to extract")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("$.a", func(t *testing.T) {
|
||||||
|
path, err := json.CreatePath("$.a")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
contents, err := path.Extract(src)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(contents) != 1 {
|
||||||
|
t.Fatal("failed to extract")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(contents[0], []byte(`{"b":10,"c":true}`)) {
|
||||||
|
t.Fatal("failed to extract")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalPath(t *testing.T) {
|
||||||
|
t.Run("int", func(t *testing.T) {
|
||||||
|
src := []byte(`{"a":{"b":10,"c":true},"b":"text"}`)
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
path, err := json.CreatePath("$.a.b")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var v int
|
||||||
|
if err := path.Unmarshal(src, &v); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if v != 10 {
|
||||||
|
t.Fatal("failed to unmarshal path")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("failure", func(t *testing.T) {
|
||||||
|
path, err := json.CreatePath("$.a.c")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var v map[string]interface{}
|
||||||
|
if err := path.Unmarshal(src, &v); err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("bool", func(t *testing.T) {
|
||||||
|
src := []byte(`{"a":{"b":10,"c":true},"b":"text"}`)
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
path, err := json.CreatePath("$.a.c")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var v bool
|
||||||
|
if err := path.Unmarshal(src, &v); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !v {
|
||||||
|
t.Fatal("failed to unmarshal path")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("failure", func(t *testing.T) {
|
||||||
|
path, err := json.CreatePath("$.a.b")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var v bool
|
||||||
|
if err := path.Unmarshal(src, &v); err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("map", func(t *testing.T) {
|
||||||
|
src := []byte(`{"a":{"b":10,"c":true},"b":"text"}`)
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
path, err := json.CreatePath("$.a")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var v map[string]interface{}
|
||||||
|
if err := path.Unmarshal(src, &v); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(v) != 2 {
|
||||||
|
t.Fatal("failed to decode map")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("path with single quote selector", func(t *testing.T) {
|
||||||
|
path, err := json.CreatePath("$['a.b'].c")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var v string
|
||||||
|
if err := path.Unmarshal([]byte(`{"a.b": {"c": "world"}}`), &v); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if v != "world" {
|
||||||
|
t.Fatal("failed to unmarshal path")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("path with double quote selector", func(t *testing.T) {
|
||||||
|
path, err := json.CreatePath(`$."a.b".c`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var v string
|
||||||
|
if err := path.Unmarshal([]byte(`{"a.b": {"c": "world"}}`), &v); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if v != "world" {
|
||||||
|
t.Fatal("failed to unmarshal path")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPath(t *testing.T) {
|
||||||
|
t.Run("selector", func(t *testing.T) {
|
||||||
|
var v interface{}
|
||||||
|
if err := json.Unmarshal([]byte(`{"a":{"b":10,"c":true},"b":"text"}`), &v); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
path, err := json.CreatePath("$.a.b")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var b int
|
||||||
|
if err := path.Get(v, &b); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if b != 10 {
|
||||||
|
t.Fatalf("failed to decode by json.Get")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("index", func(t *testing.T) {
|
||||||
|
var v interface{}
|
||||||
|
if err := json.Unmarshal([]byte(`{"a":[{"b":10,"c":true},{"b":"text"}]}`), &v); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
path, err := json.CreatePath("$.a[0].b")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var b int
|
||||||
|
if err := path.Get(v, &b); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if b != 10 {
|
||||||
|
t.Fatalf("failed to decode by json.Get")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("indexAll", func(t *testing.T) {
|
||||||
|
var v interface{}
|
||||||
|
if err := json.Unmarshal([]byte(`{"a":[{"b":1,"c":true},{"b":2},{"b":3}]}`), &v); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
path, err := json.CreatePath("$.a[*].b")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var b []int
|
||||||
|
if err := path.Get(v, &b); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(b, []int{1, 2, 3}) {
|
||||||
|
t.Fatalf("failed to decode by json.Get")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("recursive", func(t *testing.T) {
|
||||||
|
var v interface{}
|
||||||
|
if err := json.Unmarshal([]byte(`{"a":[{"b":1,"c":true},{"b":2},{"b":3}],"a2":{"b":4}}`), &v); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
path, err := json.CreatePath("$..b")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var b []int
|
||||||
|
if err := path.Get(v, &b); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sort.Ints(b)
|
||||||
|
if !reflect.DeepEqual(b, []int{1, 2, 3, 4}) {
|
||||||
|
t.Fatalf("failed to decode by json.Get")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue