debugFunctions() {
private ObjectMapper mapper = DEFAULT_MAPPER;
/**
- * Use {@link #Scope(Scope) Scope(null)} instead and explicitly
+ * Use {@link Scope#newEmptyScope()} instead and explicitly
* call {@link #loadFunctions(ClassLoader)} with the appropriate
* {@link ClassLoader} for your application. E.g.:
*
- * final Scope scope = new Scope(null);
+ * final Scope scope = Scope.newEmptyScope();
* scope.loadFunctions(Thread.currentThread().getContextClassLoader());
*
*/
@@ -88,10 +89,19 @@ public Scope() {
this(RootScopeHolder.INSTANCE);
}
+ @Deprecated
public Scope(final Scope parentScope) {
this.parentScope = parentScope;
}
+ public static Scope newEmptyScope() {
+ return new Scope(null);
+ }
+
+ public static Scope newChildScope(final Scope scope) {
+ return new Scope(scope);
+ }
+
public void addFunction(final String name, final int n, final Function q) {
functions.put(name + "/" + n, q);
}
@@ -208,7 +218,7 @@ private void loadMacros(final ClassLoader classLoader, final String path) {
final List configs = loadConfig(classLoader, path);
for (final JqJson jqJson : configs) {
for (final JqJson.JqFuncDef def : jqJson.functions)
- addFunction(def.name, def.args.size(), new JsonQueryFunction(def.name, def.args, JsonQuery.compile(def.body)));
+ addFunction(def.name, def.args.size(), new JsonQueryFunction(def.name, def.args, JsonQuery.compile(def.body), this));
}
} catch (final IOException e) {
throw new RuntimeException("Failed to load macros", e);
diff --git a/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/FixedScopeQuery.java b/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/FixedScopeQuery.java
index 8590fdc0..8b5fdd59 100644
--- a/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/FixedScopeQuery.java
+++ b/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/FixedScopeQuery.java
@@ -18,7 +18,7 @@ public FixedScopeQuery(final Scope scope, final JsonQuery query) {
}
@Override
- public List apply(final Scope _, final JsonNode in) throws JsonQueryException {
+ public List apply(final Scope unused, final JsonNode in) throws JsonQueryException {
return query.apply(scope, in);
}
}
\ No newline at end of file
diff --git a/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/IsolatedScopeQuery.java b/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/IsolatedScopeQuery.java
index c7dec195..48a2cb3e 100644
--- a/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/IsolatedScopeQuery.java
+++ b/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/IsolatedScopeQuery.java
@@ -17,7 +17,7 @@ public IsolatedScopeQuery(final JsonQuery q) {
@Override
public List apply(Scope scope, JsonNode in) throws JsonQueryException {
- final Scope isolatedScope = new Scope(scope);
+ final Scope isolatedScope = Scope.newChildScope(scope);
return q.apply(isolatedScope, in);
}
diff --git a/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/JsonQueryFunction.java b/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/JsonQueryFunction.java
index 40c903c6..281fd9db 100644
--- a/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/JsonQueryFunction.java
+++ b/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/JsonQueryFunction.java
@@ -25,15 +25,11 @@ public JsonQueryFunction(final String name, final List params, final Jso
this.closure = closure;
}
- public JsonQueryFunction(final String name, final List params, final JsonQuery body) {
- this(name, params, body, Scope.rootScope());
- }
-
@Override
public List apply(final Scope scope, final List args, final JsonNode in) throws JsonQueryException {
Preconditions.checkArgumentCount(name, args, params.size());
- final Scope fnScope = new Scope(closure);
+ final Scope fnScope = Scope.newChildScope(closure);
fnScope.addFunction(name, params.size(), this);
final List out = new ArrayList<>();
@@ -53,7 +49,7 @@ private void applyRecursive(final List out, final Scope fnScope, final
applyRecursive(out, fnScope, scope, args, in, i + 1);
}
} else {
- fnScope.addFunction(param, 0, new JsonQueryFunction(param, Collections. emptyList(), new FixedScopeQuery(scope, args.get(i))));
+ fnScope.addFunction(param, 0, new JsonQueryFunction(param, Collections. emptyList(), new FixedScopeQuery(scope, args.get(i)), fnScope));
applyRecursive(out, fnScope, scope, args, in, i + 1);
}
}
diff --git a/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/functions/JoinFunction.java b/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/functions/JoinFunction.java
new file mode 100644
index 00000000..74d681b2
--- /dev/null
+++ b/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/functions/JoinFunction.java
@@ -0,0 +1,54 @@
+package net.thisptr.jackson.jq.internal.functions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+
+import net.thisptr.jackson.jq.Function;
+import net.thisptr.jackson.jq.JsonQuery;
+import net.thisptr.jackson.jq.Scope;
+import net.thisptr.jackson.jq.exception.JsonQueryException;
+import net.thisptr.jackson.jq.exception.JsonQueryTypeException;
+import net.thisptr.jackson.jq.internal.BuiltinFunction;
+
+@BuiltinFunction("join/1")
+public class JoinFunction implements Function {
+ @Override
+ public List apply(final Scope scope, final List args, final JsonNode in) throws JsonQueryException {
+ final List out = new ArrayList<>();
+ for (final JsonNode sep : args.get(0).apply(scope, in)) {
+
+ JsonNode isep = null;
+ final StringBuilder builder = new StringBuilder();
+ for (final JsonNode item : in) {
+ if (isep != null) {
+ if (isep.isTextual()) {
+ builder.append(isep.asText());
+ } else if (isep.isNull()) {
+ // append nothing
+ } else {
+ throw new JsonQueryTypeException(new TextNode(builder.toString()), isep, "cannot be added");
+ }
+ }
+
+ if (item.isTextual()) {
+ builder.append(item.asText());
+ } else if (item.isNull()) {
+ // append nothing
+ } else if (item.isNumber() || item.isBoolean()) {
+ // https://github.com/stedolan/jq/commit/e17ccf229723d776c0d49341665256b855c70bda
+ // https://github.com/stedolan/jq/issues/930
+ builder.append(item.toString());
+ } else {
+ throw new JsonQueryTypeException(new TextNode(builder.toString()), item, "cannot be added");
+ }
+
+ isep = sep;
+ }
+ out.add(new TextNode(builder.toString()));
+ }
+ return out;
+ }
+}
diff --git a/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/tree/ForeachExpression.java b/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/tree/ForeachExpression.java
index 4ea798d0..1c0479b6 100644
--- a/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/tree/ForeachExpression.java
+++ b/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/tree/ForeachExpression.java
@@ -38,7 +38,7 @@ public List apply(final Scope scope, final JsonNode in) throws JsonQue
// Wrap in array to allow mutation inside lambda
final JsonNode[] accumulators = new JsonNode[] { accumulator };
- final Scope childScope = new Scope(scope);
+ final Scope childScope = Scope.newChildScope(scope);
for (final JsonNode item : iterExpr.apply(scope, in)) {
final Stack> stack = new Stack<>();
diff --git a/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/tree/PipedQuery.java b/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/tree/PipedQuery.java
index d6c7ce51..7505f15d 100644
--- a/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/tree/PipedQuery.java
+++ b/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/tree/PipedQuery.java
@@ -36,7 +36,7 @@ private static void applyRecursive(final Scope scope, final JsonNode in, final L
final PatternMatcher matcher = head._2;
final Scope scope2 = matcher != null
- ? new Scope(scope)
+ ? Scope.newChildScope(scope)
: scope;
for (final JsonNode o : q.apply(scope, in)) {
diff --git a/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/tree/ReduceExpression.java b/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/tree/ReduceExpression.java
index a8ec9f08..f2d2c82b 100644
--- a/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/tree/ReduceExpression.java
+++ b/jackson-jq/src/main/java/net/thisptr/jackson/jq/internal/tree/ReduceExpression.java
@@ -41,7 +41,7 @@ public List apply(final Scope scope, final JsonNode in) throws JsonQue
try {
- final Scope childScope = new Scope(scope);
+ final Scope childScope = Scope.newChildScope(scope);
for (final JsonNode item : iterExpr.apply(scope, in)) {
final Stack> stack = new Stack<>();
matcher.match(scope, item, (final List> vars) -> {
diff --git a/jackson-jq/src/main/javacc/json-query.jj b/jackson-jq/src/main/javacc/json-query.jj
index e36916d4..464dda29 100644
--- a/jackson-jq/src/main/javacc/json-query.jj
+++ b/jackson-jq/src/main/javacc/json-query.jj
@@ -89,7 +89,18 @@ TOKEN: {
}
TOKEN: { }
-TOKEN: { }
+
+// We have to be careful about spaces after ".". jq-1.4 or newer does not allow spaces between "." and in field accessors.
+// For example, '. foo' is an error while '.foo' is not. This distinction is important to parse "if . then . else empty end".
+// IDENTIFIER_AFTER_DOT and OTHERWISE_AFTER_DOT should not be re-ordered. The order matters.
+TOKEN: { : STATE_DOT }
+ TOKEN: {
+ > : DEFAULT
+}
+ MORE: {
+ : DEFAULT
+}
+
TOKEN: { }
TOKEN: { }
@@ -784,7 +795,7 @@ JsonQuery IdentifierFieldAccessor(JsonQuery obj):
}
{
- identifier =
+ identifier =
(
{ permissive = true; }
diff --git a/jackson-jq/src/main/resources/META-INF/services/net.thisptr.jackson.jq.Function b/jackson-jq/src/main/resources/META-INF/services/net.thisptr.jackson.jq.Function
index 27ae933a..de235fb5 100644
--- a/jackson-jq/src/main/resources/META-INF/services/net.thisptr.jackson.jq.Function
+++ b/jackson-jq/src/main/resources/META-INF/services/net.thisptr.jackson.jq.Function
@@ -20,6 +20,7 @@ net.thisptr.jackson.jq.internal.functions.IndicesFunction
net.thisptr.jackson.jq.internal.functions.InfiniteFunction
net.thisptr.jackson.jq.internal.functions.IsInfiniteFunction
net.thisptr.jackson.jq.internal.functions.IsNanFunction
+net.thisptr.jackson.jq.internal.functions.JoinFunction
net.thisptr.jackson.jq.internal.functions.KeysFunction
net.thisptr.jackson.jq.internal.functions.LengthFunction
net.thisptr.jackson.jq.internal.functions.LTrimStrFunction
diff --git a/jackson-jq/src/main/resources/net/thisptr/jackson/jq/jq.json b/jackson-jq/src/main/resources/net/thisptr/jackson/jq/jq.json
index f8d7299d..f602215a 100644
--- a/jackson-jq/src/main/resources/net/thisptr/jackson/jq/jq.json
+++ b/jackson-jq/src/main/resources/net/thisptr/jackson/jq/jq.json
@@ -47,7 +47,6 @@
{"name": "flatten", "args": [], "body": "_flatten(-1)"},
{"name": "flatten", "args": ["$x"], "body": "if $x < 0 then error(\"flatten depth must not be negative\") else _flatten($x) end"},
{"name": "_flatten", "args": ["$x"], "body": "reduce .[] as $i ([]; if $i | type == \"array\" and $x != 0 then . + ($i | _flatten($x-1)) else . + [$i] end)"},
- {"name": "join", "args": ["$x"], "body": "reduce .[] as $i (null; (if .==null then \"\" else .+$x end) + ($i | if type==\"boolean\" or type==\"number\" then tostring else .//\"\" end)) // \"\""},
{"name": "match", "args": ["re", "mode"], "body": "_match_impl(re; mode; false)|.[]"},
{"name": "match", "args": ["$val"], "body": "($val|type) as $vt | if $vt == \"string\" then match($val; null) elif $vt == \"array\" and ($val | length) > 1 then match($val[0]; $val[1]) elif $vt == \"array\" and ($val | length) > 0 then match($val[0]; null) else error( $vt + \" not a string or array\") end"},
{"name": "test", "args": ["re", "mode"], "body": "_match_impl(re; mode; true)"},
diff --git a/jackson-jq/src/test/java/net/thisptr/jackson/jq/internal/JsonQueryFunctionTest.java b/jackson-jq/src/test/java/net/thisptr/jackson/jq/internal/JsonQueryFunctionTest.java
index 83f97e08..4489eba3 100644
--- a/jackson-jq/src/test/java/net/thisptr/jackson/jq/internal/JsonQueryFunctionTest.java
+++ b/jackson-jq/src/test/java/net/thisptr/jackson/jq/internal/JsonQueryFunctionTest.java
@@ -7,7 +7,6 @@
import net.thisptr.jackson.jq.JsonQuery;
import net.thisptr.jackson.jq.Scope;
-import net.thisptr.jackson.jq.internal.JsonQueryFunction;
import org.junit.Test;
@@ -19,11 +18,13 @@
public class JsonQueryFunctionTest {
@Test
public void test() throws JsonProcessingException, IOException {
- final Scope scope = new Scope();
final ObjectMapper mapper = new ObjectMapper();
- scope.addFunction("inc", 1, new JsonQueryFunction("inc", Arrays.asList("x"), JsonQuery.compile("x + 1")));
- scope.addFunction("fib", 1, new JsonQueryFunction("fib", Arrays.asList("x"), JsonQuery.compile("if x == 0 then 0 elif x == 1 then 1 else fib(x-1) + fib(x-2) end")));
+ final Scope scope = Scope.newEmptyScope();
+ scope.loadFunctions(Scope.class.getClassLoader());
+
+ scope.addFunction("inc", 1, new JsonQueryFunction("inc", Arrays.asList("x"), JsonQuery.compile("x + 1"), scope));
+ scope.addFunction("fib", 1, new JsonQueryFunction("fib", Arrays.asList("x"), JsonQuery.compile("if x == 0 then 0 elif x == 1 then 1 else fib(x-1) + fib(x-2) end"), scope));
scope.addFunction("fib", 0, new JsonQueryFunction("fib", Arrays. asList(), JsonQuery.compile("fib(.)"), scope));
assertEquals(Arrays.asList(mapper.readTree("2")), JsonQuery.compile("inc(1)").apply(scope, NullNode.getInstance()));
diff --git a/jackson-jq/src/test/java/net/thisptr/jackson/jq/internal/javacc/JsonQueryParserTest.java b/jackson-jq/src/test/java/net/thisptr/jackson/jq/internal/javacc/JsonQueryParserTest.java
index 2e075501..a6cdbdbd 100644
--- a/jackson-jq/src/test/java/net/thisptr/jackson/jq/internal/javacc/JsonQueryParserTest.java
+++ b/jackson-jq/src/test/java/net/thisptr/jackson/jq/internal/javacc/JsonQueryParserTest.java
@@ -36,11 +36,15 @@ private static List loadQueries(final String fname) throws IOException,
public void testSupportedQueries() throws IOException, ParseException, TokenMgrError {
final List loadQueries = loadQueries("compiler-test-ok.txt");
for (int i = 0; i < loadQueries.size(); i++) {
- final JsonQuery jq = JsonQueryParser.compile(loadQueries.get(i));
- if (jq == null) {
- System.out.printf("%d: ---%n", i);
- } else {
- System.out.printf("%d: %s%n", i, jq);
+ try {
+ final JsonQuery jq = JsonQueryParser.compile(loadQueries.get(i));
+ if (jq == null) {
+ System.out.printf("%d: ---%n", i);
+ } else {
+ System.out.printf("%d: %s%n", i, jq);
+ }
+ } catch (final Throwable th) {
+ throw new RuntimeException("Failed to compile \"" + loadQueries.get(i) + "\"", th);
}
}
}
diff --git a/jackson-jq/src/test/resources/jq-test-extra-ok.json b/jackson-jq/src/test/resources/jq-test-extra-ok.json
index 994de911..7c1ca82c 100644
--- a/jackson-jq/src/test/resources/jq-test-extra-ok.json
+++ b/jackson-jq/src/test/resources/jq-test-extra-ok.json
@@ -712,5 +712,12 @@
{
"q": "try ((error(\"foo\"))?) catch .",
"out": []
+ },
+ {
+ "q": ".foreach",
+ "in": {"foreach": 10},
+ "out": [
+ 10
+ ]
}
]
diff --git a/pom.xml b/pom.xml
index 57c7cbb8..419fb855 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
net.thisptr
jackson-jq-parent
pom
- 0.0.8
+ 0.0.9
${project.groupId}:${project.artifactId}
jq for Jackson JSON Processor
https://github.com/eiiches/jackson-jq
@@ -28,7 +28,7 @@
scm:git:git@github.com:eiiches/jackson-jq.git
scm:git:git@github.com:eiiches/jackson-jq.git
git@github.com:juven/git-demo.git
- jackson-jq-parent-0.0.8
+ jackson-jq-parent-0.0.9