-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix JsonArray contains for non-wrapped JsonArray/JsonObject #5398
base: master
Are you sure you want to change the base?
Conversation
4b5b6d5
to
fdecd4f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for this PR @kristian
if (list.contains(value)) { | ||
return true; | ||
} | ||
|
||
if (value instanceof JsonObject) { | ||
return list.contains(((JsonObject) value).getMap()); | ||
} else if (value instanceof JsonArray) { | ||
return list.contains(((JsonArray) value).getList()); | ||
} | ||
|
||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the worst scenario, we will iterate over the list three times. Besides, we want to be able to check if the JsonArray contains a JsonArray or JsonObject regardless of how it's stored, not to check if a JsonArray contains an arbitrary list or map.
I would suggest to use something like this:
@SuppressWarnings("unchecked")
public boolean contains(Object value) {
if (value == null) {
for (Object entry : list) {
if (entry == null) {
return true;
}
}
} else {
for (Object entry : list) {
if (entry instanceof Map && value instanceof JsonObject) {
return value.equals(new JsonObject((Map<String, Object>) entry));
}
if (entry instanceof List && value instanceof JsonArray) {
return value.equals(new JsonArray((List<String>) entry));
}
if (value.equals(entry)) {
return true;
}
}
}
return false;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion @tsegismont. Yes I was debating back and forth whether to essentially re-implement ArrayList#contains
or whether the better code quality with the downside of iterating over the list a maximum number of 2 times (not 3, because it is either a JsonArray
or JsonObject
you want to check for) is the better solution. I can agree that we should go for the option that is better from a performance point of view. In my solution I did optimize for doing less comparisons, however I agree that the iteration itself is much more time consuming than doing the comparison operations.
Besides, we want to be able to check if the
JsonArray
contains aJsonArray
orJsonObject
regardless of how it's stored, not to check if aJsonArray
contains an arbitrary list or map.
Mhm, as far as I am concerned, I would argue that a Map / List inside of a JsonArray
is virtually equivalent to it's wrapped JsonArray
/ JsonObject
counterpart. Reason for me is the public interface of JsonArray
does wrapping on getting anyways, so classic duck-typing principle: If it walks like a duck and it quacks like a duck, then it must be a duck. An arbitrary Map
/ List
is not supported inside of a JsonArray
, it is just a "vehicle" for representing nested arrays anyways. This is why for your solution I would argue that creating temporary JsonObject
/ JsonArray
depending on the size of the JsonArray
creates a lot of temporary objects and thus a lot of pressure on the heap / garbage collection.
I would maybe suggest to "mix" both solutions and:
a) Keep the assumption that Map
/ List
inside of JsonArray
is equivalent to JsonObject
/ JsonArray
and
b) Re-implement contains
based on ArrayList#contains
as you suggested.
Let me make a proposal to continue our discussion. 👍 Thanks for the good suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tsegismont I made a new suggestion in my latest force push.
I do not think that there is a good way to fix this behavior. I think the best thing you can do is to document this. For example, if you apply this fix, then the following would work: JsonArray jsonArray = JsonArray.of(Map.of("foo", "bar"));
assertTrue(jsonArray.contains(JsonObject.of("foo", "bar")));
assertTrue(jsonArray.contains(Map.of("foo", "bar"))); That is also a behavior that is not expected. When you decided to change the returned value (e.g., Instant now1 = Instant.now();
JsonArray jsonArray = JsonArray.of(now1);
assertTrue(jsonArray.contains(now1));
for (Object o : jsonArray) {
assertTrue(jsonArray.contains(o));
} This test will fail, which is unexpected to me. You can’t easily fix this because I consider the |
@halber with the suggestions made in the review, there would be no weird cases with JsonObject/JsonArray entries. But you're right, we need some Javadoc on the |
fdecd4f
to
857fc15
Compare
Fixes #5397