Skip to content

Commit

Permalink
[NIFI-10612] Initial check in of isJson code.
Browse files Browse the repository at this point in the history
[NIFI-10612] Made suggested change to only test subject value where it is formatted like a Json array or object.

This closes apache#6574

Signed-off-by: Mike Thomsen <[email protected]>
  • Loading branch information
dan-s1 authored and MikeThomsen committed Nov 2, 2022
1 parent 6fed5de commit 2afe2b3
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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! |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
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;
Expand Down Expand Up @@ -260,6 +261,7 @@
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<>();
Expand Down Expand Up @@ -814,6 +816,9 @@ private Evaluator<?> buildFunctionEvaluator(final Tree tree, final Evaluator<?>
}
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),
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2365,6 +2365,39 @@ public void testUuidsWithNamespace() {
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);
}
Expand Down
33 changes: 33 additions & 0 deletions nifi-docs/src/main/asciidoc/expression-language-guide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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`
|===================================================================




Expand Down

0 comments on commit 2afe2b3

Please sign in to comment.