Skip to content

Streams support - #768

Closed
stleary wants to merge 10 commits into
masterfrom
streams
Closed

stleary wants to merge 10 commits into
masterfrom
streams

Conversation

@stleary

@stleary stleary commented Sep 4, 2023 •

Copy link
Copy Markdown
Owner

What problem does this code solve?
JSONObject and JSONArray should support streams for functional programming.
There are multiple ways this could be added. If you know of a better way, please post your comments to this PR.

This change requires Java 8. Going forward, Java 6 and Java 7 library builds are no longer supported. The last release for Java 6/7 is 20230618. Users who still need Java 6/7 support should open an issue in this repo.

Risks
Low

Changes to the API?
New API methods were added

Will this require a new release?
Yes

Should the documentation be updated?
No

Does it break the unit tests?
No. New unit tests were added

Was any code refactored in this commit?

  • gradlew permissions were updated
  • ParserConfiguration warnings were addressed

Review status
code review action items pending

@johnjaylward

Copy link
Copy Markdown
Contributor

I'm trying to add review notes here, but it seems that GitHub is having issues.

I think for this implementation we should also consider #673 and #674.

@johnjaylward johnjaylward left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this looks like a good start, However I'd personally like to see a few more test cases around getting data out and null handling in both the JSONObject and JSONArray streams.

Looking at #673 and #674, I think we should look towards always returning valid JSON value types from the streams. It is something we missed out on with the iterators and prevents people from having a consistent interface.

We should also be sure that the streams themselves (specifically the ones provided as part of the API, not the follow-on calls as part of the tests) do not throw exceptions by using opt operations wherever possible.

Comment thread src/main/java/org/json/JSONArray.java Outdated
* @return Stream of array elements
*/
public Stream<Object> stream() {
return IntStream.range(0, length()).mapToObj(this::get);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should not be this::get and instead should be this::opt. Stream should not throw exceptions, which get will do if a value is null.

We should also be sure to include at least one test case that has null in the array.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should not be this::get and instead should be this::opt. Stream should not throw exceptions, which get will do if a value is null.

We should also be sure to include at least one test case that has null in the array.

Seems to be working as is, but not sure why. Test cases are updated, let me know if this code or the tests should still be changed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I stepped through the code and that Parser is converting null to JSONObject.NULL, which is expected.

Let's add these 2 additional tests, which are very similar, but add different ways of composing the data instead of just using the parser. This second test shows the issue I was worried about:

@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<Object> 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<Object> 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);
            }
        }
    }

@stleary stleary Sep 20, 2023 •

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added tests.
Replaced this::get with this::opt

* @version 2016-08-15
*/
public class JSONObject {
public class JSONObject implements Iterable<String> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is the best Iterable<> that could have been chosen.

Would it make more sense to implement this closer to the Map<>.entrySet()? Something like: public class JSONObject implements Iterable<Map.Entry<String, Object>>?

Or maybe even better would be to keep it very similar to Map<> and not have it directly implement Iterable<> at all. Instead offer similar functions that the Map interface does:

  • keySet
  • entrySet
  • values

Each of those methods would return proper Iterable implementations.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation now streams entrySet() without Iterable<>.

JSONArray jsonArray = new JSONArray(data);

// Get employees from the engineering dept
List<String> actualList = StreamSupport.stream(jsonArray.spliterator(), false)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test doesn't seem to be testing the new Stream method. It appears to only be testing Iteratable<>.spliterator() method.

This is a valid test, but I don't think this is doing the coverage that you wanted for the new method.

@stleary stleary Sep 20, 2023 •

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced with new tests

@stleary

stleary commented Sep 16, 2023

Copy link
Copy Markdown
Owner Author

I'm trying to add review notes here, but it seems that GitHub is having issues.

I think for this implementation we should also consider #673 and #674.

Can this be done in a subsequent commit? The other issues seem out of scope for this initial implementation.

public void testStream_1_3_2() {
JSONArray jsonArray = null;

assertThrows(NullPointerException.class, () -> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this test is adding anything here. It seems obvious that an NPE would be thrown in this case.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

public void test_2_3_2() {
JSONObject jsonObject = null;

assertThrows(NullPointerException.class, () -> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this test is adding anything here. It seems obvious that an NPE would be thrown in this case.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

Comment thread src/main/java/org/json/JSONArray.java Outdated
* @return Stream of array elements
*/
public Stream<Object> stream() {
return IntStream.range(0, length()).mapToObj(this::get);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I stepped through the code and that Parser is converting null to JSONObject.NULL, which is expected.

Let's add these 2 additional tests, which are very similar, but add different ways of composing the data instead of just using the parser. This second test shows the issue I was worried about:

@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<Object> 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<Object> 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);
            }
        }
    }

@stleary

stleary commented Sep 20, 2023

Copy link
Copy Markdown
Owner Author

Added the new unit tests

@stleary

stleary commented Oct 5, 2023

Copy link
Copy Markdown
Owner Author

Closing due to lack of activity.

@stleary stleary closed this Oct 5, 2023
@stleary
stleary deleted the streams branch July 1, 2025 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants