From 911cc52a0dd667fb52ec0e553a8614490c52b6bc Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Wed, 18 Oct 2023 15:20:51 -0700 Subject: [PATCH 1/2] Sanitize maps and lists for JSONObject consumption --- api/src/org/labkey/api/util/JsonUtil.java | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/api/src/org/labkey/api/util/JsonUtil.java b/api/src/org/labkey/api/util/JsonUtil.java index 81baf1a51c0..33c8111c33c 100644 --- a/api/src/org/labkey/api/util/JsonUtil.java +++ b/api/src/org/labkey/api/util/JsonUtil.java @@ -43,7 +43,9 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.ListIterator; import java.util.Map; +import java.util.function.Consumer; /** * Helper methods for working with Jackson, JSONObject, and JSONArray @@ -262,6 +264,38 @@ else if (f == Float.NEGATIVE_INFINITY) return value; } + /** + * Sanitizes a mutable map by replacing +Infinity, -Infinity, and NaN values with JSON-legal values. Recursively + * sanitizes all embedded maps and lists as well; these must also be mutable. + * @param mutableMap mutable Map to sanitize + */ + public static void sanitizeMap(Map mutableMap) + { + mutableMap.keySet().forEach(key -> sanitize(mutableMap.get(key), num -> mutableMap.put(key, translateNumber(num)))); + } + + /** + * Sanitizes a mutable list by replacing +Infinity, -Infinity, and NaN values with JSON-legal values. Recursively + * sanitizes all embedded maps and lists as well; these must also be mutable. + * @param mutableList mutable List to sanitize + */ + public static void sanitizeList(List mutableList) + { + ListIterator iter = mutableList.listIterator(); + while (iter.hasNext()) + sanitize(iter.next(), num -> iter.set(translateNumber(num))); + } + + private static void sanitize(Object obj, Consumer numberAction) + { + if (obj instanceof Map m) + sanitizeMap(m); + else if (obj instanceof List list) + sanitizeList(list); + else if (obj instanceof Number num) + numberAction.accept(num); + } + public static class TestCase extends Assert { private static final String JSON_WITH_COMMENTS = """ From efff6a14c006d6968dae679c4955de71deba3122 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Wed, 18 Oct 2023 15:33:41 -0700 Subject: [PATCH 2/2] Junit test --- api/src/org/labkey/api/util/JsonUtil.java | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/api/src/org/labkey/api/util/JsonUtil.java b/api/src/org/labkey/api/util/JsonUtil.java index 33c8111c33c..fb519b73e9b 100644 --- a/api/src/org/labkey/api/util/JsonUtil.java +++ b/api/src/org/labkey/api/util/JsonUtil.java @@ -41,6 +41,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.ListIterator; @@ -456,5 +457,47 @@ public void testJsonString() assertEquals(html.toString(), roundTripJson.get("htmlString")); assertEquals(u.getUserId(), roundTripJson.get("user")); } + + @Test + public void testSanitize() + { + // Rough approximation of the map GetNabRunsAction generates + Map response = new HashMap<>(); + response.put("assayName", "foobar"); + response.put("assayDescription", "this is my assay"); + response.put("assayId", 1234); + response.put("neg", Double.NEGATIVE_INFINITY); + response.put("pos", Double.POSITIVE_INFINITY); + response.put("nan", Double.NaN); + + List> runList = new ArrayList<>(); + response.put("runs", runList); + runList.add(getDoubleRun()); + runList.add(getFloatRun()); + + assertThrows(JSONException.class, () -> new JSONObject(response)); + JsonUtil.sanitizeMap(response); + JSONObject json = new JSONObject(response); + } + + private Map getFloatRun() + { + Map run = new HashMap<>(); + run.put("cutoff", Float.NaN); + run.put("maxDilution", Float.NEGATIVE_INFINITY); + run.put("minDilution", Float.POSITIVE_INFINITY); + + return run; + } + + private Map getDoubleRun() + { + Map run = new HashMap<>(); + run.put("cutoff", Double.NaN); + run.put("maxDilution", Double.NEGATIVE_INFINITY); + run.put("minDilution", Double.POSITIVE_INFINITY); + + return run; + } } }