diff --git a/README.md b/README.md index 6d6f124a..bd15dd04 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Just add jackson-jq in your pom.xml. net.thisptr jackson-jq - 0.0.8 + 0.0.9 ``` @@ -104,6 +104,7 @@ Here is a *current* status of differences between jackson-jq and the jq. If you - [Modules](https://stedolan.github.io/jq/manual/#Modules) - [Streaming](https://stedolan.github.io/jq/manual/#Streaming) - [I/O](https://stedolan.github.io/jq/manual/#IO) + - `{$foo}` syntax, a syntactic sugar for `{foo:$foo}` (#24) - Missing functions in jackson-jq - Datetime functions: `fromdate/0`, `mktime/0`, `gmtime/0` @@ -141,7 +142,7 @@ Using jackson-jq-extra module net.thisptr jackson-jq-extra - 0.0.8 + 0.0.9 ``` diff --git a/jackson-jq-cli/pom.xml b/jackson-jq-cli/pom.xml index 8e9829b7..a2d06885 100644 --- a/jackson-jq-cli/pom.xml +++ b/jackson-jq-cli/pom.xml @@ -8,14 +8,14 @@ net.thisptr jackson-jq-parent - 0.0.8 + 0.0.9 net.thisptr jackson-jq-extra - 0.0.8 + 0.0.9 commons-cli diff --git a/jackson-jq-extra/pom.xml b/jackson-jq-extra/pom.xml index d0cd3585..b7e765ef 100644 --- a/jackson-jq-extra/pom.xml +++ b/jackson-jq-extra/pom.xml @@ -8,14 +8,14 @@ net.thisptr jackson-jq-parent - 0.0.8 + 0.0.9 net.thisptr jackson-jq - 0.0.8 + 0.0.9 diff --git a/jackson-jq-extra/src/test/java/net/thisptr/jackson/jq/extra/functions/UriParseFunctionTest.java b/jackson-jq-extra/src/test/java/net/thisptr/jackson/jq/extra/functions/UriParseFunctionTest.java index 9dae06e2..bcc916ab 100644 --- a/jackson-jq-extra/src/test/java/net/thisptr/jackson/jq/extra/functions/UriParseFunctionTest.java +++ b/jackson-jq-extra/src/test/java/net/thisptr/jackson/jq/extra/functions/UriParseFunctionTest.java @@ -14,7 +14,9 @@ public class UriParseFunctionTest { @Test public void test() throws JsonQueryException { + final Scope scope = Scope.newEmptyScope(); + scope.loadFunctions(Scope.class.getClassLoader()); // check this does not throw NPE - new UriParseFunction().apply(new Scope(), Collections. emptyList(), new TextNode("http://google.com")); + new UriParseFunction().apply(scope, Collections. emptyList(), new TextNode("http://google.com")); } } diff --git a/jackson-jq/pom.xml b/jackson-jq/pom.xml index 59e341dc..9af7bfc9 100644 --- a/jackson-jq/pom.xml +++ b/jackson-jq/pom.xml @@ -8,7 +8,7 @@ net.thisptr jackson-jq-parent - 0.0.8 + 0.0.9 diff --git a/jackson-jq/src/main/java/net/thisptr/jackson/jq/JsonQuery.java b/jackson-jq/src/main/java/net/thisptr/jackson/jq/JsonQuery.java index f9c6945f..ed2c753b 100644 --- a/jackson-jq/src/main/java/net/thisptr/jackson/jq/JsonQuery.java +++ b/jackson-jq/src/main/java/net/thisptr/jackson/jq/JsonQuery.java @@ -6,16 +6,17 @@ import net.thisptr.jackson.jq.exception.JsonQueryException; import net.thisptr.jackson.jq.internal.IsolatedScopeQuery; import net.thisptr.jackson.jq.internal.javacc.JsonQueryParser; -import net.thisptr.jackson.jq.internal.javacc.ParseException; import net.thisptr.jackson.jq.internal.javacc.TokenMgrError; import com.fasterxml.jackson.databind.JsonNode; public abstract class JsonQuery { + @Deprecated private static Scope defaultScope = new Scope(); public abstract List apply(final Scope scope, final JsonNode in) throws JsonQueryException; + @Deprecated public List apply(final JsonNode in) throws JsonQueryException { return apply(defaultScope, in); } @@ -27,6 +28,7 @@ public List apply(final Scope scope, final List in) throws J return out; } + @Deprecated public List apply(final List in) throws JsonQueryException { return apply(defaultScope, in); } @@ -34,7 +36,7 @@ public List apply(final List in) throws JsonQueryException { public static JsonQuery compile(final String path) throws JsonQueryException { try { return new IsolatedScopeQuery(JsonQueryParser.compile(path)); - } catch (final TokenMgrError | ParseException e) { + } catch (final TokenMgrError | Exception e) { throw new JsonQueryException(e); } } diff --git a/jackson-jq/src/main/java/net/thisptr/jackson/jq/JsonQueryUtils.java b/jackson-jq/src/main/java/net/thisptr/jackson/jq/JsonQueryUtils.java index 3d11b960..f33e25bc 100644 --- a/jackson-jq/src/main/java/net/thisptr/jackson/jq/JsonQueryUtils.java +++ b/jackson-jq/src/main/java/net/thisptr/jackson/jq/JsonQueryUtils.java @@ -11,8 +11,10 @@ public class JsonQueryUtils { private JsonQueryUtils() {} + @Deprecated private static Scope defaultScope = new Scope(); + @Deprecated public static List apply(final JsonQuery jq, final Object in, final Class resultType) throws IOException { return apply(defaultScope, jq, in, resultType); } @@ -21,6 +23,7 @@ public static List apply(final Scope scope, final JsonQuery jq, final Obj return map(scope.getObjectMapper(), jq.apply(scope, (JsonNode) scope.getObjectMapper().valueToTree(in)), resultType); } + @Deprecated public static List apply(final JsonQuery jq, final Object in, final TypeReference resultType) throws IOException { return apply(defaultScope, jq, in, resultType); } diff --git a/jackson-jq/src/main/java/net/thisptr/jackson/jq/Scope.java b/jackson-jq/src/main/java/net/thisptr/jackson/jq/Scope.java index 1a75f5ca..847cf679 100644 --- a/jackson-jq/src/main/java/net/thisptr/jackson/jq/Scope.java +++ b/jackson-jq/src/main/java/net/thisptr/jackson/jq/Scope.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.MappingIterator; import com.fasterxml.jackson.databind.ObjectMapper; + import net.thisptr.jackson.jq.exception.JsonQueryException; import net.thisptr.jackson.jq.internal.BuiltinFunction; import net.thisptr.jackson.jq.internal.JsonQueryFunction; @@ -75,11 +76,11 @@ private Map 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