You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mt...@apache.org on 2022/11/02 19:47:46 UTC

[nifi] branch main updated: [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 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 <ds...@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 <mt...@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`
+|===================================================================
+