NIFI-6675 - This closes #3741. Added support for JsonPath put operation

This commit is contained in:
mans2singh 2019-09-15 14:49:44 -04:00 committed by Joe Witt
parent 34112519c2
commit 79e4f360f4
No known key found for this signature in database
GPG Key ID: 9093BF854F811A1A
7 changed files with 184 additions and 5 deletions

View File

@ -190,6 +190,7 @@ REPLACE_ALL : 'replaceAll';
IF_ELSE : 'ifElse';
JSON_PATH_SET : 'jsonPathSet';
JSON_PATH_ADD : 'jsonPathAdd';
JSON_PATH_PUT : 'jsonPathPut';
PAD_LEFT : 'padLeft';
PAD_RIGHT : 'padRight';

View File

@ -80,6 +80,7 @@ oneArgString : ((SUBSTRING_BEFORE | SUBSTRING_BEFORE_LAST | SUBSTRING_AFTER | SU
(TO_RADIX LPAREN! anyArg (COMMA! anyArg)? RPAREN!);
twoArgString : ((REPLACE | REPLACE_FIRST | REPLACE_ALL | IF_ELSE | JSON_PATH_SET | JSON_PATH_ADD) LPAREN! anyArg COMMA! anyArg RPAREN!) |
((SUBSTRING | FORMAT | PAD_LEFT | PAD_RIGHT) LPAREN! anyArg (COMMA! anyArg)? RPAREN!);
threeArgString: ((JSON_PATH_PUT) LPAREN! anyArg COMMA! anyArg COMMA! anyArg RPAREN!);
fiveArgString : GET_DELIMITED_FIELD LPAREN! anyArg (COMMA! anyArg (COMMA! anyArg (COMMA! anyArg (COMMA! anyArg)?)?)?)? RPAREN!;
// functions that return Booleans
@ -98,7 +99,7 @@ oneArgNum : ((INDEX_OF | LAST_INDEX_OF) LPAREN! anyArg RPAREN!) |
oneOrTwoArgNum : MATH LPAREN! anyArg (COMMA! anyArg)? RPAREN!;
zeroOrOneOrTwoArgNum : TO_DATE LPAREN! anyArg? (COMMA! anyArg)? RPAREN!;
stringFunctionRef : zeroArgString | oneArgString | twoArgString | fiveArgString;
stringFunctionRef : zeroArgString | oneArgString | twoArgString | threeArgString | fiveArgString;
booleanFunctionRef : zeroArgBool | oneArgBool | multiArgBool;
numberFunctionRef : zeroArgNum | oneArgNum | oneOrTwoArgNum | zeroOrOneOrTwoArgNum;

View File

@ -68,6 +68,7 @@ import org.apache.nifi.attribute.expression.language.evaluation.functions.JsonPa
import org.apache.nifi.attribute.expression.language.evaluation.functions.JsonPathDeleteEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.JsonPathEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.JsonPathSetEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.JsonPathPutEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.LastIndexOfEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.LengthEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.LessThanEvaluator;
@ -182,6 +183,7 @@ import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpre
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.JOIN;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.JSON_PATH;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.JSON_PATH_ADD;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.JSON_PATH_PUT;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.JSON_PATH_DELETE;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.JSON_PATH_SET;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.LAST_INDEX_OF;
@ -970,6 +972,20 @@ public class ExpressionCompiler {
toStringEvaluator(argEvaluators.get(0), "first argument to jsonPathAdd"),
valueEvaluator), "jsonPathAdd");
}
case JSON_PATH_PUT: {
verifyArgCount(argEvaluators, 3, "jsonPathPut");
Evaluator<?> argValueEvaluator = argEvaluators.get(1);
String valueLocation = "second argument to jsonPathPut";
Evaluator<?> valueEvaluator = getJsonPathUpdateEvaluator(argValueEvaluator, valueLocation);
Evaluator<?> argKeyEvaluator = argEvaluators.get(2);
String keyLocation = "third argument to jsonPathPut";
Evaluator<?> keyEvaluator = getJsonPathUpdateEvaluator(argKeyEvaluator, keyLocation);
return addToken(new JsonPathPutEvaluator(toStringEvaluator(subjectEvaluator),
toStringEvaluator(argEvaluators.get(0), "first argument to jsonPathPut"),
toStringEvaluator(keyEvaluator, keyLocation),
valueEvaluator), "jsonPathPut");
}
case IF_ELSE: {
verifyArgCount(argEvaluators, 2, "ifElse");
return addToken(new IfElseEvaluator(toBooleanEvaluator(subjectEvaluator),

View File

@ -0,0 +1,63 @@
/*
* 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.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import org.apache.nifi.attribute.expression.language.EvaluationContext;
import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
import org.apache.nifi.attribute.expression.language.evaluation.QueryResult;
import org.apache.nifi.attribute.expression.language.evaluation.StringQueryResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* JsonPathPutEvaluator allows setting or adding a key and scalar value at the specified existing path
*/
public class JsonPathPutEvaluator extends JsonPathUpdateEvaluator {
private static final Logger LOGGER = LoggerFactory.getLogger(JsonPathPutEvaluator.class);
protected Evaluator<?> keyEvaluator;
public JsonPathPutEvaluator(final Evaluator<String> subject, final Evaluator<String> jsonPathExp, final Evaluator<?> valueEvaluator, final Evaluator<?> keyEvaluator) {
super(subject, jsonPathExp, valueEvaluator);
this.keyEvaluator = keyEvaluator;
}
@Override
public QueryResult<String> evaluate(EvaluationContext context) {
DocumentContext documentContext = getDocumentContext(context);
final JsonPath compiledJsonPath = getJsonPath(context);
final Object value = valueEvaluator.evaluate(context).getValue();
final String key = keyEvaluator.evaluate(context).getValue().toString();
String result;
try {
result = documentContext.put(compiledJsonPath, key, value).jsonString();
} catch (Exception e) {
LOGGER.error("Failed to put value " + value + " at key " + key + " at path " + compiledJsonPath + " with error " + e.getLocalizedMessage(), e);
// assume the path did not match anything in the document
return EMPTY_RESULT;
}
return new StringQueryResult(getResultRepresentation(result, EMPTY_RESULT.getValue()));
}
}

View File

@ -18,6 +18,7 @@ package org.apache.nifi.attribute.expression.language.evaluation.functions;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.nifi.attribute.expression.language.EvaluationContext;
import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
import org.apache.nifi.attribute.expression.language.evaluation.QueryResult;
@ -27,6 +28,8 @@ import org.slf4j.LoggerFactory;
/**
* JsonPathUpdateEvaluator is base class for updating attributes
*
* Subclasses need to implement {@link #updateAttribute} method otherwise it throws {@link NotImplementedException}
*/
public abstract class JsonPathUpdateEvaluator extends JsonPathBaseEvaluator {
@ -59,12 +62,15 @@ public abstract class JsonPathUpdateEvaluator extends JsonPathBaseEvaluator {
}
/**
* Update the attribute at the specified path
* Update the attribute at the specified path. The subclasses will need to implement this method.
* @param documentContext the document to be updated
* @param jsonPath the path to update
* @param value the value to be applied at the specified path
* @return the updated DocumentContext
* @throws NotImplementedException if operation is not implemented
*/
public abstract DocumentContext updateAttribute(DocumentContext documentContext, JsonPath jsonPath, Object value);
public DocumentContext updateAttribute(DocumentContext documentContext, JsonPath jsonPath, Object value) {
throw new NotImplementedException("Please implement updateAttribute method in the implementation class");
}
}

View File

@ -375,9 +375,9 @@ public class TestQuery {
verifyEquals(targetAttribute, attributes, originalValue);
}
String addressBookAfterDelete = Query.evaluateExpressions(updateExpression, attributes, ParameterLookup.EMPTY);
String addressBookAfterUpdate = Query.evaluateExpressions(updateExpression, attributes, ParameterLookup.EMPTY);
attributes.clear();
attributes.put("json", addressBookAfterDelete);
attributes.put("json", addressBookAfterUpdate);
verifyAddressBookAttributes(addressBook, attributes, targetAttribute, updatedValue);
@ -486,6 +486,45 @@ public class TestQuery {
"");
}
@Test
public void testJsonPathPutRootLevelMiddlenameTuron() throws IOException {
Map<String,String> attributes = verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_EMPTY,
"",
"${json:jsonPathPut('$','middlename','Turon')}",
"");
verifyEquals("${json:jsonPath('$.middlename')}", attributes, "Turon");
}
@Test
public void testJsonPathPutCountryToMap() throws IOException {
Map<String,String> attributes = verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_EMPTY,
"",
"${json:jsonPathPut('$.address','country','US')}",
"");
verifyEquals("${json:jsonPath('$.address.country')}", attributes, "US");
}
@Test
public void testJsonPathPutElementToArray() throws IOException {
Map<String,String> attributes = verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_EMPTY,
"",
"${json:jsonPathPut('$.phoneNumbers[1]', 'backup', '212-555-1212')}",
"");
verifyEquals("${json:jsonPath('$.phoneNumbers[1].backup')}", attributes, "212-555-1212");
}
@Test
public void testJsonPathPutOverwriteFirstNameToJimmy() throws IOException {
Map<String,String> attributes = verifyJsonPathExpressions(
ADDRESS_BOOK_JSON_PATH_FIRST_NAME,
"John",
"${json:jsonPathPut('$','firstName','Jimmy')}",
"Jimmy");
}
@Test
public void testEmbeddedExpressionsAndQuotesWithProperties() {
final Map<String, String> attributes = new HashMap<>();

View File

@ -1806,6 +1806,59 @@ form of the updated JSON.#
An empty subject value or a subject value with an invalid JSON document results in an exception bulletin.
[.function]
=== jsonPathPut
*Description*: [.description]#The `jsonPathPut` function puts the key and scalar value at the specified JsonPath on a Subject JSON and returns string
form of the updated JSON.#
*Subject Type*: [.subject]#String#
*Arguments*:
- [.argName]#_jsonPath_# : [.argDesc]#the JSON path expression to set value on the Subject.#
- [.argName]#_value_# : [.argDesc]#the value expression to be set on the specified path on Subject.#
- [.argName]#_key_# : [.argDesc]#the key expression with the associated value the specified path on Subject.#
*Return Type*: [.returnType]#String#
*Examples*: If the "myJson" attribute is
..........
{
"firstName": "John",
"lastName": "Smith",
"age": 25,
"voter" : true,
"height" : 6.1,
"address" : {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021-3100"
},
"phoneNumbers": [
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "office",
"number": "646 555-4567"
}
],
"nicknames" : []
}
..........
.jsonPathPut Examples
|===================================================================
| Expression | Value
| `${myJson:jsonPathPut('$','middlename','Turon')}` | `{"firstName":"James", lastName":"Smith", "middlename": "Turon", "age":25, "voter":true, "height":6.1, "address":{"streetAddress":"21 2nd Street", "city":"New York", "state":"NY", "postalCode":"10021-3100"}, "phoneNumbers":[{"type":"home", "number":"212 555-1234"}, {"type":"office", "number":"646 555-4567"}]}`
|===================================================================
An empty subject value or a subject value with an invalid JSON document results in an exception bulletin.
[[numbers]]
== Mathematical Operations and Numeric Manipulation