diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/main/java/org/json/JSONArray.java b/src/main/java/org/json/JSONArray.java index 375d03888..e08d3af04 100644 --- a/src/main/java/org/json/JSONArray.java +++ b/src/main/java/org/json/JSONArray.java @@ -15,6 +15,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.stream.IntStream; +import java.util.stream.Stream; /** @@ -224,6 +226,14 @@ public JSONArray(int initialCapacity) throws JSONException { this.myArrayList = new ArrayList(initialCapacity); } + /** + * Stream support - requires java 8 + * @return Stream of array elements + */ + public Stream stream() { + return IntStream.range(0, length()).mapToObj(this::opt); + } + @Override public Iterator iterator() { return this.myArrayList.iterator(); diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 5e00eb9a3..e40385119 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -15,18 +15,11 @@ import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.math.BigInteger; -import java.util.Collection; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.Locale; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.ResourceBundle; -import java.util.Set; +import java.util.function.Consumer; import java.util.regex.Pattern; +import java.util.stream.Stream; /** * A JSONObject is an unordered collection of name/value pairs. Its external @@ -81,7 +74,13 @@ * @author JSON.org * @version 2016-08-15 */ -public class JSONObject { +public class JSONObject implements Iterable { + + @Override + public Iterator iterator() { + return keys(); + } + /** * JSONObject.NULL is equivalent to the value that JavaScript calls null, * whilst Java's null is equivalent to the value that JavaScript calls @@ -461,6 +460,14 @@ protected JSONObject(int initialCapacity){ this.map = new HashMap(initialCapacity); } + /** + * Stream support - requires java 8 + * @return Stream of map entries + */ + public Stream> stream() { + return this.entrySet().stream(); + } + /** * Accumulate values under a key. It is similar to the put method except * that if there is already an object stored under the key then a JSONArray diff --git a/src/main/java/org/json/ParserConfiguration.java b/src/main/java/org/json/ParserConfiguration.java index 519e2099d..4151c7b34 100644 --- a/src/main/java/org/json/ParserConfiguration.java +++ b/src/main/java/org/json/ParserConfiguration.java @@ -6,7 +6,6 @@ /** * Configuration base object for parsers. The configuration is immutable. */ -@SuppressWarnings({""}) public class ParserConfiguration { /** * Used to indicate there's no defined limit to the maximum nesting depth when parsing a document. @@ -68,10 +67,9 @@ public boolean isKeepStrings() { /** * When parsing the XML into JSONML, specifies if values should be kept as strings (true), or if * they should try to be guessed into JSON values (numeric, boolean, string) - * + * @param type parameter * @param newVal * new value to use for the keepStrings configuration option. - * * @return The existing configuration will not be modified. A new configuration is returned. */ public T withKeepStrings(final boolean newVal) { @@ -95,6 +93,7 @@ public int getMaxNestingDepth() { * will throw a JsonException if the maximum depth is reached. * Using any negative value as a parameter is equivalent to setting no limit to the nesting depth, * which means the parses will go as deep as the maximum call stack size allows. + * @param type parameter * @param maxNestingDepth the maximum nesting depth allowed to the XML parser * @return The existing configuration will not be modified. A new configuration is returned. */ diff --git a/src/test/java/org/json/junit/JSONArrayTest.java b/src/test/java/org/json/junit/JSONArrayTest.java index aea4e30e8..b606ee2a3 100644 --- a/src/test/java/org/json/junit/JSONArrayTest.java +++ b/src/test/java/org/json/junit/JSONArrayTest.java @@ -9,6 +9,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import java.io.IOException; @@ -16,14 +17,8 @@ import java.io.StringWriter; import java.math.BigDecimal; import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; import org.json.JSONArray; import org.json.JSONException; @@ -66,6 +61,239 @@ public class JSONArrayTest { "\"-1\""+ "]"; + /** + * 1. JSONArray stream tests + * 1.1 Basic tests + * 1.2 Advanced tests + * 1.3 Exception tests + * 1.4 Integrated tests + */ + + /** + * 1.1.1 Stream JSONArray to a list + */ + @Test + public void testStream_1_1_1() { + String data = "[ 1, 2, 3, true, false, null, \"abc\", \"def\", { \"a\": \"b\" }, []]"; + JSONArray jsonArray = new JSONArray(data); + Object[] expectedList = {1, 2, 3, true, false, null, "abc", "def", + new JSONObject("{ \"a\": \"b\" }"), new JSONArray("[]")}; + + List actualList = jsonArray.stream() + .collect(Collectors.toList()); + System.out.println(actualList); + assertEquals(actualList.size(), expectedList.length); + for (int i = 0; i < actualList.size(); ++i) { + Object actual = actualList.get(i); + Object expected = expectedList[i]; + if (actual instanceof JSONObject) { + assertTrue(((JSONObject) actual).similar((JSONObject)expected)); + } else if (actual instanceof JSONArray) { + assertTrue(((JSONArray) actual).similar((JSONArray) expected)); + } else if (actual == JSONObject.NULL) { + assertNull(expected); + } else { + assertEquals(expected, actual); + } + } + } + + /** + * 1.1.2 Stream JSONArray through a filter. + */ + @Test + public void testStream_1_1_2() { + String data = "[ 1, 2, 3, true, false, null, \"abc\", \"def\", { \"a\": \"b\" }, []]"; + JSONArray jsonArray = new JSONArray(data); + Object[] expectedList = { 2 }; + + List actualList = jsonArray.stream() + .filter(x -> x instanceof Number) + .filter(x -> (int)x % 2 == 0) + .collect(Collectors.toList()); + assertEquals(actualList.size(), expectedList.length); + assertEquals(expectedList[0], actualList.get(0)); + } + + /** + * 1.1.3 Stream JSONArray to find first. + */ + @Test + public void testStream_1_1_3() { + String data = "[ 1, 2, 3, true, false, null, \"abc\", \"def\", { \"a\": \"b\" }, []]"; + JSONArray jsonArray = new JSONArray(data); + Object[] expectedList = { 2 }; + + Optional firstString = jsonArray.stream() + .filter(x -> x instanceof String) + .findFirst(); + + + String actual = (String)firstString.get(); + assertEquals("abc", actual); + } + + /** + * 1.2.1 Stream JSONArray through a map + */ + @Test + public void testStream_1_2_1() { + String data = "[ 1, 2, 3, true, false, null, \"abc\", \"def\", { \"a\": \"b\" }, []]"; + JSONArray jsonArray = new JSONArray(data); + Object[] expectedList = { "Number: 1", "Number: 2", "Number: 3" }; + + List actualList = jsonArray.stream() + .filter(x -> x instanceof Number) + .map(x -> "Number: " + x) + .collect(Collectors.toList()); + + assertEquals(expectedList.length, actualList.size()); + for (int i = 0; i < actualList.size(); ++i) { + assertEquals(expectedList[i], actualList.get(i)); + } + } + + /** + * 1.2.2 Stream JSONArray to find min and max + */ + @Test + public void testStream_1_2_2() { + String data = "[ 1, 2, 3, true, false, null, \"abc\", \"def\", { \"a\": \"b\" }, []]"; + JSONArray jsonArray = new JSONArray(data); + + Optional min = jsonArray.stream() + .filter(x -> x instanceof Number) + .min(Comparator.comparingInt(x -> (int) x)); + Optional max = jsonArray.stream() + .filter(x -> x instanceof Number) + .max(Comparator.comparingInt(x -> (int) x)); + + // Assert + assertTrue(min.isPresent()); + assertEquals(1, min.get()); + + assertTrue(max.isPresent()); + assertEquals(3, max.get()); + } + + /** + * 1.2.3 Stream JSONArray through groupingBy + */ + @Test + public void testStream_1_2_3() { + String data = "[ 1, 2, 3, true, false, null, \"abc\", \"def\", { \"a\": \"b\" }, []]"; + JSONArray jsonArray = new JSONArray(data); + Object[] expectedList = { "Number: 1", "Number: 2", "Number: 3" }; + + Map> groupedByMod2 = jsonArray.stream() + .filter(x -> x instanceof Number) + .collect(Collectors.groupingBy(x -> (int) x % 2)); + + // Assert + assertEquals(2, groupedByMod2.size()); + + assertEquals(1, groupedByMod2.get(0).size()); + assertEquals(2, groupedByMod2.get(0).get(0)); + + assertEquals(2, groupedByMod2.get(1).size()); + assertEquals(1, groupedByMod2.get(1).get(0)); + assertEquals(3, groupedByMod2.get(1).get(1)); + } + + /** + * 1.3.1 Stream empty JSONArray + */ + @Test + public void testStream_1_3_1() { + JSONArray jsonArray = new JSONArray(); + + List list = jsonArray.stream() + .collect(Collectors.toList()); + + assertTrue(list.isEmpty()); + } + + /** + * 1.4.1 Map from JSONObject + */ + @Test + public void testStream_1_4_1() { + JSONObject jsonObject = new JSONObject(); + JSONArray jsonArray = new JSONArray(); + jsonArray.put("apple"); + jsonArray.put("banana"); + jsonObject.put("fruits", jsonArray); + Object[] expectedList = { "apple", "banana" }; + + List actualList = jsonObject.stream() + .filter(entry -> entry.getKey().equals("fruits")) + .flatMap(entry -> jsonObject.getJSONArray((String)entry.getKey()).stream()) + .map(Object::toString) + .collect(Collectors.toList()); + + assertEquals(actualList.size(), expectedList.length); + for (int i = 0; i < actualList.size(); ++i) { + assertEquals(expectedList[i], actualList.get(i)); + } + } + + @Test + public void testStream_ComposeFromArray() { + @SuppressWarnings("boxing") + Object[] expectedList = new Object[] { 1, 2, 3, true, false, null, "abc", "def", new JSONObject("{ \"a\": \"b\" }"), new JSONArray()}; + JSONArray jsonArray = new JSONArray(expectedList); + + List actualList = jsonArray.stream() + .collect(Collectors.toList()); + System.out.println(actualList); + assertEquals(actualList.size(), expectedList.length); + for (int i = 0; i < actualList.size(); ++i) { + Object actual = actualList.get(i); + Object expected = expectedList[i]; + if (actual instanceof JSONObject) { + assertTrue("Json Object test",((JSONObject) actual).similar(expected)); + } else if (actual instanceof JSONArray) { + assertTrue("Json Array test", ((JSONArray) actual).similar(expected)); + } else if (JSONObject.NULL.equals(expected)) { + assertTrue("Null test", JSONObject.NULL.equals(actual)); + } else { + assertEquals("value test: "+expected, expected, actual); + } + } + } + + @Test + public void testStream_ComposeFromCode() { + JSONArray jsonArray = new JSONArray(); + jsonArray.put(1).put(2).put(3) + .put(true).put(false) + .put((Object)null) + .put("abc").put("def") + .put(new JSONObject("{ \"a\": \"b\" }")) + .put(new JSONArray()) + ; + @SuppressWarnings("boxing") + Object[] expectedList = new Object[] { 1, 2, 3, true, false, null, "abc", "def", new JSONObject("{ \"a\": \"b\" }"), new JSONArray()}; + + List actualList = jsonArray.stream() + .collect(Collectors.toList()); + System.out.println(actualList); + assertEquals(actualList.size(), expectedList.length); + for (int i = 0; i < actualList.size(); ++i) { + Object actual = actualList.get(i); + Object expected = expectedList[i]; + if (actual instanceof JSONObject) { + assertTrue("Json Object test",((JSONObject) actual).similar(expected)); + } else if (actual instanceof JSONArray) { + assertTrue("Json Array test", ((JSONArray) actual).similar(expected)); + } else if (JSONObject.NULL.equals(expected)) { + assertTrue("Null test", JSONObject.NULL.equals(actual)); + } else { + assertEquals("value test: "+expected, expected, actual); + } + } + } + /** * Tests that the similar method is working as expected. */ diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index 3250c258a..7f5012fcd 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -4,13 +4,7 @@ Public Domain. */ -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -24,6 +18,7 @@ import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.json.CDL; import org.json.JSONArray; @@ -72,6 +67,241 @@ public class JSONObjectTest { */ static final Pattern NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?"); + /** + * 2. JSONObject stream tests + * 2.1 Basic tests + * 2.2 Advanced tests + * 2.3 Exception tests + * 2.4 Integrated tests + */ + + /** + * Test 2.1.1 stream JSONObject to list + */ + @Test + public void test_2_1_1() { + String data = "{\n" + + " \"key1\": \"abc\",\n" + + " \"key2\": 42,\n" + + " \"key3\": true,\n" + + " \"key4\": null,\n" + + " \"key5\": [1,2,3],\n" + + " \"key6\": {\"a\":\"b\"},\n" + + " \"key7\": [],\n" + + " \"key8\": {}\n" + + "}\n"; + + JSONObject jsonObject = new JSONObject(data); + List actualList = jsonObject.stream() + .map(entry -> { + Object value = entry.getValue(); + if (value instanceof HashMap) { + // must be a JSONObject + value = new JSONObject((Map) value); + } + return entry.getKey() + ": " + value; + }) + .collect(Collectors.toList()); + + System.out.println(actualList); + assertEquals(8, actualList.size()); + assertTrue(actualList.contains("key1: abc")); + assertTrue(actualList.contains("key2: 42")); + assertTrue(actualList.contains("key3: true")); + assertTrue(actualList.contains("key4: null")); + assertTrue(actualList.contains("key5: [1,2,3]")); + assertTrue(actualList.contains("key6: {\"a\":\"b\"}")); + assertTrue(actualList.contains("key7: []")); + assertTrue(actualList.contains("key8: {}")); + } + + /** + * Test 2.1.2 filter entries based on a key condition + */ + @Test + public void test_2_1_2() { + String data = "{\n" + + " \"key1\": \"abc\",\n" + + " \"key2\": 42,\n" + + " \"key3\": true,\n" + + " \"key4\": null,\n" + + " \"key5\": [1,2,3],\n" + + " \"key6\": {\"a\":\"b\"},\n" + + " \"key7\": [],\n" + + " \"key8\": {}\n" + + "}\n"; + + JSONObject jsonObject = new JSONObject(data); + List actualList = jsonObject.stream() + .filter(entry -> entry.getValue() instanceof Number) + .map(entry -> entry.getKey() + ": " + entry.getValue()) + .collect(Collectors.toList()); + + System.out.println(actualList); + assertEquals(1, actualList.size()); + assertTrue(actualList.contains("key2: 42")); + } + + /** + * Test Case 2.1.3: Use the stream() method to find the first entry satisfying a key condition. + */ + @Test + public void test_2_1_3() { + String data = "{\n" + + " \"key1\": \"abc\",\n" + + " \"key2\": 42,\n" + + " \"key3\": true,\n" + + " \"key4\": null,\n" + + " \"key5\": [1,2,3],\n" + + " \"key6\": {\"a\":\"b\"},\n" + + " \"key7\": [],\n" + + " \"key8\": {}\n" + + "}\n"; + + JSONObject jsonObject = new JSONObject(data); + Optional> firstEntry = jsonObject.stream() + .filter(entry -> entry.getKey().startsWith("key5")) + .findFirst(); + + System.out.println(firstEntry); + assertTrue(firstEntry.isPresent()); + assertEquals("key5: [1,2,3]", firstEntry.get().getKey() + ": " + firstEntry.get().getValue()); + } + + /** + * Test Case 2.2.1: Use the stream() method in conjunction with map() to transform the values of the JSONObject + */ + @Test + public void test_2_2_1() { + String data = "{\n" + + " \"key1\": \"abc\",\n" + + " \"key2\": 42,\n" + + " \"key3\": true,\n" + + " \"key4\": null,\n" + + " \"key5\": [1,2,3],\n" + + " \"key6\": {\"a\":\"b\"},\n" + + " \"key7\": [],\n" + + " \"key8\": {}\n" + + "}\n"; + + JSONObject jsonObject = new JSONObject(data); + + Map transformedMap = jsonObject.stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> entry.getValue().toString() + )); + + System.out.println(transformedMap); + assertEquals(8, transformedMap.size()); + assertEquals("abc", transformedMap.get("key1")); + assertEquals("42", transformedMap.get("key2")); + assertEquals("true", transformedMap.get("key3")); + assertEquals("null", transformedMap.get("key4")); + assertEquals("[1,2,3]", transformedMap.get("key5")); + assertEquals("{\"a\":\"b\"}", transformedMap.get("key6")); + assertEquals("[]", transformedMap.get("key7")); + assertEquals("{}", transformedMap.get("key8")); + } + + /** + * Test Case 2.2.2: Use the stream() method to find the entry with the + * minimum and maximum value based on a comparator + */ + @Test + public void test_2_2_2() { + String data = "{\n" + + " \"key1\": 3,\n" + + " \"key2\": 42,\n" + + " \"key3\": 15,\n" + + " \"key4\": 8,\n" + + "}\n"; + + JSONObject jsonObject = new JSONObject(data); + Comparator> valueComparator = Comparator.comparingInt(entry -> (Integer) entry.getValue()); + + Optional> minEntry = jsonObject.stream() + .min(valueComparator); + Optional> maxEntry = jsonObject.stream() + .max(valueComparator); + + System.out.println("Min entry: " + minEntry); + System.out.println("Max entry: " + maxEntry); + assertTrue(minEntry.isPresent()); + assertEquals("key1: 3", minEntry.get().getKey() + ": " + minEntry.get().getValue()); + assertTrue(maxEntry.isPresent()); + assertEquals("key2: 42", maxEntry.get().getKey() + ": " + maxEntry.get().getValue()); + } + + /** + * Test Case 2.2.3: Use the stream() method to collect entries into a map grouping them + * by a classification function based on the keys + */ + @Test + public void test_2_2_3() { + String data = "{\n" + + " \"akey1\": \"abc\",\n" + + " \"bkey1\": 42,\n" + + " \"akeyA\": true,\n" + + " \"bkeyA\": null,\n" + + " \"akeyX\": [1,2,3],\n" + + " \"bkeyX\": {\"a\":\"b\"}\n" + + "}\n"; + + JSONObject jsonObject = new JSONObject(data); + Map>> groupedByInitial = jsonObject.stream() + .collect(Collectors.groupingBy(entry -> entry.getKey().charAt(4))); + + System.out.println(groupedByInitial); + assertEquals(3, groupedByInitial.size()); + assertEquals(2, groupedByInitial.get('1').size()); + assertEquals(2, groupedByInitial.get('A').size()); + assertEquals(2, groupedByInitial.get('X').size()); + } + + /** + * Test Case 2.3.1: Test the behavior of the stream() method when applied to an empty JSONObject + */ + @Test + public void test_2_3_1() { + JSONObject jsonObject = new JSONObject(); + List> entriesList = jsonObject.stream() + .collect(Collectors.toList()); + + System.out.println(entriesList); + assertTrue(entriesList.isEmpty()); + } + + /** + * Test Case 4.1.2: Use the stream() method to convert a JSONObject to a JSONArray and vice versa. + */ + @Test + public void test_2_4_1() { + String data = "{\n" + + " \"key1\": \"value1\",\n" + + " \"key2\": \"value2\"\n" + + "}\n"; + + JSONObject jsonObject = new JSONObject(data); + + // Convert JSONObject to JSONArray + JSONArray jsonArray = new JSONArray(); + jsonObject.stream() + .forEach(entry -> jsonArray.put(new JSONObject().put(entry.getKey(), entry.getValue()))); + + System.out.println(jsonArray); + + // Convert JSONArray back to JSONObject + JSONObject newJsonObject = new JSONObject(); + jsonArray.forEach(item -> { + JSONObject jsonItem = (JSONObject) item; + jsonItem.keySet().forEach(key -> newJsonObject.put(key, jsonItem.get(key))); + }); + + System.out.println(newJsonObject); + assertEquals(jsonObject.toString(), newJsonObject.toString()); + } + /** * Tests that the similar method is working as expected. */