[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 <mthomsen@apache.org>
This commit is contained in:
dan-s1 2022-10-12 20:16:14 +00:00 committed by Mike Thomsen
parent 6fed5dee17
commit 2afe2b36b9
6 changed files with 137 additions and 1 deletions

View File

@ -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';

View File

@ -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! |

View File

@ -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),

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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`
|===================================================================