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 ,
RootSelectorOnly : node == nil ,
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 {
offset , err := b .buildNext (buf [cursor :])
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 ))
b .singleQuotePathSelector = true
return b .buildNextCharIfExists (buf , cursor +2 )
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 ))
b .doubleQuotePathSelector = true
return b .buildNextCharIfExists (buf , cursor +1 )
}
}
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 +1 )
}
}
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
RootSelectorOnly bool
SingleQuotePathSelector bool
DoubleQuotePathSelector bool
}
func (p *Path ) Field (sel string ) (PathNode , bool , error ) {
if p .node == nil {
return nil , false , nil
}
return p .node .Field (sel )
}
func (p *Path ) Get (src , dst reflect .Value ) error {
if p .node == nil {
return nil
}
return p .node .Get (src , dst )
}
func (p *Path ) String () string {
if p .node == nil {
return "$"
}
return p .node .String ()
}
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
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .