Skip to content
This repository has been archived by the owner on Jan 9, 2025. It is now read-only.

feat: add JQ input field that accepts any type #201

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion operator/json/v0/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ Process JSON through a `jq` command
| Input | ID | Type | Description |
| :--- | :--- | :--- | :--- |
| Task ID (required) | `task` | string | `TASK_JQ` |
| JSON input (required) | `jsonInput` | string | JSON string to be processed |
| JSON value | `json-value` | any | JSON value (e.g. string, number, object, array...) to be processed by the filter |
| Filter (required) | `jqFilter` | string | Filter, in `jq` syntax, that will be applied to the JSON input |
| (DEPRECATED) JSON input | `jsonInput` | string | (DEPRECATED, use 'JSON value' instead) JSON string to be processed. This field allows templated inputs, but the data might preprocessing (marshalling). This field will be used in absence of 'JSON value' for backwards compatibility reasons. |



Expand Down
74 changes: 61 additions & 13 deletions operator/json/v0/component_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,19 @@ func TestOperator_Execute(t *testing.T) {
wantJSON json.RawMessage
}{
{
name: "ok - marshal",
name: "ok - marshal object",

task: taskMarshal,
in: map[string]any{"json": asMap},
wantJSON: json.RawMessage(asJSON),
},
{
name: "ok - marshal array",

task: taskMarshal,
in: map[string]any{"json": []any{false, true, "dos", 3}},
wantJSON: json.RawMessage(`[false, true, "dos", 3]`),
},
{
name: "nok - marshal",

Expand All @@ -60,6 +67,20 @@ func TestOperator_Execute(t *testing.T) {
in: map[string]any{"string": asJSON},
want: map[string]any{"json": asMap},
},
{
name: "ok - unmarshal array",

task: taskUnmarshal,
in: map[string]any{"string": `[false, true, "dos", 3]`},
want: map[string]any{"json": []any{false, true, "dos", 3}},
},
{
name: "ok - unmarshal string",

task: taskUnmarshal,
in: map[string]any{"string": `"foo"`},
want: map[string]any{"json": "foo"},
},
{
name: "nok - unmarshal",

Expand All @@ -68,7 +89,7 @@ func TestOperator_Execute(t *testing.T) {
wantErr: "Couldn't parse the JSON string. Please check the syntax is correct.",
},
{
name: "ok - jq",
name: "ok - jq from string",

task: taskJQ,
in: map[string]any{
Expand All @@ -80,28 +101,55 @@ func TestOperator_Execute(t *testing.T) {
},
},
{
name: "ok - jq create object",
name: "nok - jq invalid JSON string",

task: taskJQ,
in: map[string]any{
"jsonInput": "{",
"jqFilter": ".",
},
wantErr: "Couldn't parse the JSON input. Please check the syntax is correct.",
},
{
name: "ok - string value",

task: taskJQ,
in: map[string]any{
"jsonInput": `{"id": "sample", "10": {"b": 42}}`,
"jqFilter": `{(.id): .["10"].b}`,
"json-value": "foo",
"jqFilter": `. + "bar"`,
},
want: map[string]any{
"results": []any{
map[string]any{"sample": 42},
},
"results": []any{"foobar"},
},
},
{
name: "nok - jq invalid JSON input",
name: "ok - from array",

task: taskJQ,
in: map[string]any{
"jsonInput": "{",
"jqFilter": ".",
"json-value": []any{2, 3, 23},
"jqFilter": ".[2]",
},
want: map[string]any{
"results": []any{23},
},
},
{
name: "ok - jq create object",

task: taskJQ,
in: map[string]any{
"json-value": map[string]any{
"id": "sample",
"10": map[string]any{"b": 42},
},
"jqFilter": `{(.id): .["10"].b}`,
},
want: map[string]any{
"results": []any{
map[string]any{"sample": 42},
},
},
wantErr: "Couldn't parse the JSON input. Please check the syntax is correct.",
},
{
name: "nok - jq invalid filter",
Expand Down Expand Up @@ -138,7 +186,7 @@ func TestOperator_Execute(t *testing.T) {
if tc.wantJSON != nil {
// Check JSON in the output string.
b := got[0].Fields["string"].GetStringValue()
c.Check([]byte(b), qt.JSONEquals, tc.wantJSON)
c.Check([]byte(b), qt.JSONEquals, tc.wantJSON, qt.Commentf(string(b)+" vs "+string(tc.wantJSON)))
return
}

Expand Down
27 changes: 23 additions & 4 deletions operator/json/v0/config/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,9 @@
"instillUIOrder": 0,
"properties": {
"jsonInput": {
"instillUIOrder": 0,
"description": "JSON string to be processed",
"instillUIOrder": 2,
"description": "(DEPRECATED, use 'JSON value' instead) JSON string to be processed. This field allows templated inputs, but the data might preprocessing (marshalling). This field will be used in absence of 'JSON value' for backwards compatibility reasons.",
"instillShortDescription": "(DEPRECATED) JSON string to be processed",
"instillAcceptFormats": [
"string"
],
Expand All @@ -126,9 +127,24 @@
"template"
],
"instillUIMultiline": true,
"title": "JSON input",
"title": "(DEPRECATED) JSON input",
"type": "string"
},
"json-value": {
"instillUIOrder": 0,
"description": "JSON value (e.g. string, number, object, array...) to be processed by the filter",
"instillAcceptFormats": [
"object",
"structured/*",
"semi-structured/*"
],
"instillUpstreamTypes": [
"value",
"reference"
],
"instillUIMultiline": true,
"title": "JSON value"
},
"jqFilter": {
"instillUIOrder": 1,
"description": "Filter, in `jq` syntax, that will be applied to the JSON input",
Expand All @@ -146,7 +162,10 @@
}
},
"required": [
"jsonInput",
"jqFilter"
],
"instillEditOnNodeFields": [
"json-value",
"jqFilter"
],
"title": "Input",
Expand Down
19 changes: 10 additions & 9 deletions operator/json/v0/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package json

import (
"context"
_ "embed"
"encoding/json"
"fmt"
"sync"

_ "embed"

"github.com/itchyny/gojq"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/structpb"
Expand Down Expand Up @@ -94,25 +95,25 @@ func (e *execution) unmarshal(in *structpb.Struct) (*structpb.Struct, error) {
out := new(structpb.Struct)

b := []byte(in.Fields["string"].GetStringValue())
obj := new(structpb.Struct)
obj := new(structpb.Value)
if err := protojson.Unmarshal(b, obj); err != nil {
return nil, errmsg.AddMessage(err, "Couldn't parse the JSON string. Please check the syntax is correct.")
}

out.Fields = map[string]*structpb.Value{
"json": structpb.NewStructValue(obj),
}
out.Fields = map[string]*structpb.Value{"json": obj}

return out, nil
}

func (e *execution) jq(in *structpb.Struct) (*structpb.Struct, error) {
out := new(structpb.Struct)

b := []byte(in.Fields["jsonInput"].GetStringValue())
var input any
if err := json.Unmarshal(b, &input); err != nil {
return nil, errmsg.AddMessage(err, "Couldn't parse the JSON input. Please check the syntax is correct.")
input := in.Fields["json-value"].AsInterface()
if input == nil {
b := []byte(in.Fields["jsonInput"].GetStringValue())
if err := json.Unmarshal(b, &input); err != nil {
return nil, errmsg.AddMessage(err, "Couldn't parse the JSON input. Please check the syntax is correct.")
}
}

queryStr := in.Fields["jqFilter"].GetStringValue()
Expand Down
Loading