rs = new ListingHash<>();
+ for(V value : values)
+ {
+ rs.add(keyFunction.apply(value), value);
+ }
+
+ return (rs);
+ }
+
+
+
+ /*******************************************************************************
+ * Take a list of objects, and build a listing hash, using 2 lambdas to control
+ * how keys in the listing hash are created (from the objects), and how values
+ * are created.
+ *
+ * For example, given a list of Work records, if we want a Listing hash with
+ * workStatusId as keys, and workId a values, we would call:
+ *
+ * listToListingHash(workList, Work::getWorkStatusId, Work::getId)
+ *
+ * @param elements list of objects to be mapped
+ * @param keyFunction function to map an object from elements list into keys
+ * for the listing hash
+ * @param valueFunction function to map an object from elements list into values
+ * for the listing hash
+ *
+ * @return ListingHash that might look like:
+ * 1 -> [ 73, 75, 68]
+ * 2 -> [ 74 ]
+ * 4 -> [ 76, 77, 79, 80, 81]
+ *
+ * end
+ *******************************************************************************/
+ public static ListingHash listToListingHash(List elements, Function keyFunction, Function valueFunction)
+ {
+ if(elements == null)
+ {
+ return (null);
+ }
+
+ ListingHash rs = new ListingHash<>();
+ for(E element : elements)
+ {
+ rs.add(keyFunction.apply(element), valueFunction.apply(element));
+ }
+
+ return (rs);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static Map> listTo2LevelMap(List values, Function keyFunction1, Function keyFunction2)
+ {
+ if(values == null)
+ {
+ return (null);
+ }
+
+ Map> rs = new HashMap<>();
+ for(V value : values)
+ {
+ K1 k1 = keyFunction1.apply(value);
+ if(!rs.containsKey(k1))
+ {
+ rs.put(k1, new HashMap<>());
+ }
+ rs.get(k1).put(keyFunction2.apply(value), value);
+ }
+
+ return (rs);
+ }
+
+
+
+ /*******************************************************************************
+ ** split a large collection into lists of lists, with specified pageSize
+ **
+ *******************************************************************************/
+ public static List> getPages(Collection values, int pageSize)
+ {
+ List> rs = new LinkedList<>();
+
+ if(values.isEmpty())
+ {
+ //////////////////////////////////////////////////////////////////
+ // if there are no input values, return an empty list of lists. //
+ //////////////////////////////////////////////////////////////////
+ return (rs);
+ }
+
+ List currentPage = new LinkedList();
+ rs.add(currentPage);
+
+ for(T value : values)
+ {
+ if(currentPage.size() >= pageSize)
+ {
+ currentPage = new LinkedList();
+ rs.add(currentPage);
+ }
+
+ currentPage.add(value);
+ }
+
+ return (rs);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static String getQuestionMarks(Collection> c)
+ {
+ if(CollectionUtils.nullSafeIsEmpty(c))
+ {
+ return ("");
+ }
+
+ StringBuilder rs = new StringBuilder();
+ for(int i = 0; i < c.size(); i++)
+ {
+ rs.append(i > 0 ? "," : "").append("?");
+ }
+
+ return (rs.toString());
+ }
+
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java b/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java
new file mode 100644
index 00000000..ec386b8a
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java
@@ -0,0 +1,119 @@
+package com.kingsrook.qqq.backend.core.utils;
+
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+
+/*******************************************************************************
+ ** See: https://www.baeldung.com/jackson-vs-gson
+ **
+ *******************************************************************************/
+public class JsonUtils
+{
+ private static final Logger LOG = LogManager.getLogger(JsonUtils.class);
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static String toJson(Object object)
+ {
+ try
+ {
+ ObjectMapper mapper = newObjectMapper();
+ String jsonResult = mapper.writeValueAsString(object);
+ return (jsonResult);
+ }
+ catch(JsonProcessingException e)
+ {
+ LOG.error("Error serializing object of type [" + object.getClass().getSimpleName() + "] to json", e);
+ throw new IllegalArgumentException("Error in JSON Serialization", e);
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static String toPrettyJson(Object object)
+ {
+ try
+ {
+ ObjectMapper mapper = newObjectMapper();
+ ObjectWriter objectWriter = mapper.writerWithDefaultPrettyPrinter();
+ String jsonResult = objectWriter.writeValueAsString(object);
+ return (jsonResult);
+ }
+ catch(JsonProcessingException e)
+ {
+ LOG.error("Error serializing object of type [" + object.getClass().getSimpleName() + "] to json", e);
+ throw new IllegalArgumentException("Error in JSON Serialization", e);
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static JSONObject toJSONObject(String json) throws JSONException
+ {
+ JSONObject jsonObject = new JSONObject(json);
+ return (jsonObject);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static JSONArray toJSONArray(String json) throws JSONException
+ {
+ JSONArray jsonArray = new JSONArray(json);
+ return (jsonArray);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static ObjectMapper newObjectMapper()
+ {
+ ObjectMapper mapper = new ObjectMapper()
+ .registerModule(new JavaTimeModule())
+ .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ return (mapper);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static boolean looksLikeObject(String json)
+ {
+ return (json != null && json.matches("(?s)\\s*\\{.*"));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static boolean looksLikeArray(String json)
+ {
+ return (json != null && json.matches("(?s)\\s*\\[.*"));
+ }
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/core/utils/ListingHash.java b/src/main/java/com/kingsrook/qqq/backend/core/utils/ListingHash.java
new file mode 100755
index 00000000..4b39f0c5
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/core/utils/ListingHash.java
@@ -0,0 +1,267 @@
+package com.kingsrook.qqq.backend.core.utils;
+
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+
+/*******************************************************************************
+ ** Hash that provides "listing" capability -- keys map to lists of values that
+ ** are automatically/easily added to
+ **
+ *******************************************************************************/
+public class ListingHash implements Map>, Serializable
+{
+ public static final long serialVersionUID = 0L;
+
+ private HashMap> hashMap = null;
+
+
+
+ /*******************************************************************************
+ ** Default constructor
+ **
+ *******************************************************************************/
+ public ListingHash()
+ {
+ this.hashMap = new HashMap>();
+ }
+
+
+
+ /*******************************************************************************
+ ** Add a value to the entry/list for this key
+ **
+ *******************************************************************************/
+ public List add(K key, V value)
+ {
+ List list = getOrCreateListForKey(key);
+ list.add(value);
+ return (list);
+ }
+
+
+
+ /*******************************************************************************
+ ** Add all elements of the collection of v's to this listing hash, using keys
+ ** generated by passing each v to the supplied keyFunction (which return's K's)
+ **
+ *******************************************************************************/
+ public void addAll(Collection vs, Function keyFunction)
+ {
+ if(vs == null || keyFunction == null)
+ {
+ return;
+ }
+
+ for(V v : vs)
+ {
+ add(keyFunction.apply(v), v);
+ }
+ }
+
+
+
+ /*******************************************************************************
+ ** Add multiple values to the entry/list for this key
+ **
+ *******************************************************************************/
+ public List addAll(K key, Collection values)
+ {
+ List list = getOrCreateListForKey(key);
+ list.addAll(values);
+ return (list);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void addAll(ListingHash that)
+ {
+ if(that == null)
+ {
+ return;
+ }
+
+ for(K key : that.keySet())
+ {
+ addAll(key, that.get(key));
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private List getOrCreateListForKey(K key)
+ {
+ List list;
+
+ if(!this.hashMap.containsKey(key))
+ {
+ /////////////////////////////////
+ // create list, place into map //
+ /////////////////////////////////
+ list = new LinkedList();
+ this.hashMap.put(key, list);
+ }
+ else
+ {
+ list = this.hashMap.get(key);
+ }
+ return list;
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+ // wrappings of methods taken from the internal HashMap of this object //
+ /////////////////////////////////////////////////////////////////////////
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void clear()
+ {
+ this.hashMap.clear();
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public boolean containsKey(Object key)
+ {
+ return (this.hashMap.containsKey(key));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public boolean containsValue(Object value)
+ {
+ return (this.hashMap.containsValue(value));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public Set>> entrySet()
+ {
+ return (this.hashMap.entrySet());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public boolean equals(Object o)
+ {
+ return (this.hashMap.equals(o));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public List get(Object key)
+ {
+ return (this.hashMap.get(key));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public int hashCode()
+ {
+ return (this.hashMap.hashCode());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public boolean isEmpty()
+ {
+ return (this.hashMap.isEmpty());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public Set keySet()
+ {
+ return (this.hashMap.keySet());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public List put(K key, List value)
+ {
+ return (this.hashMap.put(key, value));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void putAll(Map extends K, ? extends List> t)
+ {
+ this.hashMap.putAll(t);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public List remove(Object key)
+ {
+ return (this.hashMap.remove(key));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public int size()
+ {
+ return (this.hashMap.size());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public Collection> values()
+ {
+ return (this.hashMap.values());
+ }
+}
diff --git a/src/main/java/com/kingsrook/qqq/backend/core/utils/StringUtils.java b/src/main/java/com/kingsrook/qqq/backend/core/utils/StringUtils.java
new file mode 100755
index 00000000..c5bd430e
--- /dev/null
+++ b/src/main/java/com/kingsrook/qqq/backend/core/utils/StringUtils.java
@@ -0,0 +1,373 @@
+package com.kingsrook.qqq.backend.core.utils;
+
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class StringUtils
+{
+ /*******************************************************************************
+ ** test if string is not null and is not empty (after being trimmed).
+ **
+ ** @param input the string to test
+ ** @return Boolean
+ *******************************************************************************/
+ public static Boolean hasContent(String input)
+ {
+ if(input != null && !input.trim().equals(""))
+ {
+ return true;
+ }
+ return false;
+ }
+
+
+
+ /*******************************************************************************
+ ** returns input.toString() if not null, or nullOutput if input == null (as in SQL NVL)
+ **
+ *******************************************************************************/
+ public static String nvl(Object input, String nullOutput)
+ {
+ if(input == null)
+ {
+ return nullOutput;
+ }
+ return input.toString();
+ }
+
+
+
+ /*******************************************************************************
+ ** returns input if not null, or nullOutput if input == null (as in SQL NVL)
+ **
+ *******************************************************************************/
+ public static String nvl(String input, String nullOutput)
+ {
+ if(input == null)
+ {
+ return nullOutput;
+ }
+ return input;
+ }
+
+
+
+ /*******************************************************************************
+ ** allCapsToMixedCase - ie, UNIT CODE -> Unit Code
+ **
+ ** @param input
+ ** @return
+ *******************************************************************************/
+ public static String allCapsToMixedCase(String input)
+ {
+ if(input == null)
+ {
+ return (null);
+ }
+
+ StringBuilder rs = new StringBuilder();
+
+ ///////////////////////////////////////////////////////////////////////
+ // match for 0 or more non-capitals (which will pass through as-is), //
+ // then one capital (which will be kept uppercase), //
+ // then 0 or more capitals (which will be lowercased), //
+ // then 0 or more non-capitals (which will pass through as-is) //
+ // //
+ // Example matches are: //
+ // "UNIT CODE" -> ()(U)(NIT)( ), then ()(C)(ODE)() -> (Unit )(Code) //
+ // "UNITCODE" -> ()(U)(NITCODE)(), -> (Unitcode) //
+ // "UnitCode" -> ()(U)()(nit), then ()(C)()(ode) -> (Unit)(Code) //
+ // "UNIT0CODE" -> ()(U)(NIT)(0), then ()(C)(ODE)() -> (Unit0)(Code) //
+ // "0UNITCODE" -> (0)(U)(NITCODE)() -> (0Unitcode) //
+ ///////////////////////////////////////////////////////////////////////
+ Pattern pattern = Pattern.compile("([^A-Z]*)([A-Z])([A-Z]*)([^A-Z]*)");
+ Matcher matcher = pattern.matcher(input);
+ while(matcher.find())
+ {
+ rs.append(matcher.group(1) != null ? matcher.group(1) : "");
+ rs.append(matcher.group(2) != null ? matcher.group(2) : "");
+ rs.append(matcher.group(3) != null ? matcher.group(3).toLowerCase() : "");
+ rs.append(matcher.group(4) != null ? matcher.group(4) : "");
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+ // just in case no match was found, return the original input string //
+ ///////////////////////////////////////////////////////////////////////
+ if(rs.length() == 0)
+ {
+ return (input);
+ }
+
+ return (rs.toString());
+ }
+
+
+
+ /*******************************************************************************
+ * truncate a string (null safely) at a max length.
+ *
+ *******************************************************************************/
+ public static String safeTruncate(String input, int maxLength)
+ {
+ if(input == null)
+ {
+ return (null);
+ }
+
+ if(input.length() <= maxLength)
+ {
+ return (input);
+ }
+
+ return (input.substring(0, maxLength));
+ }
+
+
+
+ /*******************************************************************************
+ *
+ *******************************************************************************/
+ public static String safeTruncate(String input, int maxLength, String suffix)
+ {
+ if(input == null)
+ {
+ return (null);
+ }
+
+ if(input.length() <= maxLength)
+ {
+ return (input);
+ }
+
+ return (input.substring(0, (maxLength - suffix.length())) + suffix);
+ }
+
+
+
+ /*******************************************************************************
+ ** returns input if not null, or nullOutput if input == null (as in SQL NVL)
+ **
+ *******************************************************************************/
+ public static String safeTrim(String input)
+ {
+ if(input == null)
+ {
+ return null;
+ }
+ return input.trim();
+ }
+
+
+
+ /*******************************************************************************
+ ** Join a collection of objects into 1 string
+ **
+ ** @param glue - String to insert between entries
+ ** @param collection - The collection of objects to join.
+ ** @return String
+ *******************************************************************************/
+ public static String join(String glue, Collection extends Object> collection)
+ {
+ StringBuffer rs = new StringBuffer();
+
+ int i = 0;
+ for(Object s : collection)
+ {
+ if(i++ > 0)
+ {
+ rs.append(glue);
+ }
+ rs.append(String.valueOf(s));
+ }
+
+ return (rs.toString());
+ }
+
+
+
+ /*******************************************************************************
+ ** joinWithCommasAndAnd
+ **
+ ** [one] => [one]
+ ** [one, two] => [one and two]
+ ** [one, two, three] => [one, two, and three]
+ ** [one, two, three, four] => [one, two, three, and four]
+ ** etc.
+ **
+ ** @param input
+ ** @return
+ *******************************************************************************/
+ public static String joinWithCommasAndAnd(List input)
+ {
+ StringBuilder rs = new StringBuilder();
+ int size = input.size();
+
+ for(int i = 0; i < size; i++)
+ {
+ if(i > 0 && size == 2)
+ {
+ rs.append(" and ");
+ }
+ else if(i > 0 && i < size - 1)
+ {
+ rs.append(", ");
+ }
+ else if(i > 0 && i == size - 1)
+ {
+ rs.append(", and ");
+ }
+ rs.append(input.get(i));
+ }
+
+ return (rs.toString());
+
+ }
+
+
+
+ /*******************************************************************************
+ ** isNullOrEmptyString
+ **
+ ** @param input
+ *******************************************************************************/
+ private static boolean isNullOrBlankString(Object input)
+ {
+ if(input == null)
+ {
+ return (true);
+ }
+ else if(input instanceof String && !StringUtils.hasContent((String) input))
+ {
+ return (true);
+ }
+ else
+ {
+ return (false);
+ }
+ }
+
+
+
+ /*******************************************************************************
+ ** Split a string into tokens, broken up around a given regular expression pattern.
+ **
+ ** @param original the string to do the splitting to.
+ ** @param pattern the pattern to split on.
+ ** @return a LinkedList with the elements found in the original string
+ *******************************************************************************/
+ public static LinkedList split(String original, String pattern)
+ {
+ return (new LinkedList(Arrays.asList(original.split(pattern))));
+ }
+
+
+
+ /*******************************************************************************
+ ** Split a string into tokens, broken up around a given regular expression pattern.
+ **
+ ** @param original the string to do the splitting to.
+ ** @param pattern the pattern to split on.
+ ** @param limit the max number of splits to make
+ ** @return a List with the elements found in the original string
+ *******************************************************************************/
+ public static LinkedList split(String original, String pattern, Integer limit)
+ {
+ return (new LinkedList(Arrays.asList(original.split(pattern, limit))));
+ }
+
+
+
+ /*******************************************************************************
+ ** Trims leading white spaces from a String. Returns a blank ("") value if NULL
+ **
+ ** @param input
+ ** @return String
+ *******************************************************************************/
+ public static String ltrim(String input)
+ {
+ if(!hasContent(input))
+ {
+ return "";
+ }
+
+ int i = 0;
+ while(i < input.length() && Character.isWhitespace(input.charAt(i)))
+ {
+ i++;
+ }
+ return (input.substring(i));
+ }
+
+
+
+ /*******************************************************************************
+ ** Trims trailing white spaces from a String. Returns a blank ("") value if NULL
+ **
+ ** @param input
+ ** @return String
+ *******************************************************************************/
+ public static String rtrim(String input)
+ {
+ if(!hasContent(input))
+ {
+ return "";
+ }
+
+ int i = input.length() - 1;
+ while(i > 0 && Character.isWhitespace(input.charAt(i)))
+ {
+ i--;
+ }
+ return (input.substring(0, i + 1));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static String plural(Collection> collection)
+ {
+ return (plural(collection == null ? 0 : collection.size()));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static String plural(Integer size)
+ {
+ return (plural(size, "", "s"));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static String plural(Collection> collection, String ifOne, String ifNotOne)
+ {
+ return (plural(collection.size(), ifOne, ifNotOne));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static String plural(Integer size, String ifOne, String ifNotOne)
+ {
+ return (size != null && size.equals(1) ? ifOne : ifNotOne);
+ }
+
+}
diff --git a/src/test/java/com/kingsrook/qqq/backend/core/adapters/JsonToQFieldMappingAdapterTest.java b/src/test/java/com/kingsrook/qqq/backend/core/adapters/JsonToQFieldMappingAdapterTest.java
new file mode 100644
index 00000000..1bd256dd
--- /dev/null
+++ b/src/test/java/com/kingsrook/qqq/backend/core/adapters/JsonToQFieldMappingAdapterTest.java
@@ -0,0 +1,93 @@
+package com.kingsrook.qqq.backend.core.adapters;
+
+
+import com.kingsrook.qqq.backend.core.model.actions.AbstractQFieldMapping;
+import com.kingsrook.qqq.backend.core.model.actions.QKeyBasedFieldMapping;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+class JsonToQFieldMappingAdapterTest
+{
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_buildMappingFromJson_nullInput()
+ {
+ testExpectedToThrow(null);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_buildMappingFromJson_emptyStringInput()
+ {
+ testExpectedToThrow("");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_buildMappingFromJson_malformedJsonInput()
+ {
+ testExpectedToThrow("{foo=bar}");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_buildMappingFromJson_validInput()
+ {
+ JsonToQFieldMappingAdapter jsonToQFieldMappingAdapter = new JsonToQFieldMappingAdapter();
+ AbstractQFieldMapping mapping = (QKeyBasedFieldMapping) jsonToQFieldMappingAdapter.buildMappingFromJson("""
+ {
+ "Field1": "source1",
+ "Field2": "source2",
+ }
+ """);
+ System.out.println(mapping);
+ assertNotNull(mapping);
+
+ // todo - are we backwards here??
+ assertEquals("source1", mapping.getMappedField("Field1"));
+ assertEquals("source2", mapping.getMappedField("Field2"));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private void testExpectedToThrow(String json)
+ {
+ try
+ {
+ JsonToQFieldMappingAdapter jsonToQFieldMappingAdapter = new JsonToQFieldMappingAdapter();
+ AbstractQFieldMapping> mapping = jsonToQFieldMappingAdapter.buildMappingFromJson(json);
+ System.out.println(mapping);
+ }
+ catch(IllegalArgumentException iae)
+ {
+ System.out.println("Threw expected exception");
+ return;
+ }
+
+ fail("Didn't throw expected exception");
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/kingsrook/qqq/backend/core/adapters/JsonToQRecordAdapterTest.java b/src/test/java/com/kingsrook/qqq/backend/core/adapters/JsonToQRecordAdapterTest.java
new file mode 100644
index 00000000..21923faa
--- /dev/null
+++ b/src/test/java/com/kingsrook/qqq/backend/core/adapters/JsonToQRecordAdapterTest.java
@@ -0,0 +1,149 @@
+package com.kingsrook.qqq.backend.core.adapters;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+class JsonToQRecordAdapterTest
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_buildRecordsFromJson_nullInput()
+ {
+ testExpectedToThrow(null);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_buildRecordsFromJson_emptyStringInput()
+ {
+ testExpectedToThrow("");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_buildRecordsFromJson_inputDoesntLookLikeJson()
+ {
+ testExpectedToThrow("");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_buildRecordsFromJson_inputLooksLikeJsonButIsMalformed()
+ {
+ testExpectedToThrow("{json=not}");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private void testExpectedToThrow(String json)
+ {
+ try
+ {
+ JsonToQRecordAdapter jsonToQRecordAdapter = new JsonToQRecordAdapter();
+ List qRecords = jsonToQRecordAdapter.buildRecordsFromJson(json);
+ System.out.println(qRecords);
+ }
+ catch(IllegalArgumentException iae)
+ {
+ System.out.println("Threw expected exception");
+ return;
+ }
+
+ fail("Didn't throw expected exception");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_buildRecordsFromJson_emptyList()
+ {
+ JsonToQRecordAdapter jsonToQRecordAdapter = new JsonToQRecordAdapter();
+ List qRecords = jsonToQRecordAdapter.buildRecordsFromJson("[]");
+ assertNotNull(qRecords);
+ assertTrue(qRecords.isEmpty());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_buildRecordsFromJson_inputObject()
+ {
+ JsonToQRecordAdapter jsonToQRecordAdapter = new JsonToQRecordAdapter();
+ List qRecords = jsonToQRecordAdapter.buildRecordsFromJson("""
+ {
+ "field1":"value1",
+ "field2":"value2"
+ }
+ """);
+ assertNotNull(qRecords);
+ assertEquals(1, qRecords.size());
+ assertEquals("value1", qRecords.get(0).getValue("field1"));
+ assertEquals("value2", qRecords.get(0).getValue("field2"));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_buildRecordsFromJson_inputList()
+ {
+ JsonToQRecordAdapter jsonToQRecordAdapter = new JsonToQRecordAdapter();
+ List qRecords = jsonToQRecordAdapter.buildRecordsFromJson("""
+ [
+ { "field1":"value1", "field2":"value2" },
+ { "fieldA":"valueA", "fieldB":"valueB" }
+ ]
+ """);
+ assertNotNull(qRecords);
+ assertEquals(2, qRecords.size());
+ assertEquals("value1", qRecords.get(0).getValue("field1"));
+ assertEquals("value2", qRecords.get(0).getValue("field2"));
+ assertEquals("valueA", qRecords.get(1).getValue("fieldA"));
+ assertEquals("valueB", qRecords.get(1).getValue("fieldB"));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_buildRecordsFromJson_inputListWithNonObjectMembers()
+ {
+ testExpectedToThrow("[ 1701 ]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java b/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java
new file mode 100644
index 00000000..cd66e44b
--- /dev/null
+++ b/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java
@@ -0,0 +1,179 @@
+package com.kingsrook.qqq.backend.core.utils;
+
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+class JsonUtilsTest
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_toJsonQRecordInput()
+ {
+ QRecord qRecord = getQRecord();
+ String json = JsonUtils.toJson(qRecord);
+ assertEquals("{\"tableName\":\"foo\",\"primaryKey\":1,\"values\":{\"foo\":\"Foo\",\"bar\":3.14159}}", json);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_toPrettyJsonQRecordInput()
+ {
+ QRecord qRecord = getQRecord();
+ String json = JsonUtils.toPrettyJson(qRecord);
+ // todo assertEquals("{\"tableName\":\"foo\",\"primaryKey\":1,\"values\":{\"foo\":\"Foo\",\"bar\":3.14159}}", json);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_toJSONObject()
+ {
+ JSONObject jsonObject = JsonUtils.toJSONObject("""
+ {
+ "Foo": "Bar",
+ "Baz": [1, 2, 3]
+ }
+ """);
+ assertNotNull(jsonObject);
+ assertTrue(jsonObject.has("Foo"));
+ assertEquals("Bar", jsonObject.getString("Foo"));
+ assertTrue(jsonObject.has("Baz"));
+ assertEquals(3, jsonObject.getJSONArray("Baz").length());
+ assertEquals(1, jsonObject.getJSONArray("Baz").get(0));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_toJSONObjectNonJsonObject()
+ {
+ try
+ {
+ JsonUtils.toJSONObject("");
+ }
+ catch(JSONException je)
+ {
+ System.out.println("Caught Expected exception");
+ return;
+ }
+
+ fail("Did not catch expected exception");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_toJSONArray()
+ {
+ JSONArray jsonArray = JsonUtils.toJSONArray("""
+ [
+ {"Foo": "Bar"},
+ {"Baz": [1, 2, 3]}
+ ]
+ """);
+ assertNotNull(jsonArray);
+ assertEquals(2, jsonArray.length());
+ assertTrue(jsonArray.getJSONObject(0).has("Foo"));
+ assertEquals("Bar", jsonArray.getJSONObject(0).getString("Foo"));
+ assertTrue(jsonArray.getJSONObject(1).has("Baz"));
+ assertEquals(3, jsonArray.getJSONObject(1).getJSONArray("Baz").length());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private QRecord getQRecord()
+ {
+ QRecord qRecord = new QRecord();
+ qRecord.setTableName("foo");
+ qRecord.setPrimaryKey(1);
+ Map values = new LinkedHashMap<>();
+ qRecord.setValues(values);
+ values.put("foo", "Foo");
+ values.put("bar", new BigDecimal("3.14159"));
+ return qRecord;
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_toJsonNullInput()
+ {
+ String json = JsonUtils.toJson(null);
+ assertEquals("null", json);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_looksLikeObject()
+ {
+ assertFalse(JsonUtils.looksLikeObject(""));
+ assertFalse(JsonUtils.looksLikeObject(null));
+ assertFalse(JsonUtils.looksLikeObject("json"));
+ assertFalse(JsonUtils.looksLikeObject("[]"));
+ assertTrue(JsonUtils.looksLikeObject("{}"));
+ assertTrue(JsonUtils.looksLikeObject(" {}"));
+ assertTrue(JsonUtils.looksLikeObject("\n\n\n{}"));
+ assertTrue(JsonUtils.looksLikeObject("\n{\n[]\n}\n"));
+ assertTrue(JsonUtils.looksLikeObject("\n\n\n { \n}\n\n\n"));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ public void test_looksLikeArray()
+ {
+ assertFalse(JsonUtils.looksLikeArray(""));
+ assertFalse(JsonUtils.looksLikeArray(null));
+ assertFalse(JsonUtils.looksLikeArray("json"));
+ assertFalse(JsonUtils.looksLikeArray("{json[]}"));
+ assertFalse(JsonUtils.looksLikeArray("{}"));
+ assertTrue(JsonUtils.looksLikeArray("[]"));
+ assertTrue(JsonUtils.looksLikeArray(" []"));
+ assertTrue(JsonUtils.looksLikeArray("\n\n\n[]"));
+ assertTrue(JsonUtils.looksLikeArray("\n[\n{}\n}\n"));
+ assertTrue(JsonUtils.looksLikeArray("\n\n\n [ \n]\n\n\n"));
+ }
+
+}
\ No newline at end of file