3

I am currently learning a bit about streams. I have the following JSONArray, and I want to be able to retrieve all the distinct xvalues.

 datasets: {
    ds1: {
     xvalues: [
        "(empty)",
        "x1",
        "x2"
      ]
    },
    ds2: {
    xvalues: [
        "(empty)",
        "x1",
        "x2",
        "x3"
      ]
    }
}

I am trying the following code but it doesn't seem quite right....

List<String> xvalues = arrayToStream(datasets)
                .map(JSONObject.class::cast)
                .map(dataset -> {
                    try {
                         return dataset.getJSONArray("xvalues");
                    } catch (JSONException ex) {

                    }
                    return;
            })
            .distinct()
            .collect((Collectors.toList()));

private static Stream<Object> arrayToStream(JSONArray array) {
    return StreamSupport.stream(array.spliterator(), false);
}
pvpkiran
  • 25,582
  • 8
  • 87
  • 134
coffeeak
  • 2,980
  • 7
  • 44
  • 87
  • What doesn't seem quite right? Could you explain? (Maybe with a sample input and output.) – Naman Oct 07 '19 at 03:58
  • Possible duplicate of [Java 8 Distinct by property](https://stackoverflow.com/questions/23699371/java-8-distinct-by-property) – Naman Oct 07 '19 at 03:59

2 Answers2

0

What you get with .map(dataset -> dataset.getJSONArray("xvalues") (try-catch block omitted for sake of clarity) is a list itself and the subsequent call of distinct is used on the Stream<List<Object>> checking whether the lists itself with all its content is same to another and leave distinct lists.

You need to flat map the structure to Stream<Object> and then use the distinct to get the unique items. However, first, you need to convert JSONArray to List.

List<String> xvalues = arrayToStream(datasets)
    .map(JSONObject.class::cast)
    .map(dataset -> dataset -> {
         try { return dataset.getJSONArray("xvalues"); } 
         catch (JSONException ex) {}
         return;})
    .map(jsonArray -> toJsonArray(jsonArray ))         // convert it to List
    .flatMap(List::stream)                             // to Stream<Object>
    .distinct()
    .collect(Collectors.toList());
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
  • At ".flatMap(List::stream)", the IDE complains that "cannot call static method inside non-static" – coffeeak Oct 06 '19 at 20:51
  • Also, can you please provide an answer with the try, catch because this is also providing some difficulties for me. – coffeeak Oct 06 '19 at 20:53
  • 1) you can't just remove a good piece of code because it looks ugly (in fact, a checked exception might be thrown, so things get a little tricky) 2) you are missing `map(Something::toString)` 3) what's `toJsonArray`? 4) `Stream` doesn't look right – Andrew Tobilko Oct 06 '19 at 20:55
  • I realized I have to convert `JSONArray` to `List` before flat mapping. – Nikolas Charalambidis Oct 06 '19 at 20:58
  • @AndrewTobilko The only reason I removed `try-catch` is for the sake of clarity. I know it MUST be inside. – Nikolas Charalambidis Oct 06 '19 at 21:01
  • @Nikolas it makes the answer more obscure. It doesn't show what you would do in case of exception (return a null and filter them out, or rethrow an unchecked expection, or something else). – Andrew Tobilko Oct 06 '19 at 21:03
  • @AndrewTobilko: A matter of taste. I think that omitting things like `try-catch`, constructors, getters and setters is not an issue IF included in the comment (as I did) and is not a topic of the question. The OP doesn't say anything about handling the exception in the `Stream` (yet, it's not a best practice). – Nikolas Charalambidis Oct 06 '19 at 21:24
0

I believe using a json library(Jackson, Gson) is the best way to deal with json data. If you can use Jackson, this could be a solution

public class DataSetsWrapper {   
    private Map<String, XValue> datasets;
    //Getters, Setters
}

public class XValue {
    private List<String> xvalues;
    //Getters, Setters
}  



ObjectMapper objectMapper = new ObjectMapper();
DataSetsWrapper dataSetsWrapper = objectMapper.readValue(jsonString, DataSetsWrapper.class);

List<String> distinctXValues = dataSetsWrapper.getDatasets()
                .values()
                .stream()
                .map(XValue::getXvalues)
                .flatMap(Collection::stream)
                .distinct()
                .collect(Collectors.toList()); 

Replace jsonString with your json. I tested this with this json

String jsonString = "{\"datasets\": {\n" +
        "    \"ds1\": {\n" +
        "     \"xvalues\": [\n" +
        "        \"(empty)\",\n" +
        "        \"x1\",\n" +
        "        \"x2\"\n" +
        "      ]\n" +
        "    },\n" +
        "    \"ds2\": {\n" +
        "    \"xvalues\": [\n" +
        "        \"(empty)\",\n" +
        "        \"x1\",\n" +
        "        \"x2\",\n" +
        "        \"x3\"\n" +
        "      ]\n" +
        "    }\n" +
        "}}";
pvpkiran
  • 25,582
  • 8
  • 87
  • 134