Skip to content

Commit

Permalink
Provide same behavior for filtering in arrays via search's "filter" f…
Browse files Browse the repository at this point in the history
…or specified "condition"

* applying the `condition` also on "any" elements of an array of JsonObjects
* providing new `JsonObject` APIs: `containsFlatteningArrays(key)` and `getValueFlatteningArrays`

Signed-off-by: Thomas Jäckle <thomas.jaeckle@beyonnex.io>
  • Loading branch information
thjaeckle committed Jan 11, 2024
1 parent c30d459 commit 0fe94f7
Show file tree
Hide file tree
Showing 13 changed files with 403 additions and 89 deletions.
Expand Up @@ -12,8 +12,8 @@
*/
package org.eclipse.ditto.base.model.entity.metadata;

import static org.eclipse.ditto.json.JsonFactory.newValue;
import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;
import static org.eclipse.ditto.json.JsonFactory.newValue;

import java.io.IOException;
import java.util.Iterator;
Expand All @@ -27,6 +27,7 @@
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
import org.eclipse.ditto.json.JsonArray;
import org.eclipse.ditto.json.JsonCollectors;
import org.eclipse.ditto.json.JsonFactory;
Expand All @@ -38,7 +39,6 @@
import org.eclipse.ditto.json.JsonPointer;
import org.eclipse.ditto.json.JsonValue;
import org.eclipse.ditto.json.SerializationContext;
import org.eclipse.ditto.base.model.json.JsonSchemaVersion;

