This is an automated email from the ASF dual-hosted git repository.

rmannibucau pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/johnzon.git


The following commit(s) were added to refs/heads/master by this push:
     new 51050c4  [JOHNZON-313] implement json-logic based on JSON-P
51050c4 is described below

commit 51050c423d3ee18415aa9ec4d4cc820a5e63e6d4
Author: Romain Manni-Bucau <rmannibu...@gmail.com>
AuthorDate: Tue May 26 12:20:21 2020 +0200

    [JOHNZON-313] implement json-logic based on JSON-P
---
 johnzon-jsonlogic/pom.xml                          |  40 +
 .../apache/johnzon/jsonlogic/JohnzonJsonLogic.java | 665 ++++++++++++++
 .../org/apache/johnzon/jsonlogic/spi/Operator.java |  28 +
 .../johnzon/jsonlogic/JohnzonJsonLogicTest.java    | 982 +++++++++++++++++++++
 pom.xml                                            |   1 +
 src/site/markdown/index.md                         |  42 +
 6 files changed, 1758 insertions(+)

diff --git a/johnzon-jsonlogic/pom.xml b/johnzon-jsonlogic/pom.xml
new file mode 100644
index 0000000..95dfdfa
--- /dev/null
+++ b/johnzon-jsonlogic/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <parent>
+    <artifactId>johnzon</artifactId>
+    <groupId>org.apache.johnzon</groupId>
+    <version>1.2.7-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>johnzon-jsonlogic</artifactId>
+  <name>Johnzon :: JSON Logic</name>
+  <packaging>bundle</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.johnzon</groupId>
+      <artifactId>johnzon-core</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git 
a/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/JohnzonJsonLogic.java
 
b/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/JohnzonJsonLogic.java
new file mode 100644
index 0000000..d77c878
--- /dev/null
+++ 
b/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/JohnzonJsonLogic.java
@@ -0,0 +1,665 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.johnzon.jsonlogic;
+
+import org.apache.johnzon.jsonlogic.spi.Operator;
+
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonBuilderFactory;
+import javax.json.JsonException;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonPointer;
+import javax.json.JsonString;
+import javax.json.JsonStructure;
+import javax.json.JsonValue;
+import javax.json.spi.JsonProvider;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.BiPredicate;
+import java.util.stream.Collector;
+import java.util.stream.DoubleStream;
+import java.util.stream.Stream;
+
+import static java.util.Collections.emptyMap;
+import static java.util.stream.Collectors.joining;
+
+public class JohnzonJsonLogic {
+    private final JsonProvider provider;
+    private final Map<String, Operator> operators = new HashMap<>();
+    private final Map<String, JsonPointer> pointers = new HashMap<>();
+    private final JsonBuilderFactory builderFactory;
+    private boolean cachePointers;
+
+    public JohnzonJsonLogic() {
+        this(JsonProvider.provider());
+        registerDefaultOperators();
+    }
+
+    public JohnzonJsonLogic(final JsonProvider provider) {
+        this.provider = provider;
+        this.builderFactory = provider.createBuilderFactory(emptyMap());
+    }
+
+    public JohnzonJsonLogic cachePointers() {
+        this.cachePointers = true;
+        return this;
+    }
+
+    public JohnzonJsonLogic registerOperator(final String name, final Operator 
impl) {
+        operators.put(name, impl);
+        return this;
+    }
+
+    public JsonValue apply(final JsonValue logic, final JsonValue args) {
+        if (logic.getValueType() != JsonValue.ValueType.OBJECT) {
+            return logic;
+        }
+
+        final JsonObject object = logic.asJsonObject();
+        if (object.size() > 1) {
+            return object;
+        }
+
+        final Set<String> keys = object.keySet();
+        if (keys.size() != 1) {
+            throw new IllegalArgumentException("Invaid argument, multiple keys 
found: " + keys);
+        }
+        final String operator = keys.iterator().next();
+        final Operator impl = operators.get(operator);
+        if (impl == null) {
+            throw new IllegalArgumentException("Missing operator '" + operator 
+ "'");
+        }
+        return impl.apply(this, object.get(operator), args);
+    }
+
+    public boolean isTruthy(final JsonValue value) {
+        return !isFalsy(value);
+    }
+
+    public boolean isFalsy(final JsonValue value) {
+        switch (value.getValueType()) {
+            case NUMBER:
+                return JsonNumber.class.cast(value).intValue() == 0;
+            case ARRAY:
+                return value.asJsonArray().isEmpty();
+            case STRING:
+                return JsonString.class.cast(value).getString().isEmpty();
+            case FALSE:
+            case NULL:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    public boolean areEqualsWithCoercion(final JsonValue a, final JsonValue b) 
{
+        if (a == b) {
+            return true;
+        }
+        if (a == null) {
+            return false;
+        }
+        if (b == null) {
+            return false;
+        }
+        if (a.getValueType() == b.getValueType()) {
+            return a.equals(b);
+        }
+        switch (a.getValueType()) {
+            case STRING:
+                switch (b.getValueType()) {
+                    case NUMBER:
+                        try {
+                            return 
Double.parseDouble(JsonString.class.cast(a).getString()) == 
JsonNumber.class.cast(b).doubleValue();
+                        } catch (final NumberFormatException nfe) {
+                            return false;
+                        }
+                    case TRUE:
+                    case FALSE:
+                        return isFalsy(a) == isFalsy(b);
+                    default:
+                        return false;
+                }
+            case NUMBER:
+                switch (b.getValueType()) {
+                    case STRING:
+                        try {
+                            return 
Double.parseDouble(JsonString.class.cast(b).getString()) == 
JsonNumber.class.cast(a).doubleValue();
+                        } catch (final NumberFormatException nfe) {
+                            return false;
+                        }
+                    case TRUE:
+                    case FALSE:
+                    default:
+                        return isFalsy(a) == isFalsy(b);
+                }
+            case TRUE:
+            case FALSE:
+                return isFalsy(a) == isFalsy(b);
+            default:
+                return false;
+        }
+    }
+
+    // to not depend on a logger we don't register "log" operation but it is 
trivial to do:
+    public JohnzonJsonLogic registerDefaultOperators() {
+        registerOperator("log", (logic, config, params) -> {
+            throw new UnsupportedOperationException("Log is not supported by 
default, register the following operator with your preferred logger:\n\n" +
+                    "jsonLogic.registerOperator(\"log\", (l, c, p) -> 
log.info(String.valueOf(l.apply(c, p)));\n");
+        });
+        registerOperator("var", (logic, config, params) -> varImpl(config, 
params));
+        registerOperator("missing", this::missingImpl);
+        registerOperator("missing_some", this::missingSomeImpl);
+        registerOperator("if", this::ifImpl);
+        registerOperator("<", (logic, config, params) -> numericComparison((a, 
b) -> a < b, config, logic, params));
+        registerOperator(">", (logic, config, params) -> numericComparison((a, 
b) -> a > b, config, logic, params));
+        registerOperator("<=", (logic, config, params) -> 
numericComparison((a, b) -> a <= b, config, logic, params));
+        registerOperator(">=", (logic, config, params) -> 
numericComparison((a, b) -> a >= b, config, logic, params));
+        registerOperator("==", (logic, config, params) -> 
comparison(this::areEqualsWithCoercion, config, logic, params));
+        registerOperator("!=", (logic, config, params) -> comparison((a, b) -> 
!areEqualsWithCoercion(a, b), config, logic, params));
+        registerOperator("===", (logic, config, params) -> 
comparison(Objects::equals, config, logic, params));
+        registerOperator("!==", (logic, config, params) -> comparison((a, b) 
-> !Objects.equals(a, b), config, logic, params));
+        registerOperator("!", this::notImpl);
+        registerOperator("!!", this::toBooleanImpl);
+        registerOperator("or", this::orImpl);
+        registerOperator("and", this::andImpl);
+        registerOperator("min", this::minImpl);
+        registerOperator("max", this::maxImpl);
+        registerOperator("+", this::plusImpl);
+        registerOperator("*", this::multiplyImpl);
+        registerOperator("-", this::minusImpl);
+        registerOperator("/", this::divideImpl);
+        registerOperator("%", this::moduloImpl);
+        registerOperator("map", this::mapImpl);
+        registerOperator("filter", this::filterImpl);
+        registerOperator("reduce", this::reduceImpl);
+        registerOperator("all", (logic, config, params) ->
+                arrayTest(logic, config, params, (subConf, stream) -> 
stream.allMatch(it -> isTruthy(logic.apply(subConf, it)))));
+        registerOperator("some", (logic, config, params) ->
+                arrayTest(logic, config, params, (subConf, stream) -> 
stream.anyMatch(it -> isTruthy(logic.apply(subConf, it)))));
+        registerOperator("none", (logic, config, params) ->
+                arrayTest(logic, config, params, (subConf, stream) -> 
stream.noneMatch(it -> isTruthy(logic.apply(subConf, it)))));
+        registerOperator("merge", (logic, config, params) -> 
mergeImpl(config));
+        registerOperator("in", this::inImpl);
+        registerOperator("cat", this::catImpl);
+        registerOperator("substr", this::substrImpl);
+        return this;
+    }
+
+    private JsonValue minImpl(final JohnzonJsonLogic logic, final JsonValue 
config, final JsonValue params) {
+        if (config.getValueType() != JsonValue.ValueType.ARRAY) {
+            throw new IllegalArgumentException("min only supports arrays: '" + 
config + "'");
+        }
+        return provider.createValue(mapToDouble(logic, config, 
params).min().orElse(0));
+    }
+
+    private JsonValue maxImpl(final JohnzonJsonLogic logic, final JsonValue 
config, final JsonValue params) {
+        if (config.getValueType() != JsonValue.ValueType.ARRAY) {
+            throw new IllegalArgumentException("max only supports arrays: '" + 
config + "'");
+        }
+        return provider.createValue(mapToDouble(logic, config, 
params).max().orElse(0));
+    }
+
+    private JsonValue plusImpl(final JohnzonJsonLogic logic, final JsonValue 
config, final JsonValue params) {
+        if (config.getValueType() != JsonValue.ValueType.ARRAY) {
+            return castToNumber(logic.apply(config, params));
+        }
+        if (config.asJsonArray().isEmpty()) {
+            return provider.createValue(0);
+        }
+        return provider.createValue(mapToDouble(logic, config, params).sum());
+    }
+
+    private JsonValue multiplyImpl(final JohnzonJsonLogic logic, final 
JsonValue config, final JsonValue params) {
+        if (config.getValueType() != JsonValue.ValueType.ARRAY) {
+            throw new IllegalArgumentException("* only supports arrays: '" + 
config + "'");
+        }
+        if (config.asJsonArray().isEmpty()) {
+            return provider.createValue(0);
+        }
+        return provider.createValue(mapToDouble(logic, config, params)
+                .reduce(1, (a, b) -> a * b));
+    }
+
+    private JsonValue minusImpl(final JohnzonJsonLogic logic, final JsonValue 
config, final JsonValue params) {
+        if (config.getValueType() == JsonValue.ValueType.ARRAY) {
+            final JsonArray array = config.asJsonArray();
+            if (array.size() != 2) {
+                throw new IllegalArgumentException("- only supports arrays 
with 2 elements: '" + config + "'");
+            }
+            return 
provider.createValue(JsonNumber.class.cast(logic.apply(array.get(0), 
params)).doubleValue() -
+                    JsonNumber.class.cast(logic.apply(array.get(1), 
params)).doubleValue());
+        }
+        final JsonValue applied = logic.apply(config, params);
+        if (applied.getValueType() == JsonValue.ValueType.NUMBER) {
+            return provider.createValue(-1 * 
JsonNumber.class.cast(applied).doubleValue());
+        }
+        throw new IllegalArgumentException("Unsupported - operation: '" + 
config + "'");
+    }
+
+    private JsonValue divideImpl(final JohnzonJsonLogic logic, final JsonValue 
config, final JsonValue params) {
+        if (config.getValueType() == JsonValue.ValueType.ARRAY) {
+            final JsonArray array = config.asJsonArray();
+            if (array.size() != 2) {
+                throw new IllegalArgumentException("/ only supports arrays 
with 2 elements: '" + config + "'");
+            }
+            return 
provider.createValue(JsonNumber.class.cast(logic.apply(array.get(0), 
params)).doubleValue() /
+                    JsonNumber.class.cast(logic.apply(array.get(1), 
params)).doubleValue());
+        }
+        throw new IllegalArgumentException("Unsupported / operation: '" + 
config + "'");
+    }
+
+    private JsonValue moduloImpl(final JohnzonJsonLogic logic, final JsonValue 
config, final JsonValue params) {
+        if (config.getValueType() == JsonValue.ValueType.ARRAY) {
+            final JsonArray array = config.asJsonArray();
+            if (array.size() != 2) {
+                throw new IllegalArgumentException("% only supports arrays 
with 2 elements: '" + config + "'");
+            }
+            return 
provider.createValue(JsonNumber.class.cast(logic.apply(array.get(0), 
params)).doubleValue() %
+                    JsonNumber.class.cast(logic.apply(array.get(1), 
params)).doubleValue());
+        }
+        throw new IllegalArgumentException("Unsupported % operation: '" + 
config + "'");
+    }
+
+    private JsonValue mapImpl(final JohnzonJsonLogic logic, final JsonValue 
config, final JsonValue params) {
+        if (config.getValueType() == JsonValue.ValueType.ARRAY) {
+            final JsonArray array = config.asJsonArray();
+            if (array.size() != 2) {
+                throw new IllegalArgumentException("map only supports arrays 
with 2 elements: '" + config + "'");
+            }
+            final JsonValue items = logic.apply(array.get(0), params);
+            if (items.getValueType() != JsonValue.ValueType.ARRAY) {
+                throw new IllegalArgumentException("Expected '" + array.get(0) 
+ "' to be an array, got " + items.getValueType());
+            }
+            final JsonValue subLogic = array.get(1);
+            return items.asJsonArray().stream()
+                    .map(it -> logic.apply(subLogic, it))
+                    .collect(toArray());
+        }
+        throw new IllegalArgumentException("Unsupported map operation: '" + 
config + "'");
+    }
+
+    private JsonValue filterImpl(final JohnzonJsonLogic logic, final JsonValue 
config, final JsonValue params) {
+        if (config.getValueType() == JsonValue.ValueType.ARRAY) {
+            final JsonArray array = config.asJsonArray();
+            if (array.size() != 2) {
+                throw new IllegalArgumentException("filter only supports 
arrays with 2 elements: '" + config + "'");
+            }
+            final JsonValue items = logic.apply(array.get(0), params);
+            if (items.getValueType() != JsonValue.ValueType.ARRAY) {
+                throw new IllegalArgumentException("Expected '" + array.get(0) 
+ "' to be an array, got " + items.getValueType());
+            }
+            final JsonValue subLogic = array.get(1);
+            return items.asJsonArray().stream()
+                    .filter(it -> isTruthy(logic.apply(subLogic, it)))
+                    .collect(toArray());
+        }
+        throw new IllegalArgumentException("Unsupported filter operation: '" + 
config + "'");
+    }
+
+    private JsonValue mergeImpl(final JsonValue config) {
+        if (config.getValueType() != JsonValue.ValueType.ARRAY) {
+            throw new IllegalArgumentException("merge only support an array as 
configuration, got '" + config + "'");
+        }
+        return config.asJsonArray().stream()
+                .flatMap(it -> it.getValueType() == JsonValue.ValueType.ARRAY ?
+                        it.asJsonArray().stream() : 
builderFactory.createArrayBuilder().add(it).build().stream())
+                .collect(toArray());
+    }
+
+    private JsonValue substrImpl(final JohnzonJsonLogic logic, final JsonValue 
config, final JsonValue params) {
+        if (config.getValueType() != JsonValue.ValueType.ARRAY || 
config.asJsonArray().size() < 2) {
+            throw new IllegalArgumentException("substr only support an array 
as configuration, got '" + config + "'");
+        }
+        final JsonArray array = config.asJsonArray();
+        final JsonValue value = logic.apply(array.get(0), params);
+        if (value.getValueType() != JsonValue.ValueType.STRING) {
+            throw new IllegalArgumentException("expected a string for substr, 
got '" + value + "'");
+        }
+        final String valueStr = JsonString.class.cast(value).getString();
+        final JsonValue from = logic.apply(array.get(1), params);
+        if (from.getValueType() != JsonValue.ValueType.NUMBER) {
+            throw new IllegalArgumentException("expected a number for substr, 
got '" + from + "'");
+        }
+        final int fromIdx = JsonNumber.class.cast(from).intValue();
+        final int start;
+        if (fromIdx < 0) {
+            start = valueStr.length() + fromIdx;
+        } else {
+            start = fromIdx;
+        }
+        final int end;
+        if (array.size() == 3) {
+            final JsonValue to = logic.apply(array.get(2), params);
+            if (to.getValueType() != JsonValue.ValueType.NUMBER) {
+                throw new IllegalArgumentException("expected a number for 
substr, got '" + to + "'");
+            }
+            final int length = JsonNumber.class.cast(to).intValue();
+            end = length < 0 ? valueStr.length() + length : start + length;
+        } else {
+            end = valueStr.length();
+        }
+        return provider.createValue(valueStr.substring(start, end));
+    }
+
+    private JsonValue catImpl(final JohnzonJsonLogic logic, final JsonValue 
config, final JsonValue params) {
+        if (config.getValueType() != JsonValue.ValueType.ARRAY) {
+            throw new IllegalArgumentException("cat only support an array of 
string elements as configuration, got '" + config + "'");
+        }
+        return provider.createValue(config.asJsonArray().stream()
+                .map(it -> logic.apply(it, params))
+                .filter(it -> it.getValueType() == JsonValue.ValueType.STRING)
+                .map(it -> JsonString.class.cast(it).getString())
+                .collect(joining()));
+    }
+
+    private JsonValue inImpl(final JohnzonJsonLogic logic, final JsonValue 
config, final JsonValue params) {
+        if (config.getValueType() != JsonValue.ValueType.ARRAY || 
config.asJsonArray().size() != 2) {
+            throw new IllegalArgumentException("in only support an array of 2 
elements as configuration, got '" + config + "'");
+        }
+        final JsonArray array = config.asJsonArray();
+        final JsonValue expected = logic.apply(array.get(0), params);
+        final JsonValue value = logic.apply(array.get(1), params);
+        switch (value.getValueType()) {
+            case STRING:
+                return expected.getValueType() == JsonValue.ValueType.STRING 
&& JsonString.class.cast(value).getString()
+                        .contains(JsonString.class.cast(expected).getString()) 
? JsonValue.TRUE : JsonValue.FALSE;
+            case ARRAY:
+                return value.getValueType() == JsonValue.ValueType.ARRAY && 
value.asJsonArray().stream()
+                        .anyMatch(it -> Objects.equals(it, expected)) ? 
JsonValue.TRUE : JsonValue.FALSE;
+            default:
+                return JsonValue.FALSE;
+        }
+    }
+
+    private JsonValue reduceImpl(final JohnzonJsonLogic logic, final JsonValue 
config, final JsonValue params) {
+        if (config.getValueType() == JsonValue.ValueType.ARRAY) {
+            final JsonArray array = config.asJsonArray();
+            if (array.size() < 2 || array.size() > 3) {
+                throw new IllegalArgumentException("filter only supports 
arrays with 2 or 3 elements: '" + config + "'");
+            }
+            final JsonValue items = logic.apply(array.get(0), params);
+            if (items.getValueType() != JsonValue.ValueType.ARRAY) {
+                throw new IllegalArgumentException("Expected '" + array.get(0) 
+ "' to be an array, got " + items.getValueType());
+            }
+            final JsonValue subLogic = array.get(1);
+            return items.asJsonArray().stream()
+                    .reduce(
+                            array.size() == 3 ? array.get(2) : JsonValue.NULL,
+                            (accumulator, current) -> logic.apply(subLogic, 
builderFactory.createObjectBuilder()
+                                    .add("accumulator", accumulator)
+                                    .add("current", current)
+                                    .build()));
+        }
+        throw new IllegalArgumentException("Unsupported reduce operation: '" + 
config + "'");
+    }
+
+    private JsonValue andImpl(final JohnzonJsonLogic logic, final JsonValue 
config, final JsonValue params) {
+        if (config.getValueType() != JsonValue.ValueType.ARRAY) {
+            throw new IllegalArgumentException("and only supports arrays: '" + 
config + "'");
+        }
+        final JsonArray array = config.asJsonArray();
+        return array.stream()
+                .map(it -> logic.apply(it, params))
+                .filter(this::isFalsy)
+                .findFirst()
+                .orElseGet(() -> array.isEmpty() ? JsonValue.FALSE : 
array.get(array.size() - 1));
+    }
+
+    private JsonValue orImpl(final JohnzonJsonLogic logic, final JsonValue 
config, final JsonValue params) {
+        if (config.getValueType() != JsonValue.ValueType.ARRAY) {
+            throw new IllegalArgumentException("or only supports arrays: '" + 
config + "'");
+        }
+        final JsonArray array = config.asJsonArray();
+        return array.stream()
+                .map(it -> logic.apply(it, params))
+                .filter(this::isTruthy)
+                .findFirst()
+                .orElseGet(() -> array.isEmpty() ? JsonValue.FALSE : 
array.get(array.size() - 1));
+    }
+
+    private JsonValue toBooleanImpl(final JohnzonJsonLogic logic, final 
JsonValue config, final JsonValue params) {
+        if (config.getValueType() == JsonValue.ValueType.ARRAY) {
+            final JsonArray array = config.asJsonArray();
+            if (array.size() != 1) {
+                throw new IllegalArgumentException("!! takes only one 
parameter '" + config + "'");
+            }
+            return isTruthy(logic.apply(array.get(0), params)) ? 
JsonValue.TRUE : JsonValue.FALSE;
+        }
+        return isTruthy(logic.apply(config, params)) ? JsonValue.TRUE : 
JsonValue.FALSE;
+    }
+
+    private JsonValue notImpl(final JohnzonJsonLogic logic, final JsonValue 
config, final JsonValue params) {
+        if (config.getValueType() == JsonValue.ValueType.ARRAY) {
+            final JsonArray array = config.asJsonArray();
+            if (array.size() != 1) {
+                throw new IllegalArgumentException("! takes only one parameter 
'" + config + "'");
+            }
+            return isFalsy(logic.apply(array.get(0), params)) ? JsonValue.TRUE 
: JsonValue.FALSE;
+        }
+        return isFalsy(logic.apply(config, params)) ? JsonValue.TRUE : 
JsonValue.FALSE;
+    }
+
+    private JsonValue ifImpl(final JohnzonJsonLogic logic, final JsonValue 
config, final JsonValue params) {
+        if (config.getValueType() != JsonValue.ValueType.ARRAY) {
+            throw new IllegalArgumentException("if config must be an array");
+        }
+        final JsonArray configArray = config.asJsonArray();
+        if (configArray.size() < 2) {
+            throw new IllegalArgumentException("if config must be an array >= 
2 elements");
+        }
+        for (int i = 0; i < configArray.size() - 1; i += 2) {
+            if (isTruthy(logic.apply(configArray.get(i), params))) {
+                return logic.apply(configArray.get(i + 1), params);
+            }
+        }
+        if (configArray.size() % 2 == 1) {
+            return configArray.get(configArray.size() - 1);
+        }
+        return JsonValue.FALSE;
+    }
+
+    private JsonValue missingSomeImpl(final JohnzonJsonLogic logic, final 
JsonValue config, final JsonValue params) {
+        if (config.getValueType() != JsonValue.ValueType.ARRAY) {
+            throw new IllegalArgumentException("missing_some takes an array as 
parameter: '" + config + "'");
+        }
+        final JsonArray configArray = config.asJsonArray();
+        if (configArray.size() != 2) {
+            throw new IllegalArgumentException("missing_some takes an array 
with a number and a path array as parameter: '" + config + "'");
+        }
+        final JsonArray tested = configArray.get(1).asJsonArray();
+        final JsonArray missing = tested.stream()
+                .filter(it -> varImpl(logic.apply(it, params), params) == 
JsonValue.NULL)
+                .collect(toArray());
+        if ((tested.size() - missing.size()) < 
JsonNumber.class.cast(logic.apply(configArray.get(0), params)).intValue()) {
+            return missing;
+        }
+        return JsonValue.EMPTY_JSON_ARRAY;
+    }
+
+    private JsonValue missingImpl(final JohnzonJsonLogic logic, final 
JsonValue config, final JsonValue params) {
+        if (config.getValueType() != JsonValue.ValueType.ARRAY) {
+            throw new IllegalArgumentException("missing takes an array as 
parameter: '" + config + "'");
+        }
+        return config.asJsonArray().stream()
+                .filter(it -> varImpl(logic.apply(it, params), params) == 
JsonValue.NULL)
+                .collect(toArray());
+    }
+
+    private JsonValue arrayTest(final JohnzonJsonLogic self, final JsonValue 
config, final JsonValue params,
+                                final BiPredicate<JsonValue, 
Stream<JsonValue>> tester) {
+        if (config.getValueType() == JsonValue.ValueType.ARRAY) {
+            final JsonArray array = config.asJsonArray();
+            if (array.size() != 2) {
+                throw new IllegalArgumentException("array test only supports 
arrays with 2: '" + config + "'");
+            }
+            final JsonValue items = self.apply(array.get(0), params);
+            if (items.getValueType() != JsonValue.ValueType.ARRAY) {
+                throw new IllegalArgumentException("Expected '" + array.get(0) 
+ "' to be an array, got " + items.getValueType());
+            }
+            final JsonValue subLogic = array.get(1);
+            return tester.test(subLogic, items.asJsonArray().stream()) ? 
JsonValue.TRUE : JsonValue.FALSE;
+        }
+        throw new IllegalArgumentException("Unsupported array test operation: 
'" + config + "'");
+    }
+
+    private JsonValue castToNumber(final JsonValue value) {
+        switch (value.getValueType()) {
+            case NUMBER:
+                return value;
+            case STRING:
+                return 
provider.createValue(Double.parseDouble(JsonString.class.cast(value).getString()));
+            default:
+                throw new IllegalArgumentException("Unsupported value to 
number: '" + value + "'");
+        }
+    }
+
+    private DoubleStream mapToDouble(final JohnzonJsonLogic logic, final 
JsonValue config, final JsonValue params) {
+        return config.asJsonArray().stream()
+                .map(it -> logic.apply(it, params))
+                .filter(it -> it.getValueType() == JsonValue.ValueType.NUMBER)
+                .mapToDouble(it -> JsonNumber.class.cast(it).doubleValue());
+    }
+
+    private JsonValue comparison(final BiPredicate<JsonValue, JsonValue> 
comparator,
+                                 final JsonValue config, final 
JohnzonJsonLogic self,
+                                 final JsonValue params) {
+        if (config.getValueType() != JsonValue.ValueType.ARRAY) {
+            throw new IllegalArgumentException("comparison config must be an 
array");
+        }
+        final JsonArray values = config.asJsonArray();
+        if (values.size() != 2) {
+            throw new IllegalArgumentException("comparison requires 2 
arguments");
+        }
+        final JsonValue first = self.apply(values.get(0), params);
+        final JsonValue second = self.apply(values.get(1), params);
+        return comparator.test(first, second) ? JsonValue.TRUE : 
JsonValue.FALSE;
+    }
+
+    private JsonValue numericComparison(final BiPredicate<Double, Double> 
comparator,
+                                        final JsonValue config, final 
JohnzonJsonLogic self,
+                                        final JsonValue params) {
+        if (config.getValueType() != JsonValue.ValueType.ARRAY) {
+            throw new IllegalArgumentException("numeric comparison config must 
be an array");
+        }
+        final JsonArray configArray = config.asJsonArray();
+        switch (configArray.size()) {
+            case 2: {
+                final JsonValue first = self.apply(configArray.get(0), params);
+                final JsonValue second = self.apply(configArray.get(1), 
params);
+                if (Stream.of(first, second).anyMatch(it -> it.getValueType() 
!= JsonValue.ValueType.NUMBER)) {
+                    throw new IllegalArgumentException("Only numbers can be 
compared: " + first + " / " + second);
+                }
+                return 
comparator.test(JsonNumber.class.cast(first).doubleValue(), 
JsonNumber.class.cast(second).doubleValue()) ?
+                        JsonValue.TRUE : JsonValue.FALSE;
+            }
+            case 3: { // between
+                final JsonValue first = self.apply(configArray.get(0), params);
+                final JsonValue second = self.apply(configArray.get(1), 
params);
+                final JsonValue third = self.apply(configArray.get(1), params);
+                if (Stream.of(first, second, third).anyMatch(it -> 
it.getValueType() != JsonValue.ValueType.NUMBER)) {
+                    throw new IllegalArgumentException("Only numbers can be 
compared");
+                }
+                return 
comparator.test(JsonNumber.class.cast(first).doubleValue(), 
JsonNumber.class.cast(second).doubleValue()) &&
+                        
comparator.test(JsonNumber.class.cast(second).doubleValue(), 
JsonNumber.class.cast(third).doubleValue()) ?
+                        JsonValue.TRUE : JsonValue.FALSE;
+            }
+            default:
+                throw new IllegalArgumentException("numeric comparison config 
must be an array >= 2 elements");
+        }
+    }
+
+    private JsonValue varImpl(final JsonValue config, final JsonValue params) {
+        switch (config.getValueType()) {
+            case ARRAY:
+                final JsonArray values = config.asJsonArray();
+                if (values.isEmpty()) {
+                    throw new IllegalArgumentException("var should have at 
least one parameter");
+                }
+                final JsonValue accessor = apply(values.get(0), params);
+                switch (accessor.getValueType()) {
+                    case NUMBER:
+                        final int index = 
JsonNumber.class.cast(accessor).intValue();
+                        final JsonArray array = params.asJsonArray();
+                        final JsonValue arrayAttribute = index >= array.size() 
? null : array.get(index);
+                        return arrayAttribute == null ? (values.size() > 1 ? 
apply(values.get(1), params) : JsonValue.NULL) : arrayAttribute;
+                    case STRING:
+                        final JsonValue objectAttribute = extractValue(params, 
JsonString.class.cast(accessor).getString());
+                        return objectAttribute == JsonValue.NULL && 
values.size() > 1 ? apply(values.get(1), params) : objectAttribute;
+                    default:
+                        throw new IllegalArgumentException("Unsupported var 
first paraemter: '" + accessor + "', should be string or number");
+                }
+            case STRING:
+                return extractValue(params, 
JsonString.class.cast(config).getString());
+            case NUMBER:
+                final int index = JsonNumber.class.cast(config).intValue();
+                final JsonArray array = params.asJsonArray();
+                final JsonValue arrayAttribute = array.size() <= index ? null 
: array.get(index);
+                return arrayAttribute == null ? JsonValue.NULL : 
arrayAttribute;
+            case OBJECT:
+                return varImpl(apply(config, params), params);
+            default:
+                throw new IllegalArgumentException("Unsupported configuration 
for var: '" + config + "'");
+        }
+    }
+
+    private JsonValue extractValue(final JsonValue params, final String 
string) {
+        if (string.isEmpty()) {
+            return params;
+        }
+        final JsonValue objectAttribute;
+        if (string.contains(".")) {
+            try {
+                objectAttribute = 
toPointer(string).getValue(JsonStructure.class.cast(params));
+            } catch (final JsonException je) { // missing
+                return JsonValue.NULL;
+            }
+        } else if (params.getValueType() == JsonValue.ValueType.OBJECT) {
+            objectAttribute = params.asJsonObject().get(string);
+        } else if (params.getValueType() == JsonValue.ValueType.ARRAY) {
+            objectAttribute = 
params.asJsonArray().get(Integer.parseInt(string.trim()));
+        } else {
+            objectAttribute = null;
+        }
+        return objectAttribute == null ? JsonValue.NULL : objectAttribute;
+    }
+
+    // cache?
+    private JsonPointer toPointer(final String string) {
+        if (cachePointers) {
+            return pointers.computeIfAbsent(string, this::doToPointer);
+        }
+        return doToPointer(string);
+    }
+
+    private JsonPointer doToPointer(final String string) {
+        return provider.createPointer(
+                (!string.startsWith("/") ? "/" : "") +
+                        string.replace('.', '/'));
+    }
+
+    // same as JsonCollector one except it uses this builderFactory instead of 
default one which goes through the SPI
+    private Collector<JsonValue, JsonArrayBuilder, JsonArray> toArray() {
+        return Collector.of(builderFactory::createArrayBuilder, 
JsonArrayBuilder::add, JsonArrayBuilder::addAll, JsonArrayBuilder::build);
+    }
+}
diff --git 
a/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/spi/Operator.java
 
b/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/spi/Operator.java
new file mode 100644
index 0000000..8531e0b
--- /dev/null
+++ 
b/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/spi/Operator.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.johnzon.jsonlogic.spi;
+
+import org.apache.johnzon.jsonlogic.JohnzonJsonLogic;
+
+import javax.json.JsonValue;
+
+@FunctionalInterface
+public interface Operator {
+    JsonValue apply(JohnzonJsonLogic logic, JsonValue config, JsonValue 
params);
+}
diff --git 
a/johnzon-jsonlogic/src/test/java/org/apache/johnzon/jsonlogic/JohnzonJsonLogicTest.java
 
b/johnzon-jsonlogic/src/test/java/org/apache/johnzon/jsonlogic/JohnzonJsonLogicTest.java
new file mode 100644
index 0000000..49295b3
--- /dev/null
+++ 
b/johnzon-jsonlogic/src/test/java/org/apache/johnzon/jsonlogic/JohnzonJsonLogicTest.java
@@ -0,0 +1,982 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.johnzon.jsonlogic;
+
+import org.junit.Test;
+
+import javax.json.Json;
+import javax.json.JsonBuilderFactory;
+import javax.json.JsonObject;
+import javax.json.JsonValue;
+
+import static java.util.Collections.emptyMap;
+import static org.junit.Assert.assertEquals;
+
+public class JohnzonJsonLogicTest {
+    private final JohnzonJsonLogic jsonLogic = new JohnzonJsonLogic();
+    private final JsonBuilderFactory builderFactory = 
Json.createBuilderFactory(emptyMap());
+
+    @Test
+    public void varObjectString() {
+        assertEquals(Json.createValue("b"), jsonLogic.apply(
+                builderFactory.createObjectBuilder()
+                        .add("var", "a")
+                        .build(),
+                builderFactory.createObjectBuilder()
+                        .add("a", "b")
+                        .add("c", "d")
+                        .build()));
+    }
+
+    @Test
+    public void varObjectPtr() {
+        assertEquals(Json.createValue("ok"), jsonLogic.apply(
+                builderFactory.createObjectBuilder()
+                        .add("var", "a.b.0")
+                        .build(),
+                builderFactory.createObjectBuilder()
+                        .add("a", builderFactory.createObjectBuilder()
+                                .add("b", builderFactory.createArrayBuilder()
+                                        .add("ok")))
+                        .build()));
+    }
+
+    @Test
+    public void varObjectStringMissing() {
+        assertEquals(JsonValue.NULL, jsonLogic.apply(
+                builderFactory.createObjectBuilder()
+                        .add("var", "a")
+                        .build(),
+                builderFactory.createObjectBuilder()
+                        .add("c", "d")
+                        .build()));
+    }
+
+    @Test
+    public void varArrayInt() {
+        assertEquals(Json.createValue("b"), jsonLogic.apply(
+                builderFactory.createObjectBuilder()
+                        .add("var", 1)
+                        .build(),
+                builderFactory.createArrayBuilder()
+                        .add("a")
+                        .add("b")
+                        .build()));
+    }
+
+    @Test
+    public void varObjectDefault() {
+        assertEquals(Json.createValue(26), jsonLogic.apply(
+                builderFactory.createObjectBuilder()
+                        .add("var", 
builderFactory.createArrayBuilder().add("z").add(26))
+                        .build(),
+                builderFactory.createObjectBuilder()
+                        .add("a", "b")
+                        .add("c", "d")
+                        .build()));
+    }
+
+    @Test
+    public void varArrayDefault() {
+        assertEquals(Json.createValue(26), jsonLogic.apply(
+                builderFactory.createObjectBuilder()
+                        .add("var", 
builderFactory.createArrayBuilder().add(10).add(26))
+                        .build(),
+                builderFactory.createArrayBuilder()
+                        .add("a")
+                        .add("b")
+                        .build()));
+    }
+
+    @Test
+    public void missing() {
+        final JsonObject value = builderFactory.createObjectBuilder()
+                .add("a", 1)
+                .add("b", 2)
+                .build();
+        assertEquals(
+                JsonValue.EMPTY_JSON_ARRAY,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("missing",
+                                        builderFactory.createArrayBuilder()
+                                                .add("a")
+                                                .add("b"))
+                                .build(),
+                        value));
+        assertEquals(
+                builderFactory.createArrayBuilder()
+                        .add("c")
+                        .build(),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("missing", 
builderFactory.createArrayBuilder()
+                                        .add("a")
+                                        .add("b")
+                                        .add("c"))
+                                .build(),
+                        value));
+    }
+
+    @Test
+    public void missingSome() {
+        final JsonObject value = builderFactory.createObjectBuilder()
+                .add("a", 1)
+                .add("b", 2)
+                .build();
+        assertEquals(
+                JsonValue.EMPTY_JSON_ARRAY,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("missing_some",
+                                        builderFactory.createArrayBuilder()
+                                                .add(1)
+                                                
.add(builderFactory.createArrayBuilder()
+                                                        .add("a")
+                                                        .add("c")
+                                                        .add("d")))
+                                .build(),
+                        value));
+        assertEquals(
+                builderFactory.createArrayBuilder()
+                        .add("c")
+                        .add("d")
+                        .build(),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("missing_some", 
builderFactory.createArrayBuilder()
+                                        .add(2)
+                                        
.add(builderFactory.createArrayBuilder()
+                                                .add("a")
+                                                .add("c")
+                                                .add("d")))
+                                .build(),
+                        value));
+    }
+
+    @Test
+    public void ifStatic() {
+        assertEquals(
+                Json.createValue("yes"),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("if",
+                                        builderFactory.createArrayBuilder()
+                                                .add(true)
+                                                .add("yes")
+                                                .add("false"))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                Json.createValue("no"),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("if",
+                                        builderFactory.createArrayBuilder()
+                                                .add(false)
+                                                .add("yes")
+                                                .add("no"))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+    }
+
+    @Test
+    public void ifElsIfElseWithVarEval() {
+        assertEquals(
+                Json.createValue("liquid"),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("if",
+                                        builderFactory.createArrayBuilder()
+                                                
.add(builderFactory.createObjectBuilder()
+                                                        .add("<", 
builderFactory.createArrayBuilder()
+                                                                
.add(builderFactory.createObjectBuilder()
+                                                                        
.add("var", "temp"))
+                                                                .add(0)))
+                                                .add("freezing")
+                                                
.add(builderFactory.createObjectBuilder()
+                                                        .add("<", 
builderFactory.createArrayBuilder()
+                                                                
.add(builderFactory.createObjectBuilder()
+                                                                        
.add("var", "temp"))
+                                                                .add(100)))
+                                                .add("liquid")
+                                                .add("gas"))
+                                .build(),
+                        builderFactory.createObjectBuilder().add("temp", 
55).build()));
+    }
+
+    @Test
+    public void lessThan() {
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("<", builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add("var", "temp"))
+                                        .add(100))
+                                .build(),
+                        builderFactory.createObjectBuilder().add("temp", 
99).build()));
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("<", builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add("var", "temp"))
+                                        .add(100))
+                                .build(),
+                        builderFactory.createObjectBuilder().add("temp", 
100).build()));
+    }
+
+    @Test
+    public void lessOrEqualsThan() {
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("<=", builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add("var", "temp"))
+                                        .add(100))
+                                .build(),
+                        builderFactory.createObjectBuilder().add("temp", 
100).build()));
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("<=", builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add("var", "temp"))
+                                        .add(100))
+                                .build(),
+                        builderFactory.createObjectBuilder().add("temp", 
101).build()));
+    }
+
+    @Test
+    public void greaterThan() {
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add(">", builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add("var", "temp"))
+                                        .add(100))
+                                .build(),
+                        builderFactory.createObjectBuilder().add("temp", 
101).build()));
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add(">", builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add("var", "temp"))
+                                        .add(100))
+                                .build(),
+                        builderFactory.createObjectBuilder().add("temp", 
100).build()));
+    }
+
+    @Test
+    public void greaterOrEqualsThan() {
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add(">=", builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add("var", "temp"))
+                                        .add(100))
+                                .build(),
+                        builderFactory.createObjectBuilder().add("temp", 
100).build()));
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add(">=", builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add("var", "temp"))
+                                        .add(100))
+                                .build(),
+                        builderFactory.createObjectBuilder().add("temp", 
99).build()));
+    }
+
+    @Test
+    public void equalsCoercion() {
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("==", builderFactory.createArrayBuilder()
+                                        .add(1)
+                                        .add(1))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("==", builderFactory.createArrayBuilder()
+                                        .add("1")
+                                        .add(1))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("==", builderFactory.createArrayBuilder()
+                                        .add(1)
+                                        .add("1"))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("==", builderFactory.createArrayBuilder()
+                                        .add(0)
+                                        .add(false))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("==", builderFactory.createArrayBuilder()
+                                        .add(1)
+                                        .add(false))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+    }
+
+    @Test
+    public void equalsNoCoercion() {
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("===", builderFactory.createArrayBuilder()
+                                        .add(1)
+                                        .add(1))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("===", builderFactory.createArrayBuilder()
+                                        .add("1")
+                                        .add(1))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("===", builderFactory.createArrayBuilder()
+                                        .add(1)
+                                        .add("1"))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("===", builderFactory.createArrayBuilder()
+                                        .add(0)
+                                        .add(false))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("===", builderFactory.createArrayBuilder()
+                                        .add(1)
+                                        .add(false))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+    }
+
+    @Test
+    public void negate() {
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("!", true)
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("!", 
builderFactory.createArrayBuilder().add(true).build())
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+    }
+
+    @Test
+    public void booleanEvaluation() {
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("!!", true)
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("!!", "a")
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("!!", "")
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("!!", 
builderFactory.createArrayBuilder().add(true).build())
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+    }
+
+    @Test
+    public void and() {
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("and", builderFactory.createArrayBuilder()
+                                        .add(false)
+                                        .add(true))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                Json.createValue("a"),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("and", builderFactory.createArrayBuilder()
+                                        .add(true)
+                                        .add("a"))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+    }
+
+    @Test
+    public void or() {
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("or", builderFactory.createArrayBuilder()
+                                        .add(false)
+                                        .add(true))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("or", builderFactory.createArrayBuilder()
+                                        .add(true)
+                                        .add("a"))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                Json.createValue("a"),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("or", builderFactory.createArrayBuilder()
+                                        .add("a")
+                                        .add(true))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                Json.createValue("a"),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("or", builderFactory.createArrayBuilder()
+                                        .add(false)
+                                        .add(JsonValue.EMPTY_JSON_ARRAY)
+                                        .add("a")
+                                        .add(true))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+    }
+
+    @Test
+    public void min() {
+        assertEquals(
+                Json.createValue(100.),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("min", builderFactory.createArrayBuilder()
+                                        .add(100)
+                                        .add(200)
+                                        .add(300))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+    }
+
+    @Test
+    public void max() {
+        assertEquals(
+                Json.createValue(300.),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("max", builderFactory.createArrayBuilder()
+                                        .add(100)
+                                        .add(200)
+                                        .add(300))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+    }
+
+    @Test
+    public void plus() {
+        assertEquals(
+                Json.createValue(6.),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("+", builderFactory.createArrayBuilder()
+                                        .add(4)
+                                        .add(2))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                Json.createValue(3.14),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("+", "3.14")
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                Json.createValue(7.),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("+", builderFactory.createArrayBuilder()
+                                        .add(4)
+                                        .add(2)
+                                        .add(1))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+    }
+
+    @Test
+    public void minus() {
+        assertEquals(
+                Json.createValue(2.),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("-", builderFactory.createArrayBuilder()
+                                        .add(4)
+                                        .add(2))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                Json.createValue(-2.),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("-", 2)
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+    }
+
+    @Test
+    public void multiply() {
+        assertEquals(
+                Json.createValue(8.),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("*", builderFactory.createArrayBuilder()
+                                        .add(4)
+                                        .add(2))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                Json.createValue(24.),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("*", builderFactory.createArrayBuilder()
+                                        .add(4)
+                                        .add(3)
+                                        .add(2))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+    }
+
+    @Test
+    public void divide() {
+        assertEquals(
+                Json.createValue(2.),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("/", builderFactory.createArrayBuilder()
+                                        .add(4)
+                                        .add(2))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+    }
+
+    @Test
+    public void modulo() {
+        assertEquals(
+                Json.createValue(0.),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("%", builderFactory.createArrayBuilder()
+                                        .add(4)
+                                        .add(2))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+        assertEquals(
+                Json.createValue(1.),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("%", builderFactory.createArrayBuilder()
+                                        .add(5)
+                                        .add(2))
+                                .build(),
+                        JsonValue.EMPTY_JSON_OBJECT));
+    }
+
+    @Test
+    public void map() {
+        assertEquals(
+                builderFactory.createArrayBuilder()
+                        .add(2.)
+                        .add(4.)
+                        .add(6.)
+                        .add(8.)
+                        .add(10.)
+                        .build(),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("map", builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add("var", "integers"))
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add("*", 
builderFactory.createArrayBuilder()
+                                                        
.add(builderFactory.createObjectBuilder()
+                                                                .add("var", 
""))
+                                                        .add(2))))
+                                .build(),
+                        builderFactory.createObjectBuilder()
+                                .add("integers", 
builderFactory.createArrayBuilder()
+                                        .add(1)
+                                        .add(2)
+                                        .add(3)
+                                        .add(4)
+                                        .add(5))
+                                .build()));
+    }
+
+    @Test
+    public void filter() {
+        assertEquals(
+                builderFactory.createArrayBuilder()
+                        .add(1)
+                        .add(3)
+                        .add(5)
+                        .build(),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("filter", 
builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add("var", "integers"))
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add("%", 
builderFactory.createArrayBuilder()
+                                                        
.add(builderFactory.createObjectBuilder()
+                                                                .add("var", 
""))
+                                                        .add(2))))
+                                .build(),
+                        builderFactory.createObjectBuilder()
+                                .add("integers", 
builderFactory.createArrayBuilder()
+                                        .add(1)
+                                        .add(2)
+                                        .add(3)
+                                        .add(4)
+                                        .add(5))
+                                .build()));
+    }
+
+    @Test
+    public void reduce() {
+        assertEquals(
+                Json.createValue(15.),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("reduce", 
builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add("var", "integers"))
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add("+", 
builderFactory.createArrayBuilder()
+                                                        
.add(builderFactory.createObjectBuilder()
+                                                                .add("var", 
"current"))
+                                                        
.add(builderFactory.createObjectBuilder()
+                                                                .add("var", 
"accumulator"))))
+                                        .add(0))
+                                .build(),
+                        builderFactory.createObjectBuilder()
+                                .add("integers", 
builderFactory.createArrayBuilder()
+                                        .add(1)
+                                        .add(2)
+                                        .add(3)
+                                        .add(4)
+                                        .add(5))
+                                .build()));
+    }
+
+    @Test
+    public void all() {
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("all", builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createArrayBuilder()
+                                                .add(1)
+                                                .add(2)
+                                                .add(3))
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add(">", 
builderFactory.createArrayBuilder()
+                                                        
.add(builderFactory.createObjectBuilder()
+                                                                .add("var", 
""))
+                                                        .add(0))))
+                                .build(),
+                        JsonValue.EMPTY_JSON_ARRAY));
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("all", builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createArrayBuilder()
+                                                .add(1)
+                                                .add(2)
+                                                .add(3))
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add("<", 
builderFactory.createArrayBuilder()
+                                                        
.add(builderFactory.createObjectBuilder()
+                                                                .add("var", 
""))
+                                                        .add(3))))
+                                .build(),
+                        JsonValue.EMPTY_JSON_ARRAY));
+    }
+
+    @Test
+    public void some() {
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("some", 
builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createArrayBuilder()
+                                                .add(1)
+                                                .add(2)
+                                                .add(3))
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add(">", 
builderFactory.createArrayBuilder()
+                                                        
.add(builderFactory.createObjectBuilder()
+                                                                .add("var", 
""))
+                                                        .add(2))))
+                                .build(),
+                        JsonValue.EMPTY_JSON_ARRAY));
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("some", 
builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createArrayBuilder()
+                                                .add(1)
+                                                .add(2)
+                                                .add(3))
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add(">", 
builderFactory.createArrayBuilder()
+                                                        
.add(builderFactory.createObjectBuilder()
+                                                                .add("var", 
""))
+                                                        .add(3))))
+                                .build(),
+                        JsonValue.EMPTY_JSON_ARRAY));
+    }
+
+    @Test
+    public void none() {
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("none", 
builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createArrayBuilder()
+                                                .add(1)
+                                                .add(2)
+                                                .add(3))
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add(">", 
builderFactory.createArrayBuilder()
+                                                        
.add(builderFactory.createObjectBuilder()
+                                                                .add("var", 
""))
+                                                        .add(3))))
+                                .build(),
+                        JsonValue.EMPTY_JSON_ARRAY));
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("none", 
builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createArrayBuilder()
+                                                .add(1)
+                                                .add(2)
+                                                .add(3))
+                                        
.add(builderFactory.createObjectBuilder()
+                                                .add("<", 
builderFactory.createArrayBuilder()
+                                                        
.add(builderFactory.createObjectBuilder()
+                                                                .add("var", 
""))
+                                                        .add(2))))
+                                .build(),
+                        JsonValue.EMPTY_JSON_ARRAY));
+    }
+
+    @Test
+    public void merge() {
+        assertEquals(
+                builderFactory.createArrayBuilder()
+                        .add(1)
+                        .add(2)
+                        .add(3)
+                        .add("4")
+                        .build(),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("merge", 
builderFactory.createArrayBuilder()
+                                        
.add(builderFactory.createArrayBuilder()
+                                                .add(1)
+                                                .add(2))
+                                        .add(3)
+                                        .add("4"))
+                                .build(),
+                        JsonValue.EMPTY_JSON_ARRAY));
+    }
+
+    @Test
+    public void in() {
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("in", builderFactory.createArrayBuilder()
+                                        .add(2)
+                                        
.add(builderFactory.createArrayBuilder()
+                                                .add(1)
+                                                .add(2)))
+                                .build(),
+                        JsonValue.EMPTY_JSON_ARRAY));
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("in", builderFactory.createArrayBuilder()
+                                        .add(3)
+                                        
.add(builderFactory.createArrayBuilder()
+                                                .add(1)
+                                                .add(2)))
+                                .build(),
+                        JsonValue.EMPTY_JSON_ARRAY));
+        assertEquals(
+                JsonValue.TRUE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("in", builderFactory.createArrayBuilder()
+                                        .add("ay")
+                                        .add("may"))
+                                .build(),
+                        JsonValue.EMPTY_JSON_ARRAY));
+        assertEquals(
+                JsonValue.FALSE,
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("in", builderFactory.createArrayBuilder()
+                                        .add("cem")
+                                        .add("may"))
+                                .build(),
+                        JsonValue.EMPTY_JSON_ARRAY));
+    }
+
+    @Test
+    public void cat() {
+        assertEquals(
+                Json.createValue("hello json"),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("cat", builderFactory.createArrayBuilder()
+                                        .add("hell")
+                                        .add("o json"))
+                                .build(),
+                        JsonValue.EMPTY_JSON_ARRAY));
+    }
+
+    @Test
+    public void substr() {
+        assertEquals(
+                Json.createValue("logic"),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("substr", 
builderFactory.createArrayBuilder()
+                                        .add("jsonlogic")
+                                        .add(4))
+                                .build(),
+                        JsonValue.EMPTY_JSON_ARRAY));
+        assertEquals(
+                Json.createValue("logic"),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("substr", 
builderFactory.createArrayBuilder()
+                                        .add("jsonlogic")
+                                        .add(-5))
+                                .build(),
+                        JsonValue.EMPTY_JSON_ARRAY));
+        assertEquals(
+                Json.createValue("son"),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("substr", 
builderFactory.createArrayBuilder()
+                                        .add("jsonlogic")
+                                        .add(1)
+                                        .add(3))
+                                .build(),
+                        JsonValue.EMPTY_JSON_ARRAY));
+        assertEquals(
+                Json.createValue("log"),
+                jsonLogic.apply(
+                        builderFactory.createObjectBuilder()
+                                .add("substr", 
builderFactory.createArrayBuilder()
+                                        .add("jsonlogic")
+                                        .add(4)
+                                        .add(-2))
+                                .build(),
+                        JsonValue.EMPTY_JSON_ARRAY));
+    }
+}
diff --git a/pom.xml b/pom.xml
index 34965f8..9ebcf46 100644
--- a/pom.xml
+++ b/pom.xml
@@ -64,6 +64,7 @@
     <module>johnzon-json-extras</module>
     <module>johnzon-jsonschema</module>
     <module>johnzon-osgi</module>
+    <module>johnzon-jsonlogic</module>
   </modules>
 
   <dependencyManagement>
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
index aba8f91..3ee6c0e 100644
--- a/src/site/markdown/index.md
+++ b/src/site/markdown/index.md
@@ -535,6 +535,48 @@ Known limitations are (feel free to do a PR on github to 
add these missing featu
 * Doesn't support references in the schema
 * Doesn't support: dependencies, propertyNames, if/then/else, 
allOf/anyOf/oneOf/not, format validations
 
+### JSON Logic
+
+<pre class="prettyprint linenums"><![CDATA[
+<dependency>
+  <groupId>org.apache.johnzon</groupId>
+  <artifactId>johnzon-jsonlogic</artifactId>
+  <version>${johnzon.version}</version>
+</dependency>
+<dependency> <!-- requires an implementation of JSON-P -->
+  <groupId>org.apache.johnzon</groupId>
+  <artifactId>johnzon-core</artifactId>
+  <version>${johnzon.version}</version>
+</dependency>
+]]></pre>
+
+This module provides a way to execute any [JSON Logic](http://jsonlogic.com/) 
expression.
+
+<pre class="prettyprint linenums"><![CDATA[
+final JohnzonJsonLogic jsonLogic = new JohnzonJsonLogic();
+final JsonValue result = jsonLogic.apply(
+        builderFactory.createObjectBuilder()
+                .add("merge", builderFactory.createArrayBuilder()
+                        .add(builderFactory.createArrayBuilder()
+                                .add(1)
+                                .add(2))
+                        .add(3)
+                        .add("4"))
+                .build(),
+        JsonValue.EMPTY_JSON_ARRAY);
+]]></pre>
+
+Default operators are supported - except "log" one to let you pick the logger 
(impl + name) you want.
+
+To register a custom operator just do it on your json logic instance:
+
+<pre class="prettyprint linenums"><![CDATA[
+final JohnzonJsonLogic jsonLogic = new JohnzonJsonLogic();
+jsonLogic.registerOperator(
+  "log",
+  (jsonLogic, config, args) -> log.info(String.valueOf(jsonLogic.apply(config, 
args)));
+]]></pre>
+
 ### OSGi JAX-RS Whiteboard
 
 Though Johnzon artifacts are OSGi bundles to begin with, this module provides 
further integration with the [OSGi JAX-RS 
Whiteboard](https://osgi.org/specification/osgi.cmpn/7.0.0/service.jaxrs.html) 
and [OSGi CDI 
Integration](https://osgi.org/specification/osgi.enterprise/7.0.0/service.cdi.html)
 specifications.

Reply via email to