Skip to content

Commit

Permalink
Merge pull request #814 from Iterable/bugfix/MOB-9668-Nested-JSON-wit…
Browse files Browse the repository at this point in the history
…h-Array-Criteria-Matching-did-not-work

MOB-9668 - Add support for Nested JSON with Array Criteria Match
  • Loading branch information
evantk91 authored Oct 4, 2024
2 parents ed64a9f + 4d4bd05 commit 9fb6552
Show file tree
Hide file tree
Showing 3 changed files with 308 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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" +
Expand All @@ -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" +
Expand Down Expand Up @@ -758,7 +758,7 @@ public void testComplexCriteria3Fail() 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" +
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -347,43 +348,85 @@ private boolean evaluateFieldLogic(JSONArray searchQueries, JSONObject eventData
}
}
}

// if field is a nested field
if (field.contains(".")) {
// separate the sub-fields into an array
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 event type is a custom event and event name equals the top-level sub-field
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]))) {
// remove the event name from the separated sub-fields array
splitString = Arrays.copyOfRange(splitString, 1, splitString.length);
}

JSONObject fieldValue = eventData;
boolean isSubFieldArray = false;
boolean isSubMatch = false;

// loop through the separated fields array
for (String subField : splitString) {
// check if the current sub-field exists in the event data
if (fieldValue.has(subField)) {
// get the value of the current sub-field
Object subFieldValue = fieldValue.get(subField);
// check if the value is a JSONArray
if (subFieldValue instanceof JSONArray) {
isSubFieldArray = true;
JSONArray subFieldValueArray = (JSONArray) subFieldValue;
// loop through the JSONArray
for (int i = 0; i < subFieldValueArray.length(); i++) {
// get the value of the current item in the JSONArray
Object item = subFieldValueArray.get(i);
JSONObject data = new JSONObject();

// loop through the separated fields array
// process array to allow individual items to be checked
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);
}
}
// check if the current item matches the search queries
if (evaluateFieldLogic(searchQueries, mergeEventData(eventData, data))) {
// if item matches, set to true and break the loop
isSubMatch = true;
break;
}
}

} else if (subFieldValue instanceof JSONObject) {
// set field value to the JSONObject for next iteration
fieldValue = (JSONObject) subFieldValue;
}

// return result if sub-field is an array
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) ?
Expand All @@ -399,6 +442,15 @@ private boolean evaluateFieldLogic(JSONArray searchQueries, JSONObject eventData
return matchResult;
}

private JSONObject mergeEventData(JSONObject eventData, JSONObject data) throws JSONException {
Iterator<String> 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 {
Expand Down

0 comments on commit 9fb6552

Please sign in to comment.