github.com/instill-ai/component@v0.16.0-beta/pkg/operator/json/v0/main.go (about)

     1  //go:generate compogen readme --operator ./config ./README.mdx
     2  package json
     3  
     4  import (
     5  	_ "embed"
     6  	"encoding/json"
     7  	"fmt"
     8  	"sync"
     9  
    10  	"github.com/itchyny/gojq"
    11  	"go.uber.org/zap"
    12  	"google.golang.org/protobuf/encoding/protojson"
    13  	"google.golang.org/protobuf/types/known/structpb"
    14  
    15  	"github.com/instill-ai/component/pkg/base"
    16  	"github.com/instill-ai/x/errmsg"
    17  )
    18  
    19  const (
    20  	taskMarshal   = "TASK_MARSHAL"
    21  	taskUnmarshal = "TASK_UNMARSHAL"
    22  	taskJQ        = "TASK_JQ"
    23  )
    24  
    25  var (
    26  	//go:embed config/definition.json
    27  	definitionJSON []byte
    28  	//go:embed config/tasks.json
    29  	tasksJSON []byte
    30  
    31  	once sync.Once
    32  	op   *operator
    33  )
    34  
    35  type operator struct {
    36  	base.BaseOperator
    37  }
    38  
    39  type execution struct {
    40  	base.BaseOperatorExecution
    41  
    42  	execute func(*structpb.Struct) (*structpb.Struct, error)
    43  }
    44  
    45  // Init returns an implementation of IOperator that processes JSON objects.
    46  func Init(l *zap.Logger, u base.UsageHandler) *operator {
    47  	once.Do(func() {
    48  		op = &operator{
    49  			BaseOperator: base.BaseOperator{
    50  				Logger:       l,
    51  				UsageHandler: u,
    52  			},
    53  		}
    54  		err := op.LoadOperatorDefinition(definitionJSON, tasksJSON, nil)
    55  		if err != nil {
    56  			panic(err)
    57  		}
    58  	})
    59  	return op
    60  }
    61  
    62  func (o *operator) CreateExecution(sysVars map[string]any, task string) (*base.ExecutionWrapper, error) {
    63  	e := &execution{
    64  		BaseOperatorExecution: base.BaseOperatorExecution{Operator: o, SystemVariables: sysVars, Task: task},
    65  	}
    66  
    67  	switch task {
    68  	case taskMarshal:
    69  		e.execute = e.marshal
    70  	case taskUnmarshal:
    71  		e.execute = e.unmarshal
    72  	case taskJQ:
    73  		e.execute = e.jq
    74  	default:
    75  		return nil, errmsg.AddMessage(
    76  			fmt.Errorf("not supported task: %s", task),
    77  			fmt.Sprintf("%s task is not supported.", task),
    78  		)
    79  	}
    80  	return &base.ExecutionWrapper{Execution: e}, nil
    81  }
    82  
    83  func (e *execution) marshal(in *structpb.Struct) (*structpb.Struct, error) {
    84  	out := new(structpb.Struct)
    85  
    86  	b, err := protojson.Marshal(in.Fields["json"])
    87  	if err != nil {
    88  		return nil, errmsg.AddMessage(err, "Couldn't convert the provided object to JSON.")
    89  	}
    90  
    91  	out.Fields = map[string]*structpb.Value{
    92  		"string": structpb.NewStringValue(string(b)),
    93  	}
    94  
    95  	return out, nil
    96  }
    97  
    98  func (e *execution) unmarshal(in *structpb.Struct) (*structpb.Struct, error) {
    99  	out := new(structpb.Struct)
   100  
   101  	b := []byte(in.Fields["string"].GetStringValue())
   102  	obj := new(structpb.Struct)
   103  	if err := protojson.Unmarshal(b, obj); err != nil {
   104  		return nil, errmsg.AddMessage(err, "Couldn't parse the JSON string. Please check the syntax is correct.")
   105  	}
   106  
   107  	out.Fields = map[string]*structpb.Value{
   108  		"json": structpb.NewStructValue(obj),
   109  	}
   110  
   111  	return out, nil
   112  }
   113  
   114  func (e *execution) jq(in *structpb.Struct) (*structpb.Struct, error) {
   115  	out := new(structpb.Struct)
   116  
   117  	b := []byte(in.Fields["jsonInput"].GetStringValue())
   118  	var input any
   119  	if err := json.Unmarshal(b, &input); err != nil {
   120  		return nil, errmsg.AddMessage(err, "Couldn't parse the JSON input. Please check the syntax is correct.")
   121  	}
   122  
   123  	queryStr := in.Fields["jqFilter"].GetStringValue()
   124  	q, err := gojq.Parse(queryStr)
   125  	if err != nil {
   126  		// Error messages from gojq are human-friendly enough.
   127  		msg := fmt.Sprintf("Couldn't parse the jq filter: %s. Please check the syntax is correct.", err.Error())
   128  		return nil, errmsg.AddMessage(err, msg)
   129  	}
   130  
   131  	results := []any{}
   132  	iter := q.Run(input)
   133  	for {
   134  		v, ok := iter.Next()
   135  		if !ok {
   136  			break
   137  		}
   138  
   139  		if err, ok := v.(error); ok {
   140  			msg := fmt.Sprintf("Couldn't apply the jq filter: %s.", err.Error())
   141  			return nil, errmsg.AddMessage(err, msg)
   142  		}
   143  
   144  		results = append(results, v)
   145  	}
   146  
   147  	list, err := structpb.NewList(results)
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	out.Fields = map[string]*structpb.Value{
   153  		"results": structpb.NewListValue(list),
   154  	}
   155  
   156  	return out, nil
   157  }
   158  
   159  func (e *execution) Execute(inputs []*structpb.Struct) ([]*structpb.Struct, error) {
   160  	outputs := make([]*structpb.Struct, len(inputs))
   161  
   162  	for i, input := range inputs {
   163  		output, err := e.execute(input)
   164  		if err != nil {
   165  			return nil, err
   166  		}
   167  
   168  		outputs[i] = output
   169  	}
   170  
   171  	return outputs, nil
   172  }