From 7e5865ef293b876a0bc5508dbec546a80ee596ec Mon Sep 17 00:00:00 2001 From: jiachengzhuo <“richardjcheuk@gmail.com”> Date: Sat, 24 May 2025 20:08:04 -0700 Subject: [PATCH 1/2] Implement Milestone 4: toStream() with JSONNode support --- src/main/java/org/json/JSONObject.java | 62 ++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index a1664f708..2bce36024 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -17,6 +17,8 @@ import java.util.*; import java.util.Map.Entry; import java.util.regex.Pattern; +import java.util.stream.IntStream; +import java.util.stream.Stream; /** * A JSONObject is an unordered collection of name/value pairs. Its external @@ -3030,4 +3032,64 @@ private static String removeLeadingZerosOfNumber(String value){ if (negativeFirstChar) {return "-0";} return "0"; } + + // ---------------------------- Milestone 4 ------------------------------ + + /** + * Represents a node in the JSON object tree, with path and value. + */ + public static class JSONNode { + private final String path; + private final Object value; + + public JSONNode(String path, Object value) { + this.path = path; + this.value = value; + } + + public String getPath() { + return path; + } + + public Object getValue() { + return value; + } + + @Override + public String toString() { + return "JSONNode{path='" + path + "', value=" + value + '}'; + } + } + + /** + * Convert this JSONObject into a stream of JSONNode, allowing chained operations. + * @return Stream of JSONNode objects (each has a full path and a value) + */ + public Stream toStream() { + return toStream("", this); + } + + /** + * Recursive helper method to flatten JSONObject/JSONArray into JSONNode stream. + */ + private Stream toStream(String path, Object value) { + if (value instanceof JSONObject) { + JSONObject obj = (JSONObject) value; + return obj.keySet().stream() + .flatMap(key -> { + String newPath = path.isEmpty() ? "/" + key : path + "/" + key; + return toStream(newPath, obj.get(key)); + }); + } else if (value instanceof JSONArray) { + JSONArray array = (JSONArray) value; + return IntStream.range(0, array.length()) + .boxed() + .flatMap(i -> { + String newPath = path + "[" + i + "]"; + return toStream(newPath, array.get(i)); + }); + } else { + return Stream.of(new JSONNode(path, value)); + } + } } From b5e989b3e66b2310924bdb579c30f09963b61d9f Mon Sep 17 00:00:00 2001 From: hai tong yan <1770115879@qq.com> Date: Sun, 25 May 2025 17:21:52 -0700 Subject: [PATCH 2/2] add test cases and readme file --- README-M4.md | 18 +++++ .../tests/JSONObjectStreamTest.java | 74 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 README-M4.md create mode 100644 src/test/java/org/json/junit/milestone4/tests/JSONObjectStreamTest.java diff --git a/README-M4.md b/README-M4.md new file mode 100644 index 000000000..a5cdb8b32 --- /dev/null +++ b/README-M4.md @@ -0,0 +1,18 @@ +# Milestone 4 – Stream support for **JSONObject** + +## What is Added + +| Item | Description | +|------|-------------| +| `JSONObject.JSONNode` | Immutable leaf wrapper containing an absolute `path` and its `value`. | +| `Stream JSONObject.toStream()` | Depth-first, **lazy** flattening of any `JSONObject/JSONArray` into a `Stream`. Only leaf nodes are emitted (low memory footprint). | + +Path conventions +* Object keys: `/parent/child` +* Array items: `/array[0]/child` + +--- + +## Run the test class +```bash +mvn -Dtest=org.json.junit.milestone4.tests.JSONObjectStreamTest test \ No newline at end of file diff --git a/src/test/java/org/json/junit/milestone4/tests/JSONObjectStreamTest.java b/src/test/java/org/json/junit/milestone4/tests/JSONObjectStreamTest.java new file mode 100644 index 000000000..7957fed9f --- /dev/null +++ b/src/test/java/org/json/junit/milestone4/tests/JSONObjectStreamTest.java @@ -0,0 +1,74 @@ +package org.json.junit.milestone4.tests; + +import org.json.JSONObject; +import org.json.JSONObject.JSONNode; +import org.json.XML; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Optional; +import java.util.Scanner; +import java.util.stream.Collectors; + +import static org.junit.Assert.*; + +/** + * Unit tests for the JSONObject.toStream() extension (Milestone 4) – JUnit 4 / Java 8. + */ +public class JSONObjectStreamTest { + + private static JSONObject catalog; + + @BeforeClass + public static void loadXml() throws Exception { + try (InputStream in = JSONObjectStreamTest.class.getResourceAsStream("/books.xml")) { + assertNotNull("books.xml must be on the class-path (src/test/resources)", in); + + String xml; + try (Scanner scanner = new Scanner(in, StandardCharsets.UTF_8.name())) { + scanner.useDelimiter("\\A"); + xml = scanner.hasNext() ? scanner.next() : ""; + } + + catalog = XML.toJSONObject(xml); + } + } + + @Test + public void extractTitles() { + List titles = catalog.toStream() + .filter(n -> n.getPath().endsWith("/title")) + .map(n -> n.getValue().toString()) + .collect(Collectors.toList()); + + assertEquals(12, titles.size()); + assertTrue(titles.contains("XML Developer's Guide")); + assertTrue(titles.contains("Visual Studio 7: A Comprehensive Guide")); + } + + @Test + public void findExactPath() { + String wantedPath = "/catalog/book[0]/author"; + Optional node = catalog.toStream() + .filter(n -> n.getPath().equals(wantedPath)) + .findFirst(); + + assertTrue(node.isPresent()); + assertEquals("Gambardella, Matthew", node.get().getValue()); + } + + @Test + public void filterCheapBooks() { + List cheapPrices = catalog.toStream() + .filter(n -> n.getPath().endsWith("/price")) + .filter(n -> Double.parseDouble(n.getValue().toString()) < 10.0) + .collect(Collectors.toList()); + + assertEquals(8, cheapPrices.size()); + assertTrue(cheapPrices.stream() + .allMatch(n -> Double.parseDouble(n.getValue().toString()) < 10.0)); + } +}