| package jmespath |
| |
| import ( |
| "errors" |
| "reflect" |
| "unicode" |
| "unicode/utf8" |
| ) |
| |
| /* This is a tree based interpreter. It walks the AST and directly |
| interprets the AST to search through a JSON document. |
| */ |
| |
| type treeInterpreter struct { |
| fCall *functionCaller |
| } |
| |
| func newInterpreter() *treeInterpreter { |
| interpreter := treeInterpreter{} |
| interpreter.fCall = newFunctionCaller() |
| return &interpreter |
| } |
| |
| type expRef struct { |
| ref ASTNode |
| } |
| |
| // Execute takes an ASTNode and input data and interprets the AST directly. |
| // It will produce the result of applying the JMESPath expression associated |
| // with the ASTNode to the input data "value". |
| func (intr *treeInterpreter) Execute(node ASTNode, value interface{}) (interface{}, error) { |
| switch node.nodeType { |
| case ASTComparator: |
| left, err := intr.Execute(node.children[0], value) |
| if err != nil { |
| return nil, err |
| } |
| right, err := intr.Execute(node.children[1], value) |
| if err != nil { |
| return nil, err |
| } |
| switch node.value { |
| case tEQ: |
| return objsEqual(left, right), nil |
| case tNE: |
| return !objsEqual(left, right), nil |
| } |
| leftNum, ok := left.(float64) |
| if !ok { |
| return nil, nil |
| } |
| rightNum, ok := right.(float64) |
| if !ok { |
| return nil, nil |
| } |
| switch node.value { |
| case tGT: |
| return leftNum > rightNum, nil |
| case tGTE: |
| return leftNum >= rightNum, nil |
| case tLT: |
| return leftNum < rightNum, nil |
| case tLTE: |
| return leftNum <= rightNum, nil |
| } |
| case ASTExpRef: |
| return expRef{ref: node.children[0]}, nil |
| case ASTFunctionExpression: |
| resolvedArgs := []interface{}{} |
| for _, arg := range node.children { |
| current, err := intr.Execute(arg, value) |
| if err != nil { |
| return nil, err |
| } |
| resolvedArgs = append(resolvedArgs, current) |
| } |
| return intr.fCall.CallFunction(node.value.(string), resolvedArgs, intr) |
| case ASTField: |
| if m, ok := value.(map[string]interface{}); ok { |
| key := node.value.(string) |
| return m[key], nil |
| } |
| return intr.fieldFromStruct(node.value.(string), value) |
| case ASTFilterProjection: |
| left, err := intr.Execute(node.children[0], value) |
| if err != nil { |
| return nil, nil |
| } |
| sliceType, ok := left.([]interface{}) |
| if !ok { |
| if isSliceType(left) { |
| return intr.filterProjectionWithReflection(node, left) |
| } |
| return nil, nil |
| } |
| compareNode := node.children[2] |
| collected := []interface{}{} |
| for _, element := range sliceType { |
| result, err := intr.Execute(compareNode, element) |
| if err != nil { |
| return nil, err |
| } |
| if !isFalse(result) { |
| current, err := intr.Execute(node.children[1], element) |
| if err != nil { |
| return nil, err |
| } |
| if current != nil { |
| collected = append(collected, current) |
| } |
| } |
| } |
| return collected, nil |
| case ASTFlatten: |
| left, err := intr.Execute(node.children[0], value) |
| if err != nil { |
| return nil, nil |
| } |
| sliceType, ok := left.([]interface{}) |
| if !ok { |
| // If we can't type convert to []interface{}, there's |
| // a chance this could still work via reflection if we're |
| // dealing with user provided types. |
| if isSliceType(left) { |
| return intr.flattenWithReflection(left) |
| } |
| return nil, nil |
| } |
| flattened := []interface{}{} |
| for _, element := range sliceType { |
| if elementSlice, ok := element.([]interface{}); ok { |
| flattened = append(flattened, elementSlice...) |
| } else if isSliceType(element) { |
| reflectFlat := []interface{}{} |
| v := reflect.ValueOf(element) |
| for i := 0; i < v.Len(); i++ { |
| reflectFlat = append(reflectFlat, v.Index(i).Interface()) |
| } |
| flattened = append(flattened, reflectFlat...) |
| } else { |
| flattened = append(flattened, element) |
| } |
| } |
| return flattened, nil |
| case ASTIdentity, ASTCurrentNode: |
| return value, nil |
| case ASTIndex: |
| if sliceType, ok := value.([]interface{}); ok { |
| index := node.value.(int) |
| if index < 0 { |
| index += len(sliceType) |
| } |
| if index < len(sliceType) && index >= 0 { |
| return sliceType[index], nil |
| } |
| return nil, nil |
| } |
| // Otherwise try via reflection. |
| rv := reflect.ValueOf(value) |
| if rv.Kind() == reflect.Slice { |
| index := node.value.(int) |
| if index < 0 { |
| index += rv.Len() |
| } |
| if index < rv.Len() && index >= 0 { |
| v := rv.Index(index) |
| return v.Interface(), nil |
| } |
| } |
| return nil, nil |
| case ASTKeyValPair: |
| return intr.Execute(node.children[0], value) |
| case ASTLiteral: |
| return node.value, nil |
| case ASTMultiSelectHash: |
| if value == nil { |
| return nil, nil |
| } |
| collected := make(map[string]interface{}) |
| for _, child := range node.children { |
| current, err := intr.Execute(child, value) |
| if err != nil { |
| return nil, err |
| } |
| key := child.value.(string) |
| collected[key] = current |
| } |
| return collected, nil |
| case ASTMultiSelectList: |
| if value == nil { |
| return nil, nil |
| } |
| collected := []interface{}{} |
| for _, child := range node.children { |
| current, err := intr.Execute(child, value) |
| if err != nil { |
| return nil, err |
| } |
| collected = append(collected, current) |
| } |
| return collected, nil |
| case ASTOrExpression: |
| matched, err := intr.Execute(node.children[0], value) |
| if err != nil { |
| return nil, err |
| } |
| if isFalse(matched) { |
| matched, err = intr.Execute(node.children[1], value) |
| if err != nil { |
| return nil, err |
| } |
| } |
| return matched, nil |
| case ASTAndExpression: |
| matched, err := intr.Execute(node.children[0], value) |
| if err != nil { |
| return nil, err |
| } |
| if isFalse(matched) { |
| return matched, nil |
| } |
| return intr.Execute(node.children[1], value) |
| case ASTNotExpression: |
| matched, err := intr.Execute(node.children[0], value) |
| if err != nil { |
| return nil, err |
| } |
| if isFalse(matched) { |
| return true, nil |
| } |
| return false, nil |
| case ASTPipe: |
| result := value |
| var err error |
| for _, child := range node.children { |
| result, err = intr.Execute(child, result) |
| if err != nil { |
| return nil, err |
| } |
| } |
| return result, nil |
| case ASTProjection: |
| left, err := intr.Execute(node.children[0], value) |
| if err != nil { |
| return nil, err |
| } |
| sliceType, ok := left.([]interface{}) |
| if !ok { |
| if isSliceType(left) { |
| return intr.projectWithReflection(node, left) |
| } |
| return nil, nil |
| } |
| collected := []interface{}{} |
| var current interface{} |
| for _, element := range sliceType { |
| current, err = intr.Execute(node.children[1], element) |
| if err != nil { |
| return nil, err |
| } |
| if current != nil { |
| collected = append(collected, current) |
| } |
| } |
| return collected, nil |
| case ASTSubexpression, ASTIndexExpression: |
| left, err := intr.Execute(node.children[0], value) |
| if err != nil { |
| return nil, err |
| } |
| return intr.Execute(node.children[1], left) |
| case ASTSlice: |
| sliceType, ok := value.([]interface{}) |
| if !ok { |
| if isSliceType(value) { |
| return intr.sliceWithReflection(node, value) |
| } |
| return nil, nil |
| } |
| parts := node.value.([]*int) |
| sliceParams := make([]sliceParam, 3) |
| for i, part := range parts { |
| if part != nil { |
| sliceParams[i].Specified = true |
| sliceParams[i].N = *part |
| } |
| } |
| return slice(sliceType, sliceParams) |
| case ASTValueProjection: |
| left, err := intr.Execute(node.children[0], value) |
| if err != nil { |
| return nil, nil |
| } |
| mapType, ok := left.(map[string]interface{}) |
| if !ok { |
| return nil, nil |
| } |
| values := make([]interface{}, len(mapType)) |
| for _, value := range mapType { |
| values = append(values, value) |
| } |
| collected := []interface{}{} |
| for _, element := range values { |
| current, err := intr.Execute(node.children[1], element) |
| if err != nil { |
| return nil, err |
| } |
| if current != nil { |
| collected = append(collected, current) |
| } |
| } |
| return collected, nil |
| } |
| return nil, errors.New("Unknown AST node: " + node.nodeType.String()) |
| } |
| |
| func (intr *treeInterpreter) fieldFromStruct(key string, value interface{}) (interface{}, error) { |
| rv := reflect.ValueOf(value) |
| first, n := utf8.DecodeRuneInString(key) |
| fieldName := string(unicode.ToUpper(first)) + key[n:] |
| if rv.Kind() == reflect.Struct { |
| v := rv.FieldByName(fieldName) |
| if !v.IsValid() { |
| return nil, nil |
| } |
| return v.Interface(), nil |
| } else if rv.Kind() == reflect.Ptr { |
| // Handle multiple levels of indirection? |
| if rv.IsNil() { |
| return nil, nil |
| } |
| rv = rv.Elem() |
| v := rv.FieldByName(fieldName) |
| if !v.IsValid() { |
| return nil, nil |
| } |
| return v.Interface(), nil |
| } |
| return nil, nil |
| } |
| |
| func (intr *treeInterpreter) flattenWithReflection(value interface{}) (interface{}, error) { |
| v := reflect.ValueOf(value) |
| flattened := []interface{}{} |
| for i := 0; i < v.Len(); i++ { |
| element := v.Index(i).Interface() |
| if reflect.TypeOf(element).Kind() == reflect.Slice { |
| // Then insert the contents of the element |
| // slice into the flattened slice, |
| // i.e flattened = append(flattened, mySlice...) |
| elementV := reflect.ValueOf(element) |
| for j := 0; j < elementV.Len(); j++ { |
| flattened = append( |
| flattened, elementV.Index(j).Interface()) |
| } |
| } else { |
| flattened = append(flattened, element) |
| } |
| } |
| return flattened, nil |
| } |
| |
| func (intr *treeInterpreter) sliceWithReflection(node ASTNode, value interface{}) (interface{}, error) { |
| v := reflect.ValueOf(value) |
| parts := node.value.([]*int) |
| sliceParams := make([]sliceParam, 3) |
| for i, part := range parts { |
| if part != nil { |
| sliceParams[i].Specified = true |
| sliceParams[i].N = *part |
| } |
| } |
| final := []interface{}{} |
| for i := 0; i < v.Len(); i++ { |
| element := v.Index(i).Interface() |
| final = append(final, element) |
| } |
| return slice(final, sliceParams) |
| } |
| |
| func (intr *treeInterpreter) filterProjectionWithReflection(node ASTNode, value interface{}) (interface{}, error) { |
| compareNode := node.children[2] |
| collected := []interface{}{} |
| v := reflect.ValueOf(value) |
| for i := 0; i < v.Len(); i++ { |
| element := v.Index(i).Interface() |
| result, err := intr.Execute(compareNode, element) |
| if err != nil { |
| return nil, err |
| } |
| if !isFalse(result) { |
| current, err := intr.Execute(node.children[1], element) |
| if err != nil { |
| return nil, err |
| } |
| if current != nil { |
| collected = append(collected, current) |
| } |
| } |
| } |
| return collected, nil |
| } |
| |
| func (intr *treeInterpreter) projectWithReflection(node ASTNode, value interface{}) (interface{}, error) { |
| collected := []interface{}{} |
| v := reflect.ValueOf(value) |
| for i := 0; i < v.Len(); i++ { |
| element := v.Index(i).Interface() |
| result, err := intr.Execute(node.children[1], element) |
| if err != nil { |
| return nil, err |
| } |
| if result != nil { |
| collected = append(collected, result) |
| } |
| } |
| return collected, nil |
| } |