Repository: kestra-io/kestra
Issue: JQ Function does not allow object / array destructuring afterwards
Labels: bug
enhancement

### Expected Behavior

_No response_

### Actual Behaviour

The given flow is not working since JQ filter requires a JsonNode to do its thing (which we get using Jackson readTree).  We should transform resulting ObjectNodes / ArrayNodes into Map / List to allow Pebble reading attributes down the line

### Steps To Reproduce

_No response_

### Environment Information

- Kestra Version:
- Operating System (OS / Docker / Kubernetes):
- Java Version (If not docker):


### Example flow

id: jq
namespace: io.kestra.bmu
tasks:
  - id: json
    type: io.kestra.core.tasks.debugs.Return
    format: |
      {
        "name": "test",
        "checks": [
          { "table": "worker" },
          { "table": "employee" }
        ]
      }

  - id: log_test
    type: io.kestra.core.tasks.log.Log
    message: |
      {% for check in outputs['json'].value | jq('.checks[]') %}
        {{ check.table }}
      {% endfor %}  

Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/core/src/main/java/io/kestra/core/runners/pebble/filters/JqFilter.java b/core/src/main/java/io/kestra/core/runners/pebble/filters/JqFilter.java
index 0d7b8521ca..579c6c29b9 100644
--- a/core/src/main/java/io/kestra/core/runners/pebble/filters/JqFilter.java
+++ b/core/src/main/java/io/kestra/core/runners/pebble/filters/JqFilter.java
@@ -1,15 +1,12 @@
 package io.kestra.core.runners.pebble.filters;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.BooleanNode;
-import com.fasterxml.jackson.databind.node.NullNode;
-import com.fasterxml.jackson.databind.node.NumericNode;
-import com.fasterxml.jackson.databind.node.TextNode;
+import com.fasterxml.jackson.databind.node.*;
+import io.kestra.core.serializers.JacksonMapper;
 import io.pebbletemplates.pebble.error.PebbleException;
 import io.pebbletemplates.pebble.extension.Filter;
 import io.pebbletemplates.pebble.template.EvaluationContext;
 import io.pebbletemplates.pebble.template.PebbleTemplate;
-import io.kestra.core.serializers.JacksonMapper;
 import net.thisptr.jackson.jq.BuiltinFunctionLoader;
 import net.thisptr.jackson.jq.JsonQuery;
 import net.thisptr.jackson.jq.Scope;
@@ -71,17 +68,21 @@ public Object apply(Object input, Map<String, Object> args, PebbleTemplate self,
                         out.add(v.numberValue());
                     } else if (v instanceof BooleanNode) {
                         out.add(v.booleanValue());
+                    } else if (v instanceof ObjectNode) {
+                        out.add(JacksonMapper.ofJson().convertValue(v, Map.class));
+                    } else if (v instanceof ArrayNode) {
+                        out.add(JacksonMapper.ofJson().convertValue(v, List.class));
                     } else {
                         out.add(v);
                     }
                 });
             } catch (Exception e) {
-                throw new Exception("Failed to resolve JQ expression '" + pattern +  "' and value '" + input +  "'", e);
+                throw new Exception("Failed to resolve JQ expression '" + pattern + "' and value '" + input + "'", e);
             }
 
             return out;
         } catch (Exception e) {
-            throw new PebbleException(e, "Unable to parse jq value '" + input +  "' with type '" + input.getClass().getName() + "'", lineNumber, self.getName());
+            throw new PebbleException(e, "Unable to parse jq value '" + input + "' with type '" + input.getClass().getName() + "'", lineNumber, self.getName());
         }
     }
 }


Test patch:
diff --git a/core/src/test/java/io/kestra/core/runners/pebble/filters/JqFilterTest.java b/core/src/test/java/io/kestra/core/runners/pebble/filters/JqFilterTest.java
index 886f2b2e9a..6d85c48e3f 100644
--- a/core/src/test/java/io/kestra/core/runners/pebble/filters/JqFilterTest.java
+++ b/core/src/test/java/io/kestra/core/runners/pebble/filters/JqFilterTest.java
@@ -4,6 +4,7 @@
 import io.kestra.core.exceptions.IllegalVariableEvaluationException;
 import io.kestra.core.runners.VariableRenderer;
 import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
 import org.junit.jupiter.api.Test;
 
 import java.time.ZoneId;
@@ -12,7 +13,6 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
-import jakarta.inject.Inject;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.*;
@@ -128,4 +128,20 @@ void typed() throws IllegalVariableEvaluationException {
         assertThat(variableRenderer.render("{{ vars | jq(\".bool\") | first | className }}", vars), is("java.lang.Boolean"));
         assertThat(variableRenderer.render("{{ vars | jq(\".null\") | first | className }}", vars), is(""));
     }
+
+    @Test
+    void object() throws IllegalVariableEvaluationException {
+        ImmutableMap<String, Object> vars = ImmutableMap.of(
+            "vars", ImmutableMap.of(
+                "object", Map.of("key", "value"),
+                "array", new String[]{"arrayValue"}
+            )
+        );
+
+        String render = variableRenderer.render("{% set object = vars | jq(\".object\") %}{{object[0].key}}", vars);
+        assertThat(render, is("value"));
+
+        render = variableRenderer.render("{% set array = vars | jq(\".array\") %}{{array[0][0]}}", vars);
+        assertThat(render, is("arrayValue"));
+    }
 }