diff --git a/core/src/main/java/com/schibsted/spt/data/jslt/impl/BuiltinFunctions.java b/core/src/main/java/com/schibsted/spt/data/jslt/impl/BuiltinFunctions.java index b903cbe..f984ee9 100644 --- a/core/src/main/java/com/schibsted/spt/data/jslt/impl/BuiltinFunctions.java +++ b/core/src/main/java/com/schibsted/spt/data/jslt/impl/BuiltinFunctions.java @@ -138,6 +138,7 @@ public class BuiltinFunctions { public static Map macros = new HashMap(); static { macros.put("fallback", new BuiltinFunctions.Fallback()); + macros.put("filter", new BuiltinFunctions.Filter()); } private static abstract class AbstractMacro extends AbstractCallable implements Macro { @@ -599,6 +600,33 @@ public JsonNode call(Scope scope, JsonNode input, } } + // ===== FILTER + + public static class Filter extends AbstractMacro { + + public Filter() { + super("filter", 2, 2); + } + + public JsonNode call(Scope scope, JsonNode input, + ExpressionNode[] parameters) { + JsonNode array = parameters[0].apply(scope, input); + if (array.isNull()) + return NullNode.instance; + if (!array.isArray()) + throw new JsltException("filter() argument is not an array: " + array); + + ArrayNode result = NodeUtils.mapper.createArrayNode(); + for (int ix = 0; ix < array.size(); ix++) { + JsonNode element = array.get(ix); + JsonNode test = parameters[1].apply(scope, element); + if (NodeUtils.isTrue(test)) + result.add(element); + } + return result; + } + } + // ===== IS-OBJECT public static class IsObject extends AbstractFunction { diff --git a/core/src/test/java/com/schibsted/spt/data/jslt/QueryErrorTest.java b/core/src/test/java/com/schibsted/spt/data/jslt/QueryErrorTest.java index 078044f..1fdfbb5 100644 --- a/core/src/test/java/com/schibsted/spt/data/jslt/QueryErrorTest.java +++ b/core/src/test/java/com/schibsted/spt/data/jslt/QueryErrorTest.java @@ -60,6 +60,7 @@ public static Collection data() { List strings = new ArrayList(); strings.addAll(loadTests("query-error-tests.json")); strings.addAll(loadTests("function-error-tests.json")); + strings.addAll(loadTests("filter-error-tests.json")); strings.addAll(loadTests("function-declaration-tests.yaml")); return strings; } diff --git a/core/src/test/java/com/schibsted/spt/data/jslt/QueryTest.java b/core/src/test/java/com/schibsted/spt/data/jslt/QueryTest.java index 221a803..23f2477 100644 --- a/core/src/test/java/com/schibsted/spt/data/jslt/QueryTest.java +++ b/core/src/test/java/com/schibsted/spt/data/jslt/QueryTest.java @@ -67,6 +67,7 @@ public static Collection data() { strings.addAll(loadTests("query-tests.yaml")); strings.addAll(loadTests("function-tests.json")); strings.addAll(loadTests("experimental-tests.json")); + strings.addAll(loadTests("filter-tests.json")); strings.addAll(loadTests("function-declaration-tests.yaml")); return strings; } diff --git a/core/src/test/resources/filter-error-tests.json b/core/src/test/resources/filter-error-tests.json new file mode 100644 index 0000000..7249693 --- /dev/null +++ b/core/src/test/resources/filter-error-tests.json @@ -0,0 +1,11 @@ +{ + "description" : "Error tests for the filter() macro.", + "tests" : +[ + { + "query" : "filter(42, .active)", + "input" : "{}", + "error" : "filter() argument is not an array" + } +] +} diff --git a/core/src/test/resources/filter-tests.json b/core/src/test/resources/filter-tests.json new file mode 100644 index 0000000..b625b30 --- /dev/null +++ b/core/src/test/resources/filter-tests.json @@ -0,0 +1,36 @@ +{ + "description" : "Tests for the filter() macro.", + "tests" : +[ + { + "query" : "filter([{\"a\":1,\"active\":true},{\"a\":2,\"active\":false}], .active)", + "input" : "{}", + "output" : "[{\"a\":1,\"active\":true}]" + }, + { + "query" : "filter([1, 2, 3, 4, 5], . > 3)", + "input" : "{}", + "output" : "[4, 5]" + }, + { + "query" : "filter(null, .active)", + "input" : "{}", + "output" : "null" + }, + { + "query" : "filter([], .active)", + "input" : "{}", + "output" : "[]" + }, + { + "query" : "[for (filter([{\"id\":1,\"ok\":true},{\"id\":2,\"ok\":false}], .ok)) .id]", + "input" : "{}", + "output" : "[1]" + }, + { + "query" : "size(filter([{\"active\":true},{\"active\":false},{\"active\":true}], .active))", + "input" : "{}", + "output" : "2" + } +] +} diff --git a/functions.md b/functions.md index 3065ce0..c34f441 100644 --- a/functions.md +++ b/functions.md @@ -58,6 +58,24 @@ if (not(is-array(.things))) error("'things' is not an array") ``` +### _filter(array, expr) -> array_ + +Returns a new array containing only the elements of _array_ for which +_expr_ evaluates to a truthy value. _expr_ is evaluated with each +element as the context node (`.`). + +If _array_ is `null` the result is `null`. If _array_ is empty the +result is `[]`. + +Examples: + +``` +filter([1, 2, 3, 4, 5], . > 3) => [4, 5] +filter(.items, .active) => [{...}, ...] +size(filter(.items, .active)) => 2 +[for (filter(.items, .active)) .id] => [...] +``` + ### _fallback(arg1, arg2, ...) -> value_ Returns the first argument that has a value. That is, the first