Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MOB-9668 - Add support for Nested JSON with Array Criteria Match #814

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading