Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support JSON Path for decoding #250

Merged
merged 1 commit into from Nov 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -22,7 +22,7 @@ cover-html: cover

.PHONY: lint
lint: golangci-lint
golangci-lint run
$(BIN_DIR)/golangci-lint run

golangci-lint: | $(BIN_DIR)
@{ \
Expand Down
24 changes: 24 additions & 0 deletions benchmarks/path_test.go
@@ -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 changes: 28 additions & 0 deletions decode.go
Expand Up @@ -83,6 +83,34 @@ func unmarshalContext(ctx context.Context, data []byte, v interface{}, optFuncs
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 {
src := make([]byte, len(data)+1) // append nul byte to the end
copy(src, data)
Expand Down
2 changes: 2 additions & 0 deletions error.go
Expand Up @@ -37,3 +37,5 @@ type UnmarshalTypeError = errors.UnmarshalTypeError
type UnsupportedTypeError = errors.UnsupportedTypeError

type UnsupportedValueError = errors.UnsupportedValueError

type PathError = errors.PathError
4 changes: 4 additions & 0 deletions internal/decoder/anonymous_field.go
Expand Up @@ -35,3 +35,7 @@ func (d *anonymousFieldDecoder) Decode(ctx *RuntimeContext, cursor, depth int64,
p = *(*unsafe.Pointer)(p)
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)
}
5 changes: 5 additions & 0 deletions internal/decoder/array.go
@@ -1,6 +1,7 @@
package decoder

import (
"fmt"
"unsafe"

"github.com/goccy/go-json/internal/errors"
Expand Down Expand Up @@ -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")
}