vert.x icon indicating copy to clipboard operation
vert.x copied to clipboard

JsonObject and JsonArray in Java streams causing ClassCastException

Open DarwinsBuddy opened this issue 3 years ago • 4 comments

Issue

While trying to sort a stream of JsonObjects I encountered a ClassCastException, due to missing interface Comparable and thus missing implementation of compareTo on JsonObject (and also JsonArray). Without knowing if this interface was omitted on purpose from those two datatypes, I am raising the question, if a simple implementation of Comparable on both JsonObject and JsonArray could be provided, so at least we don't get a ClassCastException, when working with Java Streams and alike?

Version

4.0.3

Reproduce JsonObject ClassCastException

List l1 = List.of(new JsonObject().put("foo", "bar"), new JsonObject().put("fnord", "fnord"));

l1.stream().sorted().collect(Collectors.toList())

Reproduce JsonArray ClassCastException

List l2 = List.of(new JsonArray(List.of("foo", "bar")), new JsonArray(List.of("fnord", "fnord")));

l2.stream().sorted().collect(Collectors.toList())

DarwinsBuddy avatar Mar 31 '21 15:03 DarwinsBuddy

how do you compare two json objects or json array ?

vietj avatar Apr 01 '21 09:04 vietj

@vietj tough question. Here my suggestion:

JsonObject

  1. Compare alphabetically sorted keys individually
  2. For each key call compareTo on their respective values. if result < 0 return result else go on with comparison
  3. throw ClassCastException if some value is not implementing Comparable (this shouldn't be the case, as this change has to consist of JsonObject and JsonArray Comparable implementation)

JsonArray

  1. sort array with natural order
  2. compare individual elements with respective compareTo and proceed as mentioned above: if result < 0 return result else go on with comparison
  3. throw ClassCastException if some value is not implementing Comparable (this shouldn't be the case, as this change has to consist of JsonObject and JsonArray Comparable implementation)

As it may be odd to implement Comparable on JsonObject, due to the lack of an obvious interpretation of order, the benefit of having a deterministic way of somehow sorting and thus comparing two of above objects is hardly deniable.

Above proposal is merely an idea and frankly, it may not suite the vertx way of doing stuff.

Anyhow, a quick google search as well provides different approaches on achieving the goal in question via jackson's implementation: https://www.baeldung.com/jackson-compare-two-json-objects

DarwinsBuddy avatar Apr 01 '21 13:04 DarwinsBuddy

ok, but here your example compares two JsonObject or two JsonArray which are not related to the order you describe

vietj avatar Apr 01 '21 14:04 vietj

I'm having trouble getting your point.

See below a proof-of-concept for a possible JsonObject Comparable implementation:

import io.vertx.core.json.JsonObject;
import org.jetbrains.annotations.NotNull;

import java.util.List;
import java.util.stream.Collectors;

    public class MyJsonObject extends JsonObject implements Comparable {

    public MyJsonObject() {
        super();
    }

    @Override
    public MyJsonObject put(String key, Object value) {
        super.put(key, value);
        return this;
    }

    @Override
    public int compareTo(@NotNull Object o) {
        if(o instanceof MyJsonObject that) {
            if(this.size() != that.size()) {
                return this.size() - that.size();
            }
            List<String> thisKeys = this.getMap().keySet().stream().sorted().collect(Collectors.toList());
            List<String> thatKeys = this.getMap().keySet().stream().sorted().collect(Collectors.toList());
            for(int i = 0; i < thisKeys.size(); i++){
                int comparedKey = thisKeys.get(i).compareTo(thatKeys.get(i));
                if(comparedKey != 0) {
                    return comparedKey;
                }
                Object thisValue = this.getValue(thisKeys.get(i));
                Object thatValue = this.getValue(thatKeys.get(i));
                if(!thisValue.getClass().equals(thatValue.getClass())) {
                    return -1;
                }
                if(thisValue instanceof Comparable thisc) {
                    if(thatValue instanceof Comparable thatc) {
                        int comparedValue = thisc.compareTo(thatc);
                        if (comparedValue != 0) {
                            return comparedValue;
                        }
                    } else {
                        throw new ClassCastException("Incomparable types");
                    }
                } else {
                    throw new ClassCastException("Incomparable types");
                }
            }
            return 0;
        } else {
            throw new ClassCastException("Incomparable types");
        }
    }
}

MyJsonObject x = new MyJsonObject().put("bar", "foo").put("zeppelin", "xaver");
MyJsonObject y = new MyJsonObject().put("zeppelin", "xaver").put("foo", "bar");
MyJsonObject z = new MyJsonObject().put("zeppelin", "xaver").put("foo", "bar").put("alpha", "beta");

x.compareTo(y);
y.compareTo(x);

x.compareTo(z);
z.compareTo(x);

List.of(z,x,y).stream().sorted().collect(Collectors.toList());

yielding

class MyJsonObject

field MyJsonObject x = {"bar":"foo","zeppelin":"xaver"}

field MyJsonObject y = {"zeppelin":"xaver","foo":"bar"}

field MyJsonObject z = {"zeppelin":"xaver","foo":"bar","alpha":"beta"}

x.compareTo(y) = 0

y.compareTo(x) = 0

x.compareTo(z) = -1

z.compareTo(x) = 1

List.of(z,x,y).stream().sorted().collect(Collectors.toList()) = [{"bar":"foo","zeppelin":"xaver"}, {"zeppelin":"xaver","foo":"bar"}, {"zeppelin":"xaver","foo":"bar","alpha":"beta"}]

DarwinsBuddy avatar Apr 01 '21 18:04 DarwinsBuddy