This is an automated email from the ASF dual-hosted git repository. mthomsen pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push: new 2afe2b36b9 [NIFI-10612] Initial check in of isJson code. [NIFI-10612] Made suggested change to only test subject value where it is formatted like a Json array or object. 2afe2b36b9 is described below commit 2afe2b36b98135689232b6c66e02916dc7e96e05 Author: dan-s1 <dsti...@gmail.com> AuthorDate: Wed Oct 12 20:16:14 2022 +0000 [NIFI-10612] Initial check in of isJson code. [NIFI-10612] Made suggested change to only test subject value where it is formatted like a Json array or object. This closes #6574 Signed-off-by: Mike Thomsen <mthom...@apache.org> --- .../language/antlr/AttributeExpressionLexer.g | 1 + .../language/antlr/AttributeExpressionParser.g | 2 +- .../language/compile/ExpressionCompiler.java | 5 ++ .../evaluation/functions/IsJsonEvaluator.java | 64 ++++++++++++++++++++++ .../attribute/expression/language/TestQuery.java | 33 +++++++++++ .../main/asciidoc/expression-language-guide.adoc | 33 +++++++++++ 6 files changed, 137 insertions(+), 1 deletion(-) diff --git a/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionLexer.g b/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionLexer.g index e0cb4caf24..734794f3be 100644 --- a/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionLexer.g +++ b/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionLexer.g @@ -145,6 +145,7 @@ BASE64_ENCODE : 'base64Encode'; BASE64_DECODE : 'base64Decode'; GET_STATE_VALUE: 'getStateValue'; EVALUATE_EL_STRING: 'evaluateELString'; +IS_JSON: 'isJson'; // 1 arg functions SUBSTRING_AFTER : 'substringAfter'; diff --git a/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionParser.g b/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionParser.g index 997dbd834a..c71719b92c 100644 --- a/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionParser.g +++ b/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionParser.g @@ -85,7 +85,7 @@ threeArgString: ((JSON_PATH_PUT) LPAREN! anyArg COMMA! anyArg COMMA! anyArg RPAR fiveArgString : GET_DELIMITED_FIELD LPAREN! anyArg (COMMA! anyArg (COMMA! anyArg (COMMA! anyArg (COMMA! anyArg)?)?)?)? RPAREN!; // functions that return Booleans -zeroArgBool : (IS_NULL | NOT_NULL | IS_EMPTY | NOT) LPAREN! RPAREN!; +zeroArgBool : (IS_NULL | NOT_NULL | IS_EMPTY | NOT | IS_JSON) LPAREN! RPAREN!; oneArgBool : ((FIND | MATCHES | EQUALS_IGNORE_CASE) LPAREN! anyArg RPAREN!) | (GREATER_THAN | LESS_THAN | GREATER_THAN_OR_EQUAL | LESS_THAN_OR_EQUAL) LPAREN! anyArg RPAREN! | (EQUALS) LPAREN! anyArg RPAREN! | diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/compile/ExpressionCompiler.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/compile/ExpressionCompiler.java index 17a0173ec5..05bc23a2ac 100644 --- a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/compile/ExpressionCompiler.java +++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/compile/ExpressionCompiler.java @@ -68,6 +68,7 @@ import org.apache.nifi.attribute.expression.language.evaluation.functions.InEval import org.apache.nifi.attribute.expression.language.evaluation.functions.IndexOfEvaluator; import org.apache.nifi.attribute.expression.language.evaluation.functions.InstantFormatEvaluator; import org.apache.nifi.attribute.expression.language.evaluation.functions.IsEmptyEvaluator; +import org.apache.nifi.attribute.expression.language.evaluation.functions.IsJsonEvaluator; import org.apache.nifi.attribute.expression.language.evaluation.functions.IsNullEvaluator; import org.apache.nifi.attribute.expression.language.evaluation.functions.JsonPathAddEvaluator; import org.apache.nifi.attribute.expression.language.evaluation.functions.JsonPathDeleteEvaluator; @@ -260,6 +261,7 @@ import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpre import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.UUID5; import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.WHOLE_NUMBER; import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.EVALUATE_EL_STRING; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.IS_JSON; public class ExpressionCompiler { private final Set<Evaluator<?>> evaluators = new HashSet<>(); @@ -814,6 +816,9 @@ public class ExpressionCompiler { } return addToken(new InEvaluator(toStringEvaluator(subjectEvaluator), list), "in"); } + case IS_JSON: + verifyArgCount(argEvaluators, 0, "isJson"); + return addToken(new IsJsonEvaluator(toStringEvaluator(subjectEvaluator)), "isJson"); case FIND: { verifyArgCount(argEvaluators, 1, "find"); return addToken(new FindEvaluator(toStringEvaluator(subjectEvaluator), diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/IsJsonEvaluator.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/IsJsonEvaluator.java new file mode 100644 index 0000000000..4d3815dc26 --- /dev/null +++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/IsJsonEvaluator.java @@ -0,0 +1,64 @@ +/* + * 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.nifi.attribute.expression.language.evaluation.functions; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.attribute.expression.language.EvaluationContext; +import org.apache.nifi.attribute.expression.language.evaluation.BooleanEvaluator; +import org.apache.nifi.attribute.expression.language.evaluation.BooleanQueryResult; +import org.apache.nifi.attribute.expression.language.evaluation.Evaluator; +import org.apache.nifi.attribute.expression.language.evaluation.QueryResult; + +import java.io.IOException; + +public class IsJsonEvaluator extends BooleanEvaluator { + private static final ObjectMapper MAPPER = new ObjectMapper(); + private final Evaluator<String> subject; + + public IsJsonEvaluator(Evaluator<String> subject) { + this.subject = subject; + } + + @Override + public QueryResult<Boolean> evaluate(EvaluationContext evaluationContext) { + final String subjectValue = subject.evaluate(evaluationContext).getValue(); + if (StringUtils.isNotBlank(subjectValue) + && (isPossibleJsonArray(subjectValue) || isPossibleJsonObject(subjectValue))) { + try { + MAPPER.readTree(subjectValue); + return new BooleanQueryResult(true); + } catch (IOException ignored) { + //IOException ignored + } + } + return new BooleanQueryResult(false); + } + + private boolean isPossibleJsonArray(String subject) { + return subject.startsWith("[") && subject.endsWith("]"); + } + + private boolean isPossibleJsonObject(String subject) { + return subject.startsWith("{") && subject.endsWith("}"); + } + + @Override + public Evaluator<?> getSubjectEvaluator() { + return subject; + } +} diff --git a/nifi-commons/nifi-expression-language/src/test/java/org/apache/nifi/attribute/expression/language/TestQuery.java b/nifi-commons/nifi-expression-language/src/test/java/org/apache/nifi/attribute/expression/language/TestQuery.java index 7656548089..4db4f0a943 100644 --- a/nifi-commons/nifi-expression-language/src/test/java/org/apache/nifi/attribute/expression/language/TestQuery.java +++ b/nifi-commons/nifi-expression-language/src/test/java/org/apache/nifi/attribute/expression/language/TestQuery.java @@ -2365,6 +2365,39 @@ public class TestQuery { verifyEquals("${myattr0:UUID5(${UUID()}):length()}", attributes, 36L); } + @Test + void testIsJson() { + final Map<String, String> attributes = new HashMap<>(); + attributes.put("jsonObj", "{\"name\":\"John\", \"age\":30, \"car\":null}"); + attributes.put("jsonObjMissingStartingBrace", "\"name\":\"John\", \"age\":30, \"car\":null}"); + attributes.put("jsonObjMissingEndingBrace", "{\"name\":\"John\", \"age\":30, \"car\":null"); + attributes.put("jsonArray", "[\"Ford\", \"BMW\", \"Fiat\"]"); + attributes.put("jsonArrayMissingStartingBracket", "\"Ford\", \"BMW\", \"Fiat\"]"); + attributes.put("jsonArrayMissingEndingBracket", "[\"Ford\", \"BMW\", \"Fiat\""); + attributes.put("emptyQuotedString", "\"\""); + attributes.put("quotedString", "\"someString\""); + attributes.put("integer", "1234"); + attributes.put("decimal", "18.36"); + attributes.put("trueAttr", "true"); + attributes.put("falseAttr", "false"); + attributes.put("nullAttr", "null"); + + verifyEquals("${jsonObj:isJson()}", attributes, true); + verifyEquals("${jsonObjMissingStartingBrace:isJson()}", attributes, false); + verifyEquals("${jsonObjMissingEndingBrace:isJson()}", attributes, false); + verifyEquals("${jsonArray:isJson()}", attributes, true); + verifyEquals("${jsonArrayMissingStartingBracket:isJson()}", attributes, false); + verifyEquals("${jsonArrayMissingEndingBracket:isJson()}", attributes, false); + verifyEquals("${someAttribute:isJson()}", attributes, false); + verifyEquals("${emptyQuotedString:isJson()}", attributes, false); + verifyEquals("${quotedString:isJson()}", attributes, false); + verifyEquals("${integer:isJson()}", attributes, false); + verifyEquals("${decimal:isJson()}", attributes, false); + verifyEquals("${trueAttr:isJson()}", attributes, false); + verifyEquals("${falseAttr:isJson()}", attributes, false); + verifyEquals("${nullAttr:isJson()}", attributes, false); + } + private void verifyEquals(final String expression, final Map<String, String> attributes, final Object expectedResult) { verifyEquals(expression,attributes, null, ParameterLookup.EMPTY, expectedResult); } diff --git a/nifi-docs/src/main/asciidoc/expression-language-guide.adoc b/nifi-docs/src/main/asciidoc/expression-language-guide.adoc index 173100efbf..8fab2761b1 100644 --- a/nifi-docs/src/main/asciidoc/expression-language-guide.adoc +++ b/nifi-docs/src/main/asciidoc/expression-language-guide.adoc @@ -569,6 +569,39 @@ the following results: | `${filename:isNull():not():ifElse('found', 'not_found')}` | `found` |=================================================================== +[.function] +=== isJson +*Description*: [.description]#The `isJson` function returns `true` if the subject is a JSON array or a JSON object, `false` otherwise. This is typically used to determine whether an attribute is JSON in order to allow for a follow-on JSONPath query. Although technically there are other valid JSON types such as string, number, boolean and null, this method is only concerned with the primary JSON objects queried with JSONPath , arrays and objects. # + +*Subject Type*: [.subject]#Any# + +*Arguments*: No arguments + +*Return Type*: [.returnType]#Boolean# + +*Examples*: If the attribute "jsonObj" has the value {"name":"John", "age":30, "car":null}, the attribute jsonObjMissingStartingBrace has the value "name":"John", "age":30, "car":null}, the attribute jsonObjMissingEndingBrace has the value {"name":"John", "age":30, "car":null, the attribute "jsonArray" has the value ["Ford", "BMW", "Fiat"], the attribute jsonArrayMissingStartingBracket has the value "Ford", "BMW", "Fiat"], the attribute jsonArrayMissingEndingBracket has the value ["Ford" [...] + + + +.isJson Examples +|=================================================================== +| Expression | Value +| `${jsonObj:isJson()}` | `true` +| `${jsonObjMissingStartingBrace:isJson()}` | `false` +| `${jsonObjMissingEndingBrace:isJson()}` | `false` +| `${jsonArray:isJson()}` | `true` +| `${jsonArrayMissingStartingBracket:isJson()}` | `false` +| `${jsonArrayMissingEndingBracket:isJson()}` | `false` +| `${someAttribute:isJson()}` | `false` +| `${emptyQuotedString:isJson())` | `false` +| `${quotedString:isJson()}` | `false` +| `${integer:isJson()}` | `false` +| `${decimal:isJson()}` | `false` +| `${trueAttr:isJson()}` | `false` +| `${falseAttr:isJson()}` | `false` +| `${nullAttr:isJson()}` | `false` +|=================================================================== +