From 5bf050b74abcfd5ddbccad05580f12a71cfc04c3 Mon Sep 17 00:00:00 2001 From: Guido Breitenhuber Date: Wed, 12 Nov 2025 21:59:29 +0100 Subject: [PATCH] Construct JSON path only on exceptional cases. --- .../github/jamsesso/jsonlogic/JsonLogic.java | 8 +- .../jsonlogic/JsonLogicException.java | 35 +++-- .../ast/JsonLogicParseException.java | 4 + .../jsonlogic/ast/JsonLogicParser.java | 33 +++-- .../JsonLogicEvaluationException.java | 11 ++ .../evaluator/JsonLogicEvaluator.java | 135 ++++++++++++++---- .../evaluator/expressions/AllExpression.java | 27 ++-- .../expressions/ArrayHasExpression.java | 30 ++-- .../expressions/FilterExpression.java | 29 ++-- .../evaluator/expressions/IfExpression.java | 56 ++++++-- .../expressions/InequalityExpression.java | 6 +- .../JsonPathHandlerJsonLogicExpression.java | 20 +++ .../expressions/LogicExpression.java | 20 ++- .../evaluator/expressions/MapExpression.java | 24 +++- .../expressions/ReduceExpression.java | 35 +++-- 15 files changed, 369 insertions(+), 104 deletions(-) create mode 100644 src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/JsonPathHandlerJsonLogicExpression.java diff --git a/src/main/java/io/github/jamsesso/jsonlogic/JsonLogic.java b/src/main/java/io/github/jamsesso/jsonlogic/JsonLogic.java index fa25f2f..3cf2351 100644 --- a/src/main/java/io/github/jamsesso/jsonlogic/JsonLogic.java +++ b/src/main/java/io/github/jamsesso/jsonlogic/JsonLogic.java @@ -2,6 +2,7 @@ import io.github.jamsesso.jsonlogic.ast.JsonLogicNode; import io.github.jamsesso.jsonlogic.ast.JsonLogicParser; +import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluationException; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluator; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicExpression; import io.github.jamsesso.jsonlogic.evaluator.expressions.*; @@ -84,7 +85,12 @@ public Object apply(String json, Object data) throws JsonLogicException { evaluator = new JsonLogicEvaluator(expressions); } - return evaluator.evaluate(parseCache.get(json), data, "$"); + try { + return evaluator.evaluate(parseCache.get(json), data, ""); + } catch (JsonLogicException e) { + e.prependPartialJsonPath("$"); + throw e; + } } public static boolean truthy(Object value) { diff --git a/src/main/java/io/github/jamsesso/jsonlogic/JsonLogicException.java b/src/main/java/io/github/jamsesso/jsonlogic/JsonLogicException.java index 0e34710..ee654b2 100644 --- a/src/main/java/io/github/jamsesso/jsonlogic/JsonLogicException.java +++ b/src/main/java/io/github/jamsesso/jsonlogic/JsonLogicException.java @@ -2,28 +2,47 @@ public class JsonLogicException extends Exception { - private String jsonPath; + private final StringBuilder jsonPath = new StringBuilder(); private JsonLogicException() { // The default constructor should not be called for exceptions. A reason must be provided. } + public JsonLogicException(String msg) { + super(msg); + } + public JsonLogicException(String msg, String jsonPath) { - super(msg); - this.jsonPath = jsonPath; + super(msg); + prependPartialJsonPath(jsonPath); } + public JsonLogicException(Throwable cause) { + super(cause); + } + public JsonLogicException(Throwable cause, String jsonPath) { - super(cause); - this.jsonPath = jsonPath; + super(cause); + prependPartialJsonPath(jsonPath); } + public JsonLogicException(String msg, Throwable cause) { + super(msg, cause); + } + public JsonLogicException(String msg, Throwable cause, String jsonPath) { super(msg, cause); - this.jsonPath = jsonPath; + prependPartialJsonPath(jsonPath); } - public String getJsonPath() { - return jsonPath; + public String getJsonPath() { + return jsonPath.toString(); } + + public void prependPartialJsonPath(String partialPath) { + if (partialPath == null) { + return; + } + jsonPath.insert(0, partialPath); + } } diff --git a/src/main/java/io/github/jamsesso/jsonlogic/ast/JsonLogicParseException.java b/src/main/java/io/github/jamsesso/jsonlogic/ast/JsonLogicParseException.java index 8bc5873..88547da 100644 --- a/src/main/java/io/github/jamsesso/jsonlogic/ast/JsonLogicParseException.java +++ b/src/main/java/io/github/jamsesso/jsonlogic/ast/JsonLogicParseException.java @@ -14,4 +14,8 @@ public JsonLogicParseException(Throwable cause, String jsonPath) { public JsonLogicParseException(String msg, Throwable cause, String jsonPath) { super(msg, cause, jsonPath); } + + public JsonLogicParseException(String msg) { + super(msg); + } } diff --git a/src/main/java/io/github/jamsesso/jsonlogic/ast/JsonLogicParser.java b/src/main/java/io/github/jamsesso/jsonlogic/ast/JsonLogicParser.java index bf442b8..66f7856 100644 --- a/src/main/java/io/github/jamsesso/jsonlogic/ast/JsonLogicParser.java +++ b/src/main/java/io/github/jamsesso/jsonlogic/ast/JsonLogicParser.java @@ -17,15 +17,16 @@ public static JsonLogicNode parse(String json) throws JsonLogicParseException { try { return parse(PARSER.parse(json)); } + catch (JsonLogicParseException e) { + e.prependPartialJsonPath("$"); + throw e; + } catch (JsonSyntaxException e) { throw new JsonLogicParseException(e, "$"); } } private static JsonLogicNode parse(JsonElement root) throws JsonLogicParseException { - return parse(root, "$"); - } - private static JsonLogicNode parse(JsonElement root, String jsonPath) throws JsonLogicParseException { // Handle null if (root.isJsonNull()) { return JsonLogicNull.NULL; @@ -56,9 +57,16 @@ private static JsonLogicNode parse(JsonElement root, String jsonPath) throws Jso JsonArray array = root.getAsJsonArray(); List elements = new ArrayList<>(array.size()); - int index = 0; - for (JsonElement element : array) { - elements.add(parse(element, String.format("%s[%d]", jsonPath, index++))); + for (int index = 0; index < array.size(); index++) { + JsonElement element = array.get(index); + JsonLogicNode arrayNode; + try { + arrayNode = parse(element); + } catch (JsonLogicParseException e) { + e.prependPartialJsonPath("[" + (index) + "]"); + throw e; + } + elements.add(arrayNode); } return new JsonLogicArray(elements); @@ -68,14 +76,21 @@ private static JsonLogicNode parse(JsonElement root, String jsonPath) throws Jso JsonObject object = root.getAsJsonObject(); if (object.keySet().size() != 1) { - throw new JsonLogicParseException("objects must have exactly 1 key defined, found " + object.keySet().size(), jsonPath); + throw new JsonLogicParseException("objects must have exactly 1 key defined, found " + object.keySet().size()); } String key = object.keySet().stream().findAny().get(); - JsonLogicNode argumentNode = parse(object.get(key), String.format("%s.%s", jsonPath, key)); + JsonLogicNode argumentNode; JsonLogicArray arguments; - // Always coerce single-argument operations into a JsonLogicArray with a single element. + try { + argumentNode = parse(object.get(key)); + } catch (JsonLogicParseException e) { + e.prependPartialJsonPath("." + key); + throw e; + } + + // Always coerce single-argument operations into a JsonLogicArray with a single element. if (argumentNode instanceof JsonLogicArray) { arguments = (JsonLogicArray) argumentNode; } diff --git a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/JsonLogicEvaluationException.java b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/JsonLogicEvaluationException.java index b35d2bc..8f5723f 100644 --- a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/JsonLogicEvaluationException.java +++ b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/JsonLogicEvaluationException.java @@ -3,14 +3,25 @@ import io.github.jamsesso.jsonlogic.JsonLogicException; public class JsonLogicEvaluationException extends JsonLogicException { + public JsonLogicEvaluationException(String msg) { + super(msg); + } public JsonLogicEvaluationException(String msg, String jsonPath) { super(msg, jsonPath); } + public JsonLogicEvaluationException(Throwable cause) { + super(cause); + } + public JsonLogicEvaluationException(Throwable cause, String jsonPath) { super(cause, jsonPath); } + public JsonLogicEvaluationException(String msg, Throwable cause) { + super(msg, cause); + } + public JsonLogicEvaluationException(String msg, Throwable cause, String jsonPath) { super(msg, cause, jsonPath); } diff --git a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/JsonLogicEvaluator.java b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/JsonLogicEvaluator.java index e15fbb0..722bd24 100644 --- a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/JsonLogicEvaluator.java +++ b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/JsonLogicEvaluator.java @@ -1,5 +1,6 @@ package io.github.jamsesso.jsonlogic.evaluator; +import io.github.jamsesso.jsonlogic.JsonLogicException; import io.github.jamsesso.jsonlogic.ast.*; import io.github.jamsesso.jsonlogic.utils.ArrayLike; @@ -24,15 +25,31 @@ public JsonLogicEvaluator(Map expressions) { this.expressions = Collections.unmodifiableMap(expressions); } + @Deprecated public Object evaluate(JsonLogicNode node, Object data, String jsonPath) throws JsonLogicEvaluationException { - switch (node.getType()) { - case PRIMITIVE: return evaluate((JsonLogicPrimitive) node); - case VARIABLE: return evaluate((JsonLogicVariable) node, data, jsonPath + ".var"); - case ARRAY: return evaluate((JsonLogicArray) node, data, jsonPath); - default: return evaluate((JsonLogicOperation) node, data, jsonPath); - } + try { + return evaluate(node, data); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath(jsonPath); + throw e; + } } + public Object evaluate(JsonLogicNode node, Object data) throws JsonLogicEvaluationException { + switch (node.getType()) { + case PRIMITIVE: return evaluate((JsonLogicPrimitive) node); + case VARIABLE: + try { + return evaluate((JsonLogicVariable) node, data); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath(".var"); + throw e; + } + case ARRAY: return evaluate((JsonLogicArray) node, data); + default: return evaluate((JsonLogicOperation) node, data); + } + } + public Object evaluate(JsonLogicPrimitive primitive) { switch (primitive.getPrimitiveType()) { case NUMBER: return ((JsonLogicNumber) primitive).getValue(); @@ -42,20 +59,53 @@ public Object evaluate(JsonLogicPrimitive primitive) { } } - public Object evaluate(JsonLogicVariable variable, Object data, String jsonPath) + @Deprecated + public Object evaluate(JsonLogicVariable variable, Object data, String jsonPath) + throws JsonLogicEvaluationException { + try { + return evaluate(variable, data); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath(jsonPath); + throw e; + } + } + + public Object evaluate(JsonLogicVariable variable, Object data) throws JsonLogicEvaluationException { - Object defaultValue = evaluate(variable.getDefaultValue(), null, jsonPath + "[1]"); + Object defaultValue; + + try { + defaultValue = evaluate(variable.getDefaultValue(), null, ""); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[1]"); + throw e; + } - if (data == null) { + if (data == null) { return defaultValue; } - Object key = evaluate(variable.getKey(), data, jsonPath + "[0]"); + Object key; + + try { + key = evaluate(variable.getKey(), data, ""); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[0]"); + throw e; + } - if (key == null) { - return Optional.of(data) + if (key == null) { + Object varValue; + try { + varValue = evaluate(variable.getDefaultValue(), null, ""); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[0]"); + throw e; + } + + return Optional.of(data) .map(JsonLogicEvaluator::transform) - .orElse(evaluate(variable.getDefaultValue(), null, jsonPath + "[1]")); + .orElse(varValue); } if (key instanceof Number) { @@ -84,9 +134,14 @@ public Object evaluate(JsonLogicVariable variable, Object data, String jsonPath) Object result = data; for (String partial : keys) { - result = evaluatePartialVariable(partial, result, jsonPath + "[0]"); - - if (result == MISSING) { + try { + result = evaluatePartialVariable(partial, result); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[0]"); + throw e; + } + + if (result == MISSING) { return defaultValue; } else if (result == null) { return null; @@ -96,10 +151,10 @@ public Object evaluate(JsonLogicVariable variable, Object data, String jsonPath) return result; } - throw new JsonLogicEvaluationException("var first argument must be null, number, or string", jsonPath + "[0]"); + throw new JsonLogicEvaluationException("var first argument must be null, number, or string", "[0]"); } - private Object evaluatePartialVariable(String key, Object data, String jsonPath) throws JsonLogicEvaluationException { + private Object evaluatePartialVariable(String key, Object data) throws JsonLogicEvaluationException { if (ArrayLike.isEligible(data)) { ArrayLike list = new ArrayLike(data); int index; @@ -108,7 +163,7 @@ private Object evaluatePartialVariable(String key, Object data, String jsonPath) index = Integer.parseInt(key); } catch (NumberFormatException e) { - throw new JsonLogicEvaluationException(e, jsonPath); + throw new JsonLogicEvaluationException(e); } if (index < 0 || index >= list.size()) { @@ -130,25 +185,55 @@ private Object evaluatePartialVariable(String key, Object data, String jsonPath) return null; } - public List evaluate(JsonLogicArray array, Object data, String jsonPath) throws JsonLogicEvaluationException { + @Deprecated + public List evaluate(JsonLogicArray array, Object data, String jsonPath) throws JsonLogicEvaluationException { + try { + return evaluate(array, data); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath(jsonPath); + throw e; + } + } + + public List evaluate(JsonLogicArray array, Object data) throws JsonLogicEvaluationException { List values = new ArrayList<>(array.size()); - int index = 0; - for(JsonLogicNode element : array) { - values.add(evaluate(element, data, String.format("%s[%d]", jsonPath, index++))); + for(int index = 0; index < array.size(); index++) { + JsonLogicNode element = array.get(index); + try { + values.add(evaluate(element, data, "")); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[" + index + "]"); + throw e; + } } return values; } + @Deprecated public Object evaluate(JsonLogicOperation operation, Object data, String jsonPath) throws JsonLogicEvaluationException { + try { + return evaluate(operation, data); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath(jsonPath); + throw e; + } + } + + public Object evaluate(JsonLogicOperation operation, Object data) throws JsonLogicEvaluationException { JsonLogicExpression handler = expressions.get(operation.getOperator()); if (handler == null) { - throw new JsonLogicEvaluationException("Undefined operation '" + operation.getOperator() + "'", jsonPath); + throw new JsonLogicEvaluationException("Undefined operation '" + operation.getOperator() + "'"); } - return handler.evaluate(this, operation.getArguments(), data, String.format("%s.%s", jsonPath, operation.getOperator())); + try { + return handler.evaluate(this, operation.getArguments(), data, ""); + } catch (JsonLogicException e) { + e.prependPartialJsonPath("." + operation.getOperator()); + throw e; + } } public static Object transform(Object value) { diff --git a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/AllExpression.java b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/AllExpression.java index 2e9350f..04d9900 100644 --- a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/AllExpression.java +++ b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/AllExpression.java @@ -1,13 +1,13 @@ package io.github.jamsesso.jsonlogic.evaluator.expressions; +import io.github.jamsesso.jsonlogic.JsonLogic; import io.github.jamsesso.jsonlogic.ast.JsonLogicArray; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluationException; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluator; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicExpression; import io.github.jamsesso.jsonlogic.utils.ArrayLike; -import io.github.jamsesso.jsonlogic.JsonLogic; -public class AllExpression implements JsonLogicExpression { +public class AllExpression extends JsonPathHandlerJsonLogicExpression implements JsonLogicExpression { public static final AllExpression INSTANCE = new AllExpression(); private AllExpression() { @@ -20,20 +20,26 @@ public String key() { } @Override - public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data, String jsonPath) + public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data) throws JsonLogicEvaluationException { if (arguments.size() != 2) { - throw new JsonLogicEvaluationException("all expects exactly 2 arguments", jsonPath); + throw new JsonLogicEvaluationException("all expects exactly 2 arguments"); } - Object maybeArray = evaluator.evaluate(arguments.get(0), data, jsonPath + "[0]"); + Object maybeArray; + try { + maybeArray = evaluator.evaluate(arguments.get(0), data, ""); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[0]"); + throw e; + } if (maybeArray == null) { return false; } if (!ArrayLike.isEligible(maybeArray)) { - throw new JsonLogicEvaluationException("first argument to all must be a valid array", jsonPath); + throw new JsonLogicEvaluationException("first argument to all must be a valid array"); } ArrayLike array = new ArrayLike(maybeArray); @@ -44,8 +50,13 @@ public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, O int index = 1; for (Object item : array) { - if(!JsonLogic.truthy(evaluator.evaluate(arguments.get(1), item, String.format("%s[%d]", jsonPath, index)))) { - return false; + try { + if (!JsonLogic.truthy(evaluator.evaluate(arguments.get(1), item, ""))) { + return false; + } + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[" + index + "]"); + throw e; } } diff --git a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/ArrayHasExpression.java b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/ArrayHasExpression.java index 7d9bb14..8504e9f 100644 --- a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/ArrayHasExpression.java +++ b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/ArrayHasExpression.java @@ -1,13 +1,13 @@ package io.github.jamsesso.jsonlogic.evaluator.expressions; +import io.github.jamsesso.jsonlogic.JsonLogic; import io.github.jamsesso.jsonlogic.ast.JsonLogicArray; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluationException; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluator; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicExpression; import io.github.jamsesso.jsonlogic.utils.ArrayLike; -import io.github.jamsesso.jsonlogic.JsonLogic; -public class ArrayHasExpression implements JsonLogicExpression { +public class ArrayHasExpression extends JsonPathHandlerJsonLogicExpression implements JsonLogicExpression { public static final ArrayHasExpression SOME = new ArrayHasExpression(true); public static final ArrayHasExpression NONE = new ArrayHasExpression(false); @@ -23,13 +23,20 @@ public String key() { } @Override - public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data, String jsonPath) + public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data) throws JsonLogicEvaluationException { if (arguments.size() != 2) { - throw new JsonLogicEvaluationException(key() + " expects exactly 2 arguments", jsonPath); + throw new JsonLogicEvaluationException(key() + " expects exactly 2 arguments"); } - Object maybeArray = evaluator.evaluate(arguments.get(0), data, jsonPath + "[0]"); + Object maybeArray; + + try { + maybeArray = evaluator.evaluate(arguments.get(0), data, ""); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[0]"); + throw e; + } // Array objects can have null values according to http://jsonlogic.com/ if (maybeArray == null) { @@ -41,13 +48,18 @@ public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, O } if (!ArrayLike.isEligible(maybeArray)) { - throw new JsonLogicEvaluationException("first argument to " + key() + " must be a valid array", jsonPath + "[0]"); + throw new JsonLogicEvaluationException("first argument to " + key() + " must be a valid array", "[0]"); } for (Object item : new ArrayLike(maybeArray)) { - if(JsonLogic.truthy(evaluator.evaluate(arguments.get(1), item, jsonPath + "[1]"))) { - return isSome; - } + try { + if (JsonLogic.truthy(evaluator.evaluate(arguments.get(1), item, ""))) { + return isSome; + } + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[1]"); + throw e; + } } return !isSome; diff --git a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/FilterExpression.java b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/FilterExpression.java index 79364e9..11fce33 100644 --- a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/FilterExpression.java +++ b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/FilterExpression.java @@ -1,16 +1,16 @@ package io.github.jamsesso.jsonlogic.evaluator.expressions; -import io.github.jamsesso.jsonlogic.utils.ArrayLike; import io.github.jamsesso.jsonlogic.JsonLogic; import io.github.jamsesso.jsonlogic.ast.JsonLogicArray; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluationException; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluator; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicExpression; +import io.github.jamsesso.jsonlogic.utils.ArrayLike; import java.util.ArrayList; import java.util.List; -public class FilterExpression implements JsonLogicExpression { +public class FilterExpression extends JsonPathHandlerJsonLogicExpression implements JsonLogicExpression { public static final FilterExpression INSTANCE = new FilterExpression(); private FilterExpression() { @@ -23,24 +23,35 @@ public String key() { } @Override - public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data, String jsonPath) + public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data) throws JsonLogicEvaluationException { if (arguments.size() != 2) { - throw new JsonLogicEvaluationException("filter expects exactly 2 arguments", jsonPath); + throw new JsonLogicEvaluationException("filter expects exactly 2 arguments"); } - Object maybeArray = evaluator.evaluate(arguments.get(0), data, jsonPath + "[0]"); + Object maybeArray; + try { + maybeArray = evaluator.evaluate(arguments.get(0), data, ""); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[0]"); + throw e; + } if (!ArrayLike.isEligible(maybeArray)) { - throw new JsonLogicEvaluationException("first argument to filter must be a valid array", jsonPath + "[0]"); + throw new JsonLogicEvaluationException("first argument to filter must be a valid array", "[0]"); } List result = new ArrayList<>(); for (Object item : new ArrayLike(maybeArray)) { - if(JsonLogic.truthy(evaluator.evaluate(arguments.get(1), item, jsonPath + "[1]"))) { - result.add(item); - } + try { + if (JsonLogic.truthy(evaluator.evaluate(arguments.get(1), item, ""))) { + result.add(item); + } + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[1]"); + throw e; + } } return result; diff --git a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/IfExpression.java b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/IfExpression.java index 230a636..627f782 100644 --- a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/IfExpression.java +++ b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/IfExpression.java @@ -1,13 +1,13 @@ package io.github.jamsesso.jsonlogic.evaluator.expressions; -import io.github.jamsesso.jsonlogic.evaluator.JsonLogicExpression; import io.github.jamsesso.jsonlogic.JsonLogic; import io.github.jamsesso.jsonlogic.ast.JsonLogicArray; import io.github.jamsesso.jsonlogic.ast.JsonLogicNode; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluationException; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluator; +import io.github.jamsesso.jsonlogic.evaluator.JsonLogicExpression; -public class IfExpression implements JsonLogicExpression { +public class IfExpression extends JsonPathHandlerJsonLogicExpression implements JsonLogicExpression { public static final IfExpression IF = new IfExpression("if"); public static final IfExpression TERNARY = new IfExpression("?:"); @@ -23,8 +23,7 @@ public String key() { } @Override - public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data, - String jsonPath) + public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data) throws JsonLogicEvaluationException { if (arguments.size() < 1) { return null; @@ -32,29 +31,64 @@ public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, O // If there is only a single argument, simply evaluate & return that argument. if (arguments.size() == 1) { - return evaluator.evaluate(arguments.get(0), data, jsonPath + "[0]"); + try { + return evaluator.evaluate(arguments.get(0), data, ""); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[0]"); + throw e; + } } // If there is 2 arguments, only evaluate the second argument if the first argument is truthy. if (arguments.size() == 2) { - return JsonLogic.truthy(evaluator.evaluate(arguments.get(0), data, jsonPath + "[0]")) - ? evaluator.evaluate(arguments.get(1), data, jsonPath + "[1]") - : null; + try { + if (!JsonLogic.truthy(evaluator.evaluate(arguments.get(0), data, ""))) { + return null; + } + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[0]"); + throw e; + } + + try { + return evaluator.evaluate(arguments.get(1), data, ""); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[1]"); + throw e; + } } for (int i = 0; i < arguments.size() - 1; i += 2) { JsonLogicNode condition = arguments.get(i); JsonLogicNode resultIfTrue = arguments.get(i + 1); - if (JsonLogic.truthy(evaluator.evaluate(condition, data, String.format("%s[%d]", jsonPath, i)))) { - return evaluator.evaluate(resultIfTrue, data, String.format("%s[%d]", jsonPath, i + 1)); + try { + if (!JsonLogic.truthy(evaluator.evaluate(condition, data, ""))) { + continue; + } + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[" + i + "]"); + throw e; } + + try { + return evaluator.evaluate(resultIfTrue, data, ""); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[" + (i + 1) + "]"); + throw e; + } + } if ((arguments.size() & 1) == 0) { return null; } - return evaluator.evaluate(arguments.get(arguments.size() - 1), data, String.format("%s[%d]", jsonPath, arguments.size() - 1)); + try { + return evaluator.evaluate(arguments.get(arguments.size() - 1), data, ""); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[" + (arguments.size() - 1) + "]"); + throw e; + } } } diff --git a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/InequalityExpression.java b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/InequalityExpression.java index 747afb0..0cda458 100644 --- a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/InequalityExpression.java +++ b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/InequalityExpression.java @@ -5,7 +5,7 @@ import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluator; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicExpression; -public class InequalityExpression implements JsonLogicExpression { +public class InequalityExpression extends JsonPathHandlerJsonLogicExpression implements JsonLogicExpression { public static final InequalityExpression INSTANCE = new InequalityExpression(EqualityExpression.INSTANCE); private final EqualityExpression delegate; @@ -20,9 +20,9 @@ public String key() { } @Override - public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data, String jsonPath) + public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data) throws JsonLogicEvaluationException { - boolean result = (boolean) delegate.evaluate(evaluator, arguments, data, jsonPath); + boolean result = (boolean) delegate.evaluate(evaluator, arguments, data, ""); return !result; } diff --git a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/JsonPathHandlerJsonLogicExpression.java b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/JsonPathHandlerJsonLogicExpression.java new file mode 100644 index 0000000..1b33efc --- /dev/null +++ b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/JsonPathHandlerJsonLogicExpression.java @@ -0,0 +1,20 @@ +package io.github.jamsesso.jsonlogic.evaluator.expressions; + +import io.github.jamsesso.jsonlogic.ast.JsonLogicArray; +import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluationException; +import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluator; +import io.github.jamsesso.jsonlogic.evaluator.JsonLogicExpression; + +public abstract class JsonPathHandlerJsonLogicExpression implements JsonLogicExpression { + @Override + public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data, String jsonPath) throws JsonLogicEvaluationException { + try { + return evaluate(evaluator, arguments, data); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath(jsonPath); + throw e; + } + } + + public abstract Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data) throws JsonLogicEvaluationException; +} diff --git a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/LogicExpression.java b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/LogicExpression.java index 8412168..849a97e 100644 --- a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/LogicExpression.java +++ b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/LogicExpression.java @@ -7,7 +7,7 @@ import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluator; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicExpression; -public class LogicExpression implements JsonLogicExpression { +public class LogicExpression extends JsonPathHandlerJsonLogicExpression implements JsonLogicExpression { public static final LogicExpression AND = new LogicExpression(true); public static final LogicExpression OR = new LogicExpression(false); @@ -23,19 +23,25 @@ public String key() { } @Override - public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data, String jsonPath) + public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data) throws JsonLogicEvaluationException { if (arguments.size() < 1) { - throw new JsonLogicEvaluationException(key() + " operator expects at least 1 argument", jsonPath); + throw new JsonLogicEvaluationException(key() + " operator expects at least 1 argument"); } Object result = null; - int index = 0; - for (JsonLogicNode element : arguments) { - result = evaluator.evaluate(element, data, String.format("%s[%d]", jsonPath, index++)); + for (int index = 0; index < arguments.size() ; index++) { + JsonLogicNode element = arguments.get(index); - if ((isAnd && !JsonLogic.truthy(result)) || (!isAnd && JsonLogic.truthy(result))) { + try { + result = evaluator.evaluate(element, data, ""); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[" + (index) + "]"); + throw e; + } + + if ((isAnd && !JsonLogic.truthy(result)) || (!isAnd && JsonLogic.truthy(result))) { return result; } } diff --git a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/MapExpression.java b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/MapExpression.java index 9c3a863..d273cbc 100644 --- a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/MapExpression.java +++ b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/MapExpression.java @@ -10,7 +10,7 @@ import java.util.Collections; import java.util.List; -public class MapExpression implements JsonLogicExpression { +public class MapExpression extends JsonPathHandlerJsonLogicExpression implements JsonLogicExpression { public static final MapExpression INSTANCE = new MapExpression(); private MapExpression() { @@ -23,22 +23,34 @@ public String key() { } @Override - public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data, String jsonPath) + public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data) throws JsonLogicEvaluationException { if (arguments.size() != 2) { - throw new JsonLogicEvaluationException("map expects exactly 2 arguments", jsonPath); + throw new JsonLogicEvaluationException("map expects exactly 2 arguments"); } - Object maybeArray = evaluator.evaluate(arguments.get(0), data, jsonPath + "[0]"); + Object maybeArray; - if (!ArrayLike.isEligible(maybeArray)) { + try { + maybeArray = evaluator.evaluate(arguments.get(0), data, ""); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[0]"); + throw e; + } + + if (!ArrayLike.isEligible(maybeArray)) { return Collections.emptyList(); } List result = new ArrayList<>(); for (Object item : new ArrayLike(maybeArray)) { - result.add(evaluator.evaluate(arguments.get(1), item, jsonPath + "[1]")); + try { + result.add(evaluator.evaluate(arguments.get(1), item, "")); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[1]"); + throw e; + } } return result; diff --git a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/ReduceExpression.java b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/ReduceExpression.java index 35bb882..6ae0fe7 100644 --- a/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/ReduceExpression.java +++ b/src/main/java/io/github/jamsesso/jsonlogic/evaluator/expressions/ReduceExpression.java @@ -1,15 +1,15 @@ package io.github.jamsesso.jsonlogic.evaluator.expressions; -import io.github.jamsesso.jsonlogic.utils.ArrayLike; import io.github.jamsesso.jsonlogic.ast.JsonLogicArray; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluationException; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicEvaluator; import io.github.jamsesso.jsonlogic.evaluator.JsonLogicExpression; +import io.github.jamsesso.jsonlogic.utils.ArrayLike; import java.util.HashMap; import java.util.Map; -public class ReduceExpression implements JsonLogicExpression { +public class ReduceExpression extends JsonPathHandlerJsonLogicExpression implements JsonLogicExpression { public static final ReduceExpression INSTANCE = new ReduceExpression(); private ReduceExpression() { @@ -22,16 +22,30 @@ public String key() { } @Override - public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data, String jsonPath) + public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, Object data) throws JsonLogicEvaluationException { if (arguments.size() != 3) { - throw new JsonLogicEvaluationException("reduce expects exactly 3 arguments", jsonPath); + throw new JsonLogicEvaluationException("reduce expects exactly 3 arguments"); } - Object maybeArray = evaluator.evaluate(arguments.get(0), data, jsonPath + "[0]"); - Object accumulator = evaluator.evaluate(arguments.get(2), data, jsonPath + "[2]"); + Object maybeArray; + Object accumulator; + + try { + maybeArray = evaluator.evaluate(arguments.get(0), data, ""); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[0]"); + throw e; + } + + try { + accumulator = evaluator.evaluate(arguments.get(2), data, ""); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[2]"); + throw e; + } - if (!ArrayLike.isEligible(maybeArray)) { + if (!ArrayLike.isEligible(maybeArray)) { return accumulator; } @@ -40,7 +54,12 @@ public Object evaluate(JsonLogicEvaluator evaluator, JsonLogicArray arguments, O for (Object item : new ArrayLike(maybeArray)) { context.put("current", item); - context.put("accumulator", evaluator.evaluate(arguments.get(1), context, jsonPath + "[1]")); + try { + context.put("accumulator", evaluator.evaluate(arguments.get(1), context, "")); + } catch (JsonLogicEvaluationException e) { + e.prependPartialJsonPath("[1]"); + throw e; + } } return context.get("accumulator");