/**
* Immutable implementation of {@link org.eclipse.ditto.base.model.entity.metadata.Metadata}.
Expand Down Expand Up @@ -211,6 +211,11 @@ public boolean contains(final CharSequence key) {
return wrapped.contains(key);
}

@Override
public boolean containsFlatteningArrays(final CharSequence key) {
return wrapped.containsFlatteningArrays(key);
}

@Override
public JsonObject get(final JsonPointer pointer) {
return wrapped.get(pointer);
Expand Down Expand Up @@ -241,6 +246,11 @@ public Optional<JsonValue> getValue(final CharSequence key) {
return wrapped.getValue(key);
}

@Override
public Optional<JsonValue> getValueFlatteningArrays(final CharSequence key) {
return wrapped.getValueFlatteningArrays(key);
}

@Override
public List<JsonKey> getKeys() {
return wrapped.getKeys();
Expand Down
Expand Up @@ -24,6 +24,7 @@
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
import org.eclipse.ditto.json.JsonArray;
import org.eclipse.ditto.json.JsonFactory;
import org.eclipse.ditto.json.JsonField;
Expand All @@ -35,7 +36,6 @@
import org.eclipse.ditto.json.JsonPointer;
import org.eclipse.ditto.json.JsonValue;
import org.eclipse.ditto.json.SerializationContext;
import org.eclipse.ditto.base.model.json.JsonSchemaVersion;

/**
* JSON NULL value version of {@link org.eclipse.ditto.base.model.entity.metadata.Metadata}.
Expand Down Expand Up @@ -190,11 +190,21 @@ public boolean contains(final CharSequence key) {
return false;
}

@Override
public boolean containsFlatteningArrays(final CharSequence key) {
return false;
}

@Override
public Optional<JsonValue> getValue(final CharSequence name) {
return Optional.empty();
}

@Override
public Optional<JsonValue> getValueFlatteningArrays(final CharSequence key) {
return Optional.empty();
}

@Override
public JsonObject get(final JsonPointer pointer) {
return this;
Expand Down
115 changes: 85 additions & 30 deletions json/src/main/java/org/eclipse/ditto/json/ImmutableJsonObject.java
Expand Up @@ -29,6 +29,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -223,36 +224,60 @@ private static boolean isEmpty(final Iterable<?> iterable) {
@Override
public boolean contains(final CharSequence key) {
requireNonNull(key, "The key or pointer to check the existence of a value for must not be null!");
return internalContains(key, false);
}

@Override
public boolean containsFlatteningArrays(final CharSequence key) {
requireNonNull(key, "The key or pointer to check the existence of a value for must not be null!");
return internalContains(key, true);
}

boolean internalContains(final CharSequence key, final boolean flatteningArrays) {
final boolean result;

final JsonPointer pointer = JsonPointer.of(key);

if (1 >= pointer.getLevelCount()) {
result = pointer.getRoot().map(this::containsKey).orElse(false);
} else {
result = pointer.getRoot()
.flatMap(this::getValueForKey)
.filter(JsonValue::isObject)
.map(JsonValue::asObject)
.map(jsonObject -> jsonObject.contains(pointer.nextLevel()))
.orElse(false);
result = containsPointer(pointer, flatteningArrays);
}

return result;
}

private Boolean containsPointer(final JsonPointer pointer, final boolean flatteningArrays) {
return pointer.getRoot()
.flatMap(this::getValueForKey)
.filter(val -> val.isObject() || (flatteningArrays && val.isArray()))
.map(val -> val.isObject() ? Stream.of(val.asObject()) :
val.asArray().stream().filter(JsonValue::isObject).map(JsonValue::asObject)
)
.map(stream -> stream.anyMatch(jsonObject -> flatteningArrays ?
jsonObject.containsFlatteningArrays(pointer.nextLevel()) :
jsonObject.contains(pointer.nextLevel()))
)
.orElse(false);
}

private boolean containsKey(final CharSequence key) {
return fieldMap.containsKey(key.toString());
}

@Override
public Optional<JsonValue> getValue(final CharSequence key) {
requireNonNull(key, "The key or pointer of the value to be retrieved must not be null!");
return getValueForPointer(JsonPointer.of(key));
return getValueForPointer(JsonPointer.of(key), false);
}

@Override
public Optional<JsonValue> getValueFlatteningArrays(final CharSequence key) {
requireNonNull(key, "The key or pointer of the value to be retrieved must not be null!");
return getValueForPointer(JsonPointer.of(key), true);
}

private Optional<JsonValue> getValueForPointer(final JsonPointer pointer) {
private Optional<JsonValue> getValueForPointer(final JsonPointer pointer, final boolean flatteningArrays) {
final Optional<JsonValue> result;

final JsonKey rootKey = pointer.getRoot().orElse(ROOT_KEY);
Expand All @@ -263,10 +288,31 @@ private Optional<JsonValue> getValueForPointer(final JsonPointer pointer) {
// same as getting a value for a key
result = getValueForKey(rootKey);
} else {
result = getValueForKey(rootKey)
.filter(JsonValue::isObject)
.map(JsonValue::asObject)
.flatMap(jsonObject -> jsonObject.getValue(pointer.nextLevel()));
final AtomicReference<Boolean> valueIsArray = new AtomicReference<>(false);
final List<JsonValue> collected = getValueForKey(rootKey).map(Stream::of).orElse(Stream.empty())
.filter(val -> val.isObject() || (flatteningArrays && val.isArray()))
.flatMap(val -> {
if (val.isObject()) {
return Stream.of(val.asObject());
} else {
valueIsArray.set(true);
return val.asArray().stream().filter(JsonValue::isObject).map(JsonValue::asObject);
}
})
.flatMap(jsonObject -> flatteningArrays ?
jsonObject.getValueFlatteningArrays(pointer.nextLevel())
.map(Stream::of).orElseGet(Stream::empty) :
jsonObject.getValue(pointer.nextLevel())
.map(Stream::of).orElseGet(Stream::empty)
).collect(Collectors.toList());

if (collected.isEmpty()) {
result = Optional.empty();
} else if (Boolean.TRUE.equals(valueIsArray.get())) {
result = Optional.of(collected.stream().collect(JsonCollectors.valuesToArray()));
} else {
result = Optional.of(collected.get(0));
}
}

return result;
Expand All @@ -281,7 +327,7 @@ private Optional<JsonValue> getValueForKey(final CharSequence key) {
public <T> Optional<T> getValue(final JsonFieldDefinition<T> fieldDefinition) {
checkFieldDefinition(fieldDefinition);

return getValueForPointer(fieldDefinition.getPointer()).map(fieldDefinition::mapValue);
return getValueForPointer(fieldDefinition.getPointer(), false).map(fieldDefinition::mapValue);
}

private static void checkFieldDefinition(final JsonFieldDefinition<?> fieldDefinition) {
Expand All @@ -308,7 +354,7 @@ public JsonObject get(final JsonPointer pointer) {
final Optional<JsonFieldDefinition> rootKeyDefinition = getDefinitionForKey(rootKey);
if (1 >= pointer.getLevelCount()) {
result = rootKeyValue.map(
jsonValue -> JsonField.newInstance(rootKey, jsonValue, rootKeyDefinition.orElse(null)))
jsonValue -> JsonField.newInstance(rootKey, jsonValue, rootKeyDefinition.orElse(null)))
.map(jsonField -> Collections.singletonMap(jsonField.getKeyName(), jsonField))
.map(ImmutableJsonObject::of)
.orElseGet(ImmutableJsonObject::empty);
Expand All @@ -321,16 +367,16 @@ public JsonObject get(final JsonPointer pointer) {
.isPresent();

result = rootKeyValue.map(jsonValue -> {
if (jsonValue.isObject()) {
if (containsNextLevelRootKey.test(jsonValue.asObject())) {
return jsonValue.asObject().get(nextPointerLevel); // Recursion
} else {
return null;
}
} else {
return jsonValue;
}
})
if (jsonValue.isObject()) {
if (containsNextLevelRootKey.test(jsonValue.asObject())) {
return jsonValue.asObject().get(nextPointerLevel); // Recursion
} else {
return null;
}
} else {
return jsonValue;
}
})
.map(jsonValue -> JsonField.newInstance(rootKey, jsonValue, rootKeyDefinition.orElse(null)))
.map(jsonField -> Collections.singletonMap(jsonField.getKeyName(), jsonField))
.map(ImmutableJsonObject::of)
Expand Down Expand Up @@ -360,7 +406,7 @@ public JsonObject get(final JsonFieldSelector fieldSelector) {

final List<JsonPointer> pointersContainedInThis = fieldSelector.getPointers()
.stream()
.filter(this::contains)
.filter(this::containsFlatteningArrays)
.collect(Collectors.toList());

if (pointersContainedInThis.isEmpty()) {
Expand All @@ -381,9 +427,17 @@ private static JsonObject filterByTrie(final JsonObject self, final JsonFieldSel
for (final JsonKey key : trie.getKeys()) {
self.getField(key).ifPresent(child -> {
final JsonValue childValue = child.getValue();
final JsonValue filteredChildValue = childValue.isObject()
? filterByTrie(childValue.asObject(), trie.descend(key))
: childValue;
final JsonValue filteredChildValue;
if (childValue.isObject()) {
filteredChildValue = filterByTrie(childValue.asObject(), trie.descend(key)); // recurse!
} else if (childValue.isArray()) {
filteredChildValue = childValue.asArray().stream()
.filter(JsonValue::isObject)
.map(value -> filterByTrie(value.asObject(), trie.descend(key))) // recurse!
.collect(JsonCollectors.valuesToArray());
} else {
filteredChildValue = childValue;
}
final Optional<JsonFieldDefinition> childFieldDefinition = child.getDefinition();
if (childFieldDefinition.isPresent()) {
builder.set(childFieldDefinition.get(), filteredChildValue);
Expand Down Expand Up @@ -422,7 +476,8 @@ private JsonObject removeForPointer(final JsonPointer pointer) {
.map(JsonValue::asObject)
.filter(containsNextLevelRootKey)
.map(jsonObject -> jsonObject.remove(nextPointerLevel)) // Recursion
.map(withoutValue -> JsonField.newInstance(rootKey, withoutValue, getDefinitionForKey(rootKey).orElse(null)))
.map(withoutValue -> JsonField.newInstance(rootKey, withoutValue,
getDefinitionForKey(rootKey).orElse(null)))
.map(this::set)
.orElse(this);
}
Expand Down Expand Up @@ -568,7 +623,7 @@ private SoftReferencedFieldMap(final Map<String, JsonField> jsonFieldMap,
if (CBOR_FACTORY.isCborAvailable()) {
try {
this.cborObjectRepresentation = CBOR_FACTORY.createCborRepresentation(jsonFieldMap,
guessSerializedSize());
guessSerializedSize());
} catch (final IOException e) {
assert false; // this should not happen, so assertions will throw during testing
jsonObjectStringRepresentation = createStringRepresentation(jsonFieldMap);
Expand Down
Expand Up @@ -112,6 +112,11 @@ public boolean contains(final CharSequence key) {
return false;
}

@Override
public boolean containsFlatteningArrays(final CharSequence key) {
return false;
}

@Override
public ImmutableJsonObjectNull get(final JsonPointer pointer) {
return this;
Expand All @@ -127,6 +132,11 @@ public Optional<JsonValue> getValue(final CharSequence key) {
return Optional.empty();
}

@Override
public Optional<JsonValue> getValueFlatteningArrays(final CharSequence key) {
return Optional.empty();
}

@Override
public <T> Optional<T> getValue(final JsonFieldDefinition<T> fieldDefinition) {
return Optional.empty();
Expand Down

0 comments on commit 0fe94f7

Please sign in to comment.