...

Source file src/sigs.k8s.io/cli-utils/pkg/jsonpath/jsonpath.go

Documentation: sigs.k8s.io/cli-utils/pkg/jsonpath

     1  // Copyright 2021 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package jsonpath
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  
    10  	// Using gopkg.in/yaml.v3 instead of sigs.k8s.io/yaml on purpose.
    11  	// yaml.v3 correctly parses ints:
    12  	// https://github.com/kubernetes-sigs/yaml/issues/45
    13  	// yaml.v3 Node is also used as input to yqlib.
    14  	"gopkg.in/yaml.v3"
    15  	"k8s.io/klog/v2"
    16  
    17  	"github.com/spyzhov/ajson"
    18  )
    19  
    20  // Get evaluates the JSONPath expression to extract values from the input map.
    21  // Returns the node values that were found (zero or more), or an error.
    22  // For details about the JSONPath expression language, see:
    23  // https://goessner.net/articles/JsonPath/
    24  func Get(obj map[string]interface{}, expression string) ([]interface{}, error) {
    25  	// format input object as json for input into jsonpath library
    26  	jsonBytes, err := json.Marshal(obj)
    27  	if err != nil {
    28  		return nil, fmt.Errorf("failed to marshal input to json: %w", err)
    29  	}
    30  
    31  	klog.V(7).Info("jsonpath.Get input as json:\n%s", jsonBytes)
    32  
    33  	// parse json into an ajson node
    34  	root, err := ajson.Unmarshal(jsonBytes)
    35  	if err != nil {
    36  		return nil, fmt.Errorf("failed to unmarshal input json: %w", err)
    37  	}
    38  
    39  	// find nodes that match the expression
    40  	nodes, err := root.JSONPath(expression)
    41  	if err != nil {
    42  		return nil, fmt.Errorf("failed to evaluate jsonpath expression (%s): %w", expression, err)
    43  	}
    44  
    45  	result := make([]interface{}, len(nodes))
    46  
    47  	// get value of all matching nodes
    48  	for i, node := range nodes {
    49  		// format node value as json
    50  		jsonBytes, err = ajson.Marshal(node)
    51  		if err != nil {
    52  			return nil, fmt.Errorf("failed to marshal jsonpath result to json: %w", err)
    53  		}
    54  
    55  		klog.V(7).Info("jsonpath.Get output as json:\n%s", jsonBytes)
    56  
    57  		// parse json back into a Go primitive
    58  		var value interface{}
    59  		err = yaml.Unmarshal(jsonBytes, &value)
    60  		if err != nil {
    61  			return nil, fmt.Errorf("failed to unmarshal jsonpath result: %w", err)
    62  		}
    63  		result[i] = value
    64  	}
    65  
    66  	return result, nil
    67  }
    68  
    69  // Set evaluates the JSONPath expression to set a value in the input map.
    70  // Returns the number of matching nodes that were updated, or an error.
    71  // For details about the JSONPath expression language, see:
    72  // https://goessner.net/articles/JsonPath/
    73  func Set(obj map[string]interface{}, expression string, value interface{}) (int, error) {
    74  	// format input object as json for input into jsonpath library
    75  	jsonBytes, err := json.Marshal(obj)
    76  	if err != nil {
    77  		return 0, fmt.Errorf("failed to marshal input to json: %w", err)
    78  	}
    79  
    80  	klog.V(7).Info("jsonpath.Set input as json:\n%s", jsonBytes)
    81  
    82  	// parse json into an ajson node
    83  	root, err := ajson.Unmarshal(jsonBytes)
    84  	if err != nil {
    85  		return 0, fmt.Errorf("failed to unmarshal input json: %w", err)
    86  	}
    87  
    88  	// retrieve nodes that match the expression
    89  	nodes, err := root.JSONPath(expression)
    90  	if err != nil {
    91  		return 0, fmt.Errorf("failed to evaluate jsonpath expression (%s): %w", expression, err)
    92  	}
    93  	if len(nodes) == 0 {
    94  		// zero nodes found, none updated
    95  		return 0, nil
    96  	}
    97  
    98  	// set value of all matching nodes
    99  	for _, node := range nodes {
   100  		switch typedValue := value.(type) {
   101  		case bool:
   102  			err = node.SetBool(typedValue)
   103  		case string:
   104  			err = node.SetString(typedValue)
   105  		case int:
   106  			err = node.SetNumeric(float64(typedValue))
   107  		case float64:
   108  			err = node.SetNumeric(typedValue)
   109  		case []interface{}:
   110  			var arrayValue []*ajson.Node
   111  			arrayValue, err = toArrayOfNodes(typedValue)
   112  			if err != nil {
   113  				break
   114  			}
   115  			err = node.SetArray(arrayValue)
   116  		case map[string]interface{}:
   117  			var mapValue map[string]*ajson.Node
   118  			mapValue, err = toMapOfNodes(typedValue)
   119  			if err != nil {
   120  				break
   121  			}
   122  			err = node.SetObject(mapValue)
   123  		default:
   124  			if value == nil {
   125  				err = node.SetNull()
   126  			} else {
   127  				err = fmt.Errorf("unsupported value type: %T", value)
   128  			}
   129  		}
   130  		if err != nil {
   131  			return 0, err
   132  		}
   133  	}
   134  
   135  	// format into an ajson node
   136  	jsonBytes, err = ajson.Marshal(root)
   137  	if err != nil {
   138  		return 0, fmt.Errorf("failed to marshal jsonpath result to json: %w", err)
   139  	}
   140  
   141  	klog.V(7).Info("jsonpath.Set output as json:\n%s", jsonBytes)
   142  
   143  	// parse json back into the input map
   144  	err = yaml.Unmarshal(jsonBytes, &obj)
   145  	if err != nil {
   146  		return 0, fmt.Errorf("failed to unmarshal jsonpath result: %w", err)
   147  	}
   148  
   149  	return len(nodes), nil
   150  }
   151  
   152  func toArrayOfNodes(obj []interface{}) ([]*ajson.Node, error) {
   153  	out := make([]*ajson.Node, len(obj))
   154  	for index, value := range obj {
   155  		// format input object as json for input into jsonpath library
   156  		jsonBytes, err := json.Marshal(value)
   157  		if err != nil {
   158  			return nil, fmt.Errorf("failed to marshal array element to json: %w", err)
   159  		}
   160  
   161  		// parse json into an ajson node
   162  		node, err := ajson.Unmarshal(jsonBytes)
   163  		if err != nil {
   164  			return nil, fmt.Errorf("failed to unmarshal array element: %w", err)
   165  		}
   166  		out[index] = node
   167  	}
   168  	return out, nil
   169  }
   170  
   171  func toMapOfNodes(obj map[string]interface{}) (map[string]*ajson.Node, error) {
   172  	out := make(map[string]*ajson.Node, len(obj))
   173  	for key, value := range obj {
   174  		// format input object as json for input into jsonpath library
   175  		jsonBytes, err := json.Marshal(value)
   176  		if err != nil {
   177  			return nil, fmt.Errorf("failed to marshal map value to json: %w", err)
   178  		}
   179  
   180  		// parse json into an ajson node
   181  		node, err := ajson.Unmarshal(jsonBytes)
   182  		if err != nil {
   183  			return nil, fmt.Errorf("failed to unmarshal map value: %w", err)
   184  		}
   185  		out[key] = node
   186  	}
   187  	return out, nil
   188  }
   189  

View as plain text