diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/ComplexCriteriaCheckerTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/ComplexCriteriaCheckerTest.java index 24e5f2507..7d2a606c6 100644 --- a/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/ComplexCriteriaCheckerTest.java +++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/ComplexCriteriaCheckerTest.java @@ -698,7 +698,7 @@ public void testComplexCriteria3() throws Exception { " {\n" + " \"eventName\": \"button-clicked\",\n" + " \"dataFields\": {\n" + - " \"button-clicked.lastPageViewed\": \"welcome page\"\n" + + " \"lastPageViewed\": \"welcome page\"\n" + " },\n" + " \"total\": 3,\n" + " \"createdAt\": 1700071052507,\n" + @@ -720,7 +720,7 @@ public void testComplexCriteria3() throws Exception { " {\n" + " \"eventName\": \"button-clicked\",\n" + " \"dataFields\": {\n" + - " \"button-clicked.lastPageViewed\": \"welcome page\"\n" + + " \"lastPageViewed\": \"welcome page\"\n" + " },\n" + " \"total\": 3,\n" + " \"createdAt\": 1700071052507,\n" + diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/MultiLevelNestedCriteriaWithArrayMatchTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/MultiLevelNestedCriteriaWithArrayMatchTest.java new file mode 100644 index 000000000..ccaf5120a --- /dev/null +++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/MultiLevelNestedCriteriaWithArrayMatchTest.java @@ -0,0 +1,224 @@ +package com.iterable.iterableapi.util; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import org.json.JSONArray; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class MultiLevelNestedCriteriaWithArrayMatchTest { + + static final String mockDataNestedMultiLevelArrayTrackEvent = "{\n" + + " \"count\": 1,\n" + + " \"criteriaSets\": [\n" + + " {\n" + + " \"criteriaId\": \"459\",\n" + + " \"name\": \"event a.h.b=d && a.h.c=g\",\n" + + " \"createdAt\": 1727717997842,\n" + + " \"updatedAt\": 1728024187962,\n" + + " \"searchQuery\": {\n" + + " \"combinator\": \"And\",\n" + + " \"searchQueries\": [\n" + + " {\n" + + " \"combinator\": \"And\",\n" + + " \"searchQueries\": [\n" + + " {\n" + + " \"dataType\": \"customEvent\",\n" + + " \"searchCombo\": {\n" + + " \"combinator\": \"And\",\n" + + " \"searchQueries\": [\n" + + " {\n" + + " \"dataType\": \"customEvent\",\n" + + " \"field\": \"TopLevelArrayObject.a.h.b\",\n" + + " \"comparatorType\": \"Equals\",\n" + + " \"value\": \"d\",\n" + + " \"fieldType\": \"string\"\n" + + " },\n" + + " {\n" + + " \"dataType\": \"customEvent\",\n" + + " \"field\": \"TopLevelArrayObject.a.h.c\",\n" + + " \"comparatorType\": \"Equals\",\n" + + " \"value\": \"g\",\n" + + " \"fieldType\": \"string\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + " }\n" + + " }\n" + + " ]\n" + + " }"; + + static final String mockDataMultiLevelNestedWithArray = "{\n" + + " \"count\": 1,\n" + + " \"criteriaSets\": [\n" + + " {\n" + + " \"criteriaId\": \"436\",\n" + + " \"name\": \"Criteria 2.1 - 09252024 Bug Bash\",\n" + + " \"createdAt\": 1727286807360,\n" + + " \"updatedAt\": 1727445082036,\n" + + " \"searchQuery\": {\n" + + " \"combinator\": \"And\",\n" + + " \"searchQueries\": [\n" + + " {\n" + + " \"combinator\": \"And\",\n" + + " \"searchQueries\": [\n" + + " {\n" + + " \"dataType\": \"user\",\n" + + " \"searchCombo\": {\n" + + " \"combinator\": \"And\",\n" + + " \"searchQueries\": [\n" + + " {\n" + + " \"dataType\": \"user\",\n" + + " \"field\": \"furniture.material.type\",\n" + + " \"comparatorType\": \"Contains\",\n" + + " \"value\": \"table\",\n" + + " \"fieldType\": \"string\"\n" + + " },\n" + + " {\n" + + " \"dataType\": \"user\",\n" + + " \"field\": \"furniture.material.color\",\n" + + " \"comparatorType\": \"Equals\",\n" + + " \"values\": [\"black\"]\n" + + " }\n" + + " ]\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + " }\n" + + " }\n" + + " ]\n" + + "}\n"; + + + private CriteriaCompletionChecker evaluator; + + @Before + public void setUp() { + evaluator = new CriteriaCompletionChecker(); + } + + + @Test + public void testMultiLevelNestedWithArrayPass() throws Exception { + String jsonString = "[\n" + + " {\n" + + " \"dataFields\": {\n" + + " \"furniture\": {\n" + + " \"material\": [\n" + + " {\n" + + " \"type\": \"table\",\n" + + " \"color\": \"black\",\n" + + " \"lengthInches\": 40,\n" + + " \"widthInches\": 60\n" + + " },\n" + + " {\n" + + " \"type\": \"Sofa\",\n" + + " \"color\": \"Gray\",\n" + + " \"lengthInches\": 20,\n" + + " \"widthInches\": 30\n" + + " }\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"eventType\": \"user\"\n" + + " }\n" + + "]\n"; + JSONArray jsonArray = new JSONArray(jsonString); + String result = evaluator.getMatchedCriteria(mockDataMultiLevelNestedWithArray, jsonArray); + assertNotNull(result); + } + + @Test + public void testMultiLevelNestedWithArrayFail() throws Exception { + String jsonString = "[\n" + + " {\n" + + " \"dataFields\": {\n" + + " \"furniture\": {\n" + + " \"material\": [\n" + + " {\n" + + " \"type\": \"table\",\n" + + " \"color\": \"gray\",\n" + + " \"lengthInches\": 40,\n" + + " \"widthInches\": 60\n" + + " },\n" + + " {\n" + + " \"type\": \"Sofa\",\n" + + " \"color\": \"black\",\n" + + " \"lengthInches\": 20,\n" + + " \"widthInches\": 30\n" + + " }\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"eventType\": \"user\"\n" + + " }\n" + + "]\n"; + JSONArray jsonArray = new JSONArray(jsonString); + String result = evaluator.getMatchedCriteria(mockDataMultiLevelNestedWithArray, jsonArray); + assertNull(result); + } + + @Test + public void testNestedMultiLevelArrayTrackEventPass() throws Exception { + String jsonString = "[\n" + + " {\n" + + " \"eventName\": \"TopLevelArrayObject\",\n" + + " \"dataFields\": {\n" + + " \"a\": {\n" + + " \"h\": [\n" + + " {\n" + + " \"b\": \"e\",\n" + + " \"c\": \"h\"\n" + + " },\n" + + " {\n" + + " \"b\": \"d\",\n" + + " \"c\": \"g\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"eventType\": \"customEvent\"\n" + + " }\n" + + "]"; + JSONArray jsonArray = new JSONArray(jsonString); + String result = evaluator.getMatchedCriteria(mockDataNestedMultiLevelArrayTrackEvent, jsonArray); + assertNotNull(result); + } + + @Test + public void testNestedMultiLevelArrayTrackEventFail() throws Exception { + String jsonString = "[\n" + + " {\n" + + " \"eventName\": \"TopLevelArrayObject\",\n" + + " \"dataFields\": {\n" + + " \"a\": {\n" + + " \"h\": [\n" + + " {\n" + + " \"b\": \"d\",\n" + + " \"c\": \"h\"\n" + + " },\n" + + " {\n" + + " \"b\": \"e\",\n" + + " \"c\": \"g\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"eventType\": \"customEvent\"\n" + + " }\n" + + "]"; + JSONArray jsonArray = new JSONArray(jsonString); + String result = evaluator.getMatchedCriteria(mockDataNestedMultiLevelArrayTrackEvent, jsonArray); + assertNull(result); + } +} diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/util/CriteriaCompletionChecker.java b/iterableapi/src/main/java/com/iterable/iterableapi/util/CriteriaCompletionChecker.java index c921ff275..eb291a250 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/util/CriteriaCompletionChecker.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/util/CriteriaCompletionChecker.java @@ -9,6 +9,7 @@ import org.json.JSONObject; import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; @@ -349,41 +350,64 @@ private boolean evaluateFieldLogic(JSONArray searchQueries, JSONObject eventData } if (field.contains(".")) { String[] splitString = field.split("\\."); - String firstElement = splitString[0]; - Object eventDataFirstElement = eventData.has(firstElement) ? eventData.get(firstElement) : null; - if (eventDataFirstElement instanceof JSONArray) { - JSONArray jsonArraySourceTo = (JSONArray) eventDataFirstElement; - for (int i = 0; i < jsonArraySourceTo.length(); i++) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put(firstElement, jsonArraySourceTo.get(i)); - jsonObject.put(IterableConstants.SHARED_PREFS_EVENT_TYPE, eventData.get(IterableConstants.SHARED_PREFS_EVENT_TYPE)); - matchResult = evaluateFieldLogic(searchQueries, jsonObject); - if (matchResult) { - break; + if ((eventData.has(IterableConstants.SHARED_PREFS_EVENT_TYPE) && eventData.get(IterableConstants.SHARED_PREFS_EVENT_TYPE).equals(IterableConstants.TRACK_EVENT)) + && (eventData.has(IterableConstants.KEY_EVENT_NAME) && eventData.get(IterableConstants.KEY_EVENT_NAME).equals(splitString[0]))) { + splitString = Arrays.copyOfRange(splitString, 1, splitString.length); + } + + JSONObject fieldValue = eventData; + boolean isSubFieldArray = false; + boolean isSubMatch = false; + + for (String subField : splitString) { + if (fieldValue.has(subField)) { + Object subFieldValue = fieldValue.get(subField); + if (subFieldValue instanceof JSONArray) { + isSubFieldArray = true; + JSONArray subFieldValueArray = (JSONArray) subFieldValue; + for (int i = 0; i < subFieldValueArray.length(); i++) { + Object item = subFieldValueArray.get(i); + JSONObject data = new JSONObject(); + for (int j = splitString.length - 1; j >= 0; j--) { + String split = splitString[j]; + if (split.equals(subField)) { + data.put(split, item); + } else { + JSONObject temp = new JSONObject(data.toString()); + data = new JSONObject(); + data.put(split, temp); + } + } + if (evaluateFieldLogic(searchQueries, mergeEventData(eventData, data))) { + isSubMatch = true; + break; + } + } + + } else if (subFieldValue instanceof JSONObject) { + fieldValue = (JSONObject) subFieldValue; + } + if (isSubFieldArray) { + return isSubMatch; } } + } + Object valueFromObj = getFieldValue(eventData, field); + if (valueFromObj != null) { + matchResult = evaluateComparison( + searchQuery.getString(IterableConstants.COMPARATOR_TYPE), + valueFromObj, + searchQuery.has(IterableConstants.VALUES) ? + searchQuery.getJSONArray(IterableConstants.VALUES) : + searchQuery.getString(IterableConstants.VALUE) + ); if (matchResult) { + continue; + } else { break; } - } else { - Object valueFromObj = getFieldValue(eventData, field); - if (valueFromObj != null) { - matchResult = evaluateComparison( - searchQuery.getString(IterableConstants.COMPARATOR_TYPE), - valueFromObj, - searchQuery.has(IterableConstants.VALUES) ? - searchQuery.getJSONArray(IterableConstants.VALUES) : - searchQuery.getString(IterableConstants.VALUE) - ); - if (matchResult) { - continue; - } else { - break; - } - } } - } - if (isKeyExists) { + } else if (isKeyExists) { if (evaluateComparison(searchQuery.getString(IterableConstants.COMPARATOR_TYPE), eventData.get(field), searchQuery.has(IterableConstants.VALUES) ? @@ -399,6 +423,15 @@ private boolean evaluateFieldLogic(JSONArray searchQueries, JSONObject eventData return matchResult; } + private JSONObject mergeEventData(JSONObject eventData, JSONObject data) throws JSONException { + Iterator keys = data.keys(); + while (keys.hasNext()) { + String key = keys.next(); + eventData.put(key, data.get(key)); + } + return eventData; + } + private Object getFieldValue(JSONObject data, String field) { String[] fields = field.split("\\."); try {