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> 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 subject; + + public IsJsonEvaluator(Evaluator subject) { + this.subject = subject; + } + + @Override + public QueryResult 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 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 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", "BMW", "Fiat", the "someAttribute" attribute does not exist, the "emptyQuotedString" attribute value is "", the attribute "quotedString" has the value "someString", the attribute "integer" has the value 1234, the attribute "decimal" has the value 18.36, the attribute "trueAttr" has the value true, the attribute "falseAttr" has the value false and the "nullAttr" attribute has the value null, then the following expressions will provide the following results: + + + +.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` +|=================================================================== +