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 index 4d3815dc26f1..4f26dd0b0d6f 100644 --- 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 @@ -17,7 +17,6 @@ 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; @@ -37,13 +36,15 @@ public IsJsonEvaluator(Evaluator 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 + if (subjectValue != null) { + final String trimmedSubjectValue = subjectValue.trim(); + if (isPossibleJsonArray(trimmedSubjectValue) || isPossibleJsonObject(trimmedSubjectValue)) { + try { + MAPPER.readTree(trimmedSubjectValue); + return new BooleanQueryResult(true); + } catch (IOException ignored) { + //IOException ignored + } } } return new BooleanQueryResult(false); 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 3270bd69b837..2bb819233770 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 @@ -2498,6 +2498,7 @@ public void testUuidsWithNamespace() { void testIsJson() { final Map attributes = new HashMap<>(); attributes.put("jsonObj", "{\"name\":\"John\", \"age\":30, \"car\":null}"); + attributes.put("jsonObjWithLeadingAndTrailingingSpaces", "\n{\"name\":\"John\", \"age\":30, \"car\":null}\n"); attributes.put("jsonObjMissingStartingBrace", "\"name\":\"John\", \"age\":30, \"car\":null}"); attributes.put("jsonObjMissingEndingBrace", "{\"name\":\"John\", \"age\":30, \"car\":null"); attributes.put("jsonArray", "[\"Ford\", \"BMW\", \"Fiat\"]"); @@ -2512,6 +2513,7 @@ void testIsJson() { attributes.put("nullAttr", "null"); verifyEquals("${jsonObj:isJson()}", attributes, true); + verifyEquals("${jsonObjWithLeadingAndTrailingingSpaces:isJson()}", attributes, true); verifyEquals("${jsonObjMissingStartingBrace:isJson()}", attributes, false); verifyEquals("${jsonObjMissingEndingBrace:isJson()}", attributes, false); verifyEquals("${jsonArray:isJson()}", attributes, true); diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/AttributesToJSON.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/AttributesToJSON.java index 17adfeab3371..41eb455f74ed 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/AttributesToJSON.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/AttributesToJSON.java @@ -324,11 +324,14 @@ private Map getFormattedAttributes(Map flowFileA Map formattedAttributes = new LinkedHashMap<>(); for (Map.Entry entry : flowFileAttributes.entrySet()) { String value = (String) entry.getValue(); - if (StringUtils.isNotBlank(value) && (isPossibleJsonArray(value) || isPossibleJsonObject(value))) { - formattedAttributes.put(entry.getKey(), OBJECT_MAPPER.readTree(value)); - } else { - formattedAttributes.put(entry.getKey(), value); + if (value != null) { + final String trimmedValue = value.trim(); + if (isPossibleJsonArray(trimmedValue) || isPossibleJsonObject(trimmedValue)) { + formattedAttributes.put(entry.getKey(), OBJECT_MAPPER.readTree(trimmedValue)); + continue; + } } + formattedAttributes.put(entry.getKey(), value); } return formattedAttributes; diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestAttributesToJSON.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestAttributesToJSON.java index a0c0e9e17910..fbcc3c96e5ce 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestAttributesToJSON.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestAttributesToJSON.java @@ -17,6 +17,7 @@ package org.apache.nifi.processors.standard; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.flowfile.attributes.CoreAttributes; @@ -42,6 +43,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; @@ -54,6 +56,8 @@ public class TestAttributesToJSON { private static final String TEST_ATTRIBUTE_KEY = "TestAttribute"; private static final String TEST_ATTRIBUTE_VALUE = "TestValue"; + private static final ObjectMapper MAPPER = new ObjectMapper(); + @Test public void testInvalidUserSuppliedAttributeList() { final TestRunner testRunner = TestRunners.newTestRunner(new AttributesToJSON()); @@ -109,8 +113,7 @@ public void testNullValueForEmptyAttribute() throws Exception { String json = testRunner.getFlowFilesForRelationship(AttributesToJSON.REL_SUCCESS) .get(0).getAttribute(AttributesToJSON.JSON_ATTRIBUTE_NAME); - ObjectMapper mapper = new ObjectMapper(); - Map val = mapper.readValue(json, HashMap.class); + Map val = MAPPER.readValue(json, HashMap.class); assertNull(val.get(NON_PRESENT_ATTRIBUTE_KEY)); } @@ -139,8 +142,7 @@ public void testEmptyStringValueForEmptyAttribute() throws Exception { String json = testRunner.getFlowFilesForRelationship(AttributesToJSON.REL_SUCCESS) .get(0).getAttribute(AttributesToJSON.JSON_ATTRIBUTE_NAME); - ObjectMapper mapper = new ObjectMapper(); - Map val = mapper.readValue(json, HashMap.class); + Map val = MAPPER.readValue(json, HashMap.class); assertEquals(val.get(NON_PRESENT_ATTRIBUTE_KEY), ""); } @@ -188,8 +190,7 @@ public void testAttributes_emptyListUserSpecifiedAttributes() throws Exception { String json = testRunner.getFlowFilesForRelationship(AttributesToJSON.REL_SUCCESS) .get(0).getAttribute(AttributesToJSON.JSON_ATTRIBUTE_NAME); - ObjectMapper mapper = new ObjectMapper(); - Map val = mapper.readValue(json, HashMap.class); + Map val = MAPPER.readValue(json, HashMap.class); assertTrue(val.get(TEST_ATTRIBUTE_KEY).equals(TEST_ATTRIBUTE_VALUE)); } @@ -234,8 +235,7 @@ public void testAttribute_singleUserDefinedAttribute() throws Exception { String json = testRunner.getFlowFilesForRelationship(AttributesToJSON.REL_SUCCESS) .get(0).getAttribute(AttributesToJSON.JSON_ATTRIBUTE_NAME); - ObjectMapper mapper = new ObjectMapper(); - Map val = mapper.readValue(json, HashMap.class); + Map val = MAPPER.readValue(json, HashMap.class); assertTrue(val.get(TEST_ATTRIBUTE_KEY).equals(TEST_ATTRIBUTE_VALUE)); assertTrue(val.size() == 1); } @@ -262,8 +262,7 @@ public void testAttribute_singleUserDefinedAttributeWithWhiteSpace() throws Exce String json = testRunner.getFlowFilesForRelationship(AttributesToJSON.REL_SUCCESS) .get(0).getAttribute(AttributesToJSON.JSON_ATTRIBUTE_NAME); - ObjectMapper mapper = new ObjectMapper(); - Map val = mapper.readValue(json, HashMap.class); + Map val = MAPPER.readValue(json, HashMap.class); assertTrue(val.get(TEST_ATTRIBUTE_KEY).equals(TEST_ATTRIBUTE_VALUE)); assertTrue(val.size() == 1); } @@ -290,8 +289,7 @@ public void testAttribute_singleNonExistingUserDefinedAttribute() throws Excepti String json = testRunner.getFlowFilesForRelationship(AttributesToJSON.REL_SUCCESS) .get(0).getAttribute(AttributesToJSON.JSON_ATTRIBUTE_NAME); - ObjectMapper mapper = new ObjectMapper(); - Map val = mapper.readValue(json, HashMap.class); + Map val = MAPPER.readValue(json, HashMap.class); //If a Attribute is requested but does not exist then it is placed in the JSON with an empty string assertTrue(val.get("NonExistingAttribute").equals("")); @@ -320,8 +318,7 @@ public void testAttribute_noIncludeCoreAttributesUserDefined() throws IOExceptio String json = testRunner.getFlowFilesForRelationship(AttributesToJSON.REL_SUCCESS) .get(0).getAttribute(AttributesToJSON.JSON_ATTRIBUTE_NAME); - ObjectMapper mapper = new ObjectMapper(); - Map val = mapper.readValue(json, HashMap.class); + Map val = MAPPER.readValue(json, HashMap.class); assertEquals(TEST_ATTRIBUTE_VALUE, val.get(TEST_ATTRIBUTE_KEY)); assertEquals(TEST_ATTRIBUTE_VALUE, val.get(CoreAttributes.PATH.key())); assertEquals(2, val.size()); @@ -349,8 +346,7 @@ public void testAttribute_noIncludeCoreAttributesRegex() throws IOException { String json = testRunner.getFlowFilesForRelationship(AttributesToJSON.REL_SUCCESS) .get(0).getAttribute(AttributesToJSON.JSON_ATTRIBUTE_NAME); - ObjectMapper mapper = new ObjectMapper(); - Map val = mapper.readValue(json, HashMap.class); + Map val = MAPPER.readValue(json, HashMap.class); assertEquals(TEST_ATTRIBUTE_VALUE, val.get(CoreAttributes.PATH.key())); assertEquals(1, val.size()); } @@ -372,8 +368,7 @@ public void testAttribute_noIncludeCoreAttributesContent() throws IOException { testRunner.assertTransferCount(AttributesToJSON.REL_SUCCESS, 1); testRunner.assertTransferCount(AttributesToJSON.REL_FAILURE, 0); - ObjectMapper mapper = new ObjectMapper(); - Map val = mapper.readValue(testRunner.getFlowFilesForRelationship(AttributesToJSON.REL_SUCCESS).get(0).toByteArray(), HashMap.class); + Map val = MAPPER.readValue(testRunner.getFlowFilesForRelationship(AttributesToJSON.REL_SUCCESS).get(0).toByteArray(), HashMap.class); assertEquals(TEST_ATTRIBUTE_VALUE, val.get(TEST_ATTRIBUTE_KEY)); assertEquals(1, val.size()); } @@ -399,7 +394,7 @@ public void testAttribute_includeCoreAttributesContent() throws IOException { assertEquals(AttributesToJSON.APPLICATION_JSON, flowFile.getAttribute(CoreAttributes.MIME_TYPE.key())); - Map val = new ObjectMapper().readValue(flowFile.toByteArray(), HashMap.class); + Map val = MAPPER.readValue(flowFile.toByteArray(), HashMap.class); assertEquals(3, val.size()); Set coreAttributes = Arrays.stream(CoreAttributes.values()).map(CoreAttributes::key).collect(Collectors.toSet()); val.keySet().forEach(k -> assertTrue(coreAttributes.contains(k))); @@ -425,7 +420,7 @@ public void testAttribute_includeCoreAttributesAttribute() throws IOException { assertNull(flowFile.getAttribute(CoreAttributes.MIME_TYPE.key())); - Map val = new ObjectMapper().readValue(flowFile.getAttribute(AttributesToJSON.JSON_ATTRIBUTE_NAME), HashMap.class); + Map val = MAPPER.readValue(flowFile.getAttribute(AttributesToJSON.JSON_ATTRIBUTE_NAME), HashMap.class); assertEquals(3, val.size()); Set coreAttributes = Arrays.stream(CoreAttributes.values()).map(CoreAttributes::key).collect(Collectors.toSet()); val.keySet().forEach(k -> assertTrue(coreAttributes.contains(k))); @@ -455,7 +450,7 @@ public void testAttributesRegex() throws IOException { MockFlowFile flowFile = testRunner.getFlowFilesForRelationship(AttributesToJSON.REL_SUCCESS).get(0); - Map val = new ObjectMapper().readValue(flowFile.getAttribute(AttributesToJSON.JSON_ATTRIBUTE_NAME), HashMap.class); + Map val = MAPPER.readValue(flowFile.getAttribute(AttributesToJSON.JSON_ATTRIBUTE_NAME), HashMap.class); assertTrue(val.keySet().contains("delimited.header.column.1")); assertTrue(val.keySet().contains("delimited.header.column.2")); assertTrue(val.keySet().contains("delimited.header.column.3")); @@ -485,7 +480,7 @@ public void testAttributeWithNestedJsonOutputAsJsonInContent(String nestedJson, List flowFilesForRelationship = testRunner.getFlowFilesForRelationship(AttributesToJSON.REL_SUCCESS); MockFlowFile flowFile = flowFilesForRelationship.get(0); assertEquals(AttributesToJSON.APPLICATION_JSON, flowFile.getAttribute(CoreAttributes.MIME_TYPE.key())); - Map val = new ObjectMapper().readValue(flowFile.toByteArray(), Map.class); + Map val = MAPPER.readValue(flowFile.toByteArray(), Map.class); assertInstanceOf(expectedClass, val.get(TEST_ATTRIBUTE_KEY)); } @@ -516,8 +511,7 @@ public void testAttributeWithNestedJsonOutputAsJsonInAttribute(String nestedJson String json = testRunner.getFlowFilesForRelationship(AttributesToJSON.REL_SUCCESS) .get(0).getAttribute(AttributesToJSON.JSON_ATTRIBUTE_NAME); - ObjectMapper mapper = new ObjectMapper(); - Map val = mapper.readValue(json, Map.class); + Map val = MAPPER.readValue(json, Map.class); assertInstanceOf(expectedClass, val.get(TEST_ATTRIBUTE_KEY)); } @@ -560,8 +554,34 @@ public void testAttributeWithNestedJsonOutputAsStringInAttribute(String nestedJs String json = testRunner.getFlowFilesForRelationship(AttributesToJSON.REL_SUCCESS) .get(0).getAttribute(AttributesToJSON.JSON_ATTRIBUTE_NAME); - ObjectMapper mapper = new ObjectMapper(); - Map val = mapper.readValue(json, Map.class); + Map val = MAPPER.readValue(json, Map.class); assertInstanceOf(String.class, val.get(TEST_ATTRIBUTE_KEY)); } + + @Test + public void testAttributeWithJsonBetweenLeadingAndTrailingSpaces() throws JsonProcessingException { + final String jsonBetweenLeadingAndTrailingSpaces = "\n{\n" + + " \"fruit\": \"Apple\",\n" + + " \"size\": \"Large\",\n" + + " \"color\": \"Red\"\n" + + "}\n"; + assertDoesNotThrow(() -> MAPPER.readValue(jsonBetweenLeadingAndTrailingSpaces, Map.class)); + + final TestRunner testRunner = TestRunners.newTestRunner(new AttributesToJSON()); + testRunner.setProperty(AttributesToJSON.DESTINATION, AttributesToJSON.DESTINATION_CONTENT); + testRunner.setProperty(AttributesToJSON.JSON_HANDLING_STRATEGY, + AttributesToJSON.JsonHandlingStrategy.NESTED.getValue()); + + ProcessSession session = testRunner.getProcessSessionFactory().createSession(); + FlowFile ff = session.create(); + ff = session.putAttribute(ff, TEST_ATTRIBUTE_KEY, jsonBetweenLeadingAndTrailingSpaces); + testRunner.enqueue(ff); + testRunner.run(); + + testRunner.assertTransferCount(AttributesToJSON.REL_FAILURE, 0); + testRunner.assertTransferCount(AttributesToJSON.REL_SUCCESS, 1); + MockFlowFile result = testRunner.getFlowFilesForRelationship(AttributesToJSON.REL_SUCCESS).get(0); + Map attributes = MAPPER.readValue(result.getContent(), Map.class); + assertInstanceOf(Map.class, attributes.get(TEST_ATTRIBUTE_KEY)); + } }