1

Im using net.sf.json.JSONArray and net.sf.json.JSONObject. JSONArray contains multiple JSONObject.

Basically this:

[
    {
        "obj1": [
            {
                "ID": 12
                "NAME":"Whatever",
                "XY":[1,2]
            },
            {
                "ID": 34
                "NAME":"Again",
                "XY":[23,43]
            },
            etc
        ]
    },
    { "obj2": repeat}
]

I would like to flatten that with Java 8, i.e. result:

[
    {
        "obj1": [
                    [12,'Whatever',1,2],
                    [34,'Again',23,43],
                    etc...
        ]
    },
    { "obj2": repeat}
]
sherkal
  • 57
  • 2
  • 8

2 Answers2

3

Despite you can easily accomplish this in imperative manner with/out recursion, it might be easier to implement in functional style. I'm not sure if I'm good at idiomatic functional code in Java 8, but you'd need:

  • A collector to collect items to a single array
static Collector<Object, JSONArray, JSONArray> toJSONArray() {
    return Collector.of(
            JSONArray::new,     // Create a new array
            JSONArray::add,     // Add each element to the target array
            (a1, a2) -> {       // Merge the second array into the first one
                a1.addAll(a2);
                return a1;
            },
            identity()          // Return the populated array itself
    );
}
  • A recursive flatten method (inspired by Recursive use of Stream.flatMap()). Not sure whether it can be implemented easier (Q43481457 is just the current class name).
static Stream<?> flatten(final Object value) {
    return value instanceof Collection
            ? ((Collection<?>) value).stream().flatMap(Q43481457::flatten) // Flatten recursively
            : Stream.of(value);                                            // Otherwise wrap a single element into a stream
}
  • And the main code:
@SupressWarnings("unchecked")
final Collection<JSON> jsonArray = (Collection<JSON>) originalJsonArray;
final JSONArray flatJSONArray = jsonArray.stream()
        .map(json -> (Map<?, ?>) json)          // All outer array elements are JSON objects and maps in particular
        .map(jsonObject -> jsonObject.values()  // Recursively flatten the JSON object values
                .stream()
                .flatMap(Q43481457::flatten)
                .collect(toJSONArray())         // ... collecting them to JSON arrays
        )
        .collect(toJSONArray());                // ... which are collected to JSON arrays
System.out.println(flatJSONArray);

Output:

[[12,"Whatever",1,2],[34,"Again",23,43]]

Community
  • 1
  • 1
Lyubomyr Shaydariv
  • 20,327
  • 12
  • 64
  • 105
  • To me it doesnt compile. At .values() here : `.map(jsonObject -> jsonObject.values()` cannot find symbol symbol: method values() – sherkal Apr 19 '17 at 13:21
  • @sherkal What do you use to compile with? – Lyubomyr Shaydariv Apr 19 '17 at 13:22
  • NetBeans 8 JDK 8 – sherkal Apr 19 '17 at 13:39
  • @sherkal Strange. I'm using `javac` from JDK 1.8/121 - works fine. – Lyubomyr Shaydariv Apr 19 '17 at 13:46
  • @sherkal What are your imports? – Lyubomyr Shaydariv Apr 19 '17 at 13:49
  • import java.util.Collection; import java.util.Map; import static java.util.function.Function.identity; import java.util.stream.Collector; import java.util.stream.Stream; import net.sf.json.JSONArray; – sherkal Apr 19 '17 at 14:01
  • @sherkal It's weird. What if you try to replace cast `(Map, ?>)` with `JSONObject` (or something like this, can't really recall)? – Lyubomyr Shaydariv Apr 19 '17 at 14:03
  • @sherkal It's really strange. What if you replace the lambda expression you mentioned in your comment with a anonymous class (not sure if NetBeans can do such a refactor) `Function` (if I'm not mistaken)? – Lyubomyr Shaydariv Apr 19 '17 at 14:12
  • doesnt seem to work either :/ jsonObject is just an instance of Object it seems, tried casting it to JSONObject but doesnt work. – sherkal Apr 19 '17 at 14:20
  • @sherkal Here are my imports: `import java.util.Collection`; `import java.util.Map`; `import java.util.stream.Collector`; `import java.util.stream.Stream`; `import net.sf.json.JSON`; `import net.sf.json.JSONArray`; `import static java.util.function.Function.identity`; `import static net.sf.json.JSONArray.fromObject`; . How is your `jsonArray` typed? It must be at least `Collection` to be able to provide a stream of `JSON` instances. – Lyubomyr Shaydariv Apr 19 '17 at 14:30
  • just JSONArray jsonArray = new JSONArray(); – sherkal Apr 19 '17 at 14:53
  • @sherkal This is probably the issue: `JSONArray` is a raw list. Cast it to `Collection` so your stream could be typed. – Lyubomyr Shaydariv Apr 19 '17 at 15:03
0

Usage

JSONArray array = ...;
JSONArray flattedArray = flatten(array);

JSONObject map = ...;
JSONObject flattedMap = flatten(map);

Implementation

When the json structure changed the implementation has undergone tremendous changes that you can comparing my commit history on github. and the tests can tell you how I implementing your features.

public JSONArray flatten(Collection<?> array) {
    return array.stream().flatMap(this::flatting).collect(toJSONArray());
}

private Stream<?> flatting(Object it) {
    if (it instanceof Collection) {
        return ((Collection<?>) it).stream();
    }
    if (it instanceof Map) {
        return Stream.of(flatten((Map<?, ?>) it));
    }
    return Stream.of(it);
}

public JSONObject flatten(Map<?, ?> map) {
    return map.entrySet().stream().collect(
            JSONObject::new,
            (it, field) -> it.put(field.getKey(), flatten(field.getValue())),
            JSONObject::putAll
    );
}

private Object flatten(Object it) {
    if (it instanceof Collection) {
        return ((Collection<?>) it).stream().map(this::flatten)
                                            .collect(toJSONArray());
    }
    if (it instanceof Map) {
        return flatten(((Map<?, ?>) it).values());
    }
    return it;
}

private <T> Collector<T, ?, JSONArray> toJSONArray() {
    return toCollection(JSONArray::new);
}
holi-java
  • 29,655
  • 7
  • 72
  • 83