github.com/hairyhenderson/gomplate/v4@v4.0.0-pre-2.0.20240520121557-362f058f0c93/coll/jq.go (about)

     1  package coll
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"reflect"
     8  
     9  	"github.com/itchyny/gojq"
    10  )
    11  
    12  // JQ -
    13  func JQ(ctx context.Context, jqExpr string, in interface{}) (interface{}, error) {
    14  	query, err := gojq.Parse(jqExpr)
    15  	if err != nil {
    16  		return nil, fmt.Errorf("jq parsing expression %q: %w", jqExpr, err)
    17  	}
    18  
    19  	// convert input to a supported type, if necessary
    20  	in, err = jqConvertType(in)
    21  	if err != nil {
    22  		return nil, fmt.Errorf("jq type conversion: %w", err)
    23  	}
    24  
    25  	iter := query.RunWithContext(ctx, in)
    26  	var out interface{}
    27  	a := []interface{}{}
    28  	for {
    29  		v, ok := iter.Next()
    30  		if !ok {
    31  			break
    32  		}
    33  		if err, ok := v.(error); ok {
    34  			return nil, fmt.Errorf("jq execution: %w", err)
    35  		}
    36  		a = append(a, v)
    37  	}
    38  	if len(a) == 1 {
    39  		out = a[0]
    40  	} else {
    41  		out = a
    42  	}
    43  
    44  	return out, nil
    45  }
    46  
    47  // jqConvertType converts the input to a map[string]interface{}, []interface{},
    48  // or other supported primitive JSON types.
    49  func jqConvertType(in interface{}) (interface{}, error) {
    50  	// if it's already a supported type, pass it through
    51  	switch in.(type) {
    52  	case map[string]interface{}, []interface{},
    53  		string, []byte,
    54  		nil, bool,
    55  		int, int8, int16, int32, int64,
    56  		uint, uint8, uint16, uint32, uint64,
    57  		float32, float64:
    58  		return in, nil
    59  	}
    60  
    61  	inType := reflect.TypeOf(in)
    62  	value := reflect.ValueOf(in)
    63  
    64  	// pointers need to be dereferenced first
    65  	if inType.Kind() == reflect.Ptr {
    66  		inType = inType.Elem()
    67  		value = value.Elem()
    68  	}
    69  
    70  	mapType := reflect.TypeOf(map[string]interface{}{})
    71  	sliceType := reflect.TypeOf([]interface{}{})
    72  	// if it can be converted to a map or slice, do that
    73  	if inType.ConvertibleTo(mapType) {
    74  		return value.Convert(mapType).Interface(), nil
    75  	} else if inType.ConvertibleTo(sliceType) {
    76  		return value.Convert(sliceType).Interface(), nil
    77  	}
    78  
    79  	// if it's a struct, the simplest (though not necessarily most efficient)
    80  	// is to JSON marshal/unmarshal it
    81  	if inType.Kind() == reflect.Struct {
    82  		b, err := json.Marshal(in)
    83  		if err != nil {
    84  			return nil, fmt.Errorf("json marshal struct: %w", err)
    85  		}
    86  		var m map[string]interface{}
    87  		err = json.Unmarshal(b, &m)
    88  		if err != nil {
    89  			return nil, fmt.Errorf("json unmarshal struct: %w", err)
    90  		}
    91  		return m, nil
    92  	}
    93  
    94  	// we maybe don't need to convert the value, so return it as-is
    95  	return in, nil
    96  }