Skip to content

Commit 0fe94f7

Browse files
committed
Provide same behavior for filtering in arrays via search's "filter" for 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 <[email protected]>
1 parent c30d459 commit 0fe94f7

File tree

13 files changed

+403
-89
lines changed

13 files changed

+403
-89
lines changed

base/model/src/main/java/org/eclipse/ditto/base/model/entity/metadata/ImmutableMetadata.java

+12-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
*/
1313
package org.eclipse.ditto.base.model.entity.metadata;
1414

15-
import static org.eclipse.ditto.json.JsonFactory.newValue;
1615
import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;
16+
import static org.eclipse.ditto.json.JsonFactory.newValue;
1717

1818
import java.io.IOException;
1919
import java.util.Iterator;
@@ -27,6 +27,7 @@
2727
import javax.annotation.Nullable;
2828
import javax.annotation.concurrent.Immutable;
2929

30+
import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
3031
import org.eclipse.ditto.json.JsonArray;
3132
import org.eclipse.ditto.json.JsonCollectors;
3233
import org.eclipse.ditto.json.JsonFactory;
@@ -38,7 +39,6 @@
3839
import org.eclipse.ditto.json.JsonPointer;
3940
import org.eclipse.ditto.json.JsonValue;
4041
import org.eclipse.ditto.json.SerializationContext;
41-
import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
4242

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

214+
@Override
215+
public boolean containsFlatteningArrays(final CharSequence key) {
216+
return wrapped.containsFlatteningArrays(key);
217+
}
218+
214219
@Override
215220
public JsonObject get(final JsonPointer pointer) {
216221
return wrapped.get(pointer);
@@ -241,6 +246,11 @@ public Optional<JsonValue> getValue(final CharSequence key) {
241246
return wrapped.getValue(key);
242247
}
243248

249+
@Override
250+
public Optional<JsonValue> getValueFlatteningArrays(final CharSequence key) {
251+
return wrapped.getValueFlatteningArrays(key);
252+
}
253+
244254
@Override
245255
public List<JsonKey> getKeys() {
246256
return wrapped.getKeys();

base/model/src/main/java/org/eclipse/ditto/base/model/entity/metadata/NullMetadata.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import javax.annotation.Nullable;
2525
import javax.annotation.concurrent.Immutable;
2626

27+
import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
2728
import org.eclipse.ditto.json.JsonArray;
2829
import org.eclipse.ditto.json.JsonFactory;
2930
import org.eclipse.ditto.json.JsonField;
@@ -35,7 +36,6 @@
3536
import org.eclipse.ditto.json.JsonPointer;
3637
import org.eclipse.ditto.json.JsonValue;
3738
import org.eclipse.ditto.json.SerializationContext;
38-
import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
3939

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

193+
@Override
194+
public boolean containsFlatteningArrays(final CharSequence key) {
195+
return false;
196+
}
197+
193198
@Override
194199
public Optional<JsonValue> getValue(final CharSequence name) {
195200
return Optional.empty();
196201
}
197202

203+
@Override
204+
public Optional<JsonValue> getValueFlatteningArrays(final CharSequence key) {
205+
return Optional.empty();
206+
}
207+
198208
@Override
199209
public JsonObject get(final JsonPointer pointer) {
200210
return this;

json/src/main/java/org/eclipse/ditto/json/ImmutableJsonObject.java

+85-30
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.Objects;
3030
import java.util.Optional;
3131
import java.util.ServiceLoader;
32+
import java.util.concurrent.atomic.AtomicReference;
3233
import java.util.function.Predicate;
3334
import java.util.stream.Collectors;
3435
import java.util.stream.Stream;
@@ -223,36 +224,60 @@ private static boolean isEmpty(final Iterable<?> iterable) {
223224
@Override
224225
public boolean contains(final CharSequence key) {
225226
requireNonNull(key, "The key or pointer to check the existence of a value for must not be null!");
227+
return internalContains(key, false);
228+
}
229+
230+
@Override
231+
public boolean containsFlatteningArrays(final CharSequence key) {
232+
requireNonNull(key, "The key or pointer to check the existence of a value for must not be null!");
233+
return internalContains(key, true);
234+
}
226235

236+
boolean internalContains(final CharSequence key, final boolean flatteningArrays) {
227237
final boolean result;
228238

229239
final JsonPointer pointer = JsonPointer.of(key);
230240

231241
if (1 >= pointer.getLevelCount()) {
232242
result = pointer.getRoot().map(this::containsKey).orElse(false);
233243
} else {
234-
result = pointer.getRoot()
235-
.flatMap(this::getValueForKey)
236-
.filter(JsonValue::isObject)
237-
.map(JsonValue::asObject)
238-
.map(jsonObject -> jsonObject.contains(pointer.nextLevel()))
239-
.orElse(false);
244+
result = containsPointer(pointer, flatteningArrays);
240245
}
241246

242247
return result;
243248
}
244249

250+
private Boolean containsPointer(final JsonPointer pointer, final boolean flatteningArrays) {
251+
return pointer.getRoot()
252+
.flatMap(this::getValueForKey)
253+
.filter(val -> val.isObject() || (flatteningArrays && val.isArray()))
254+
.map(val -> val.isObject() ? Stream.of(val.asObject()) :
255+
val.asArray().stream().filter(JsonValue::isObject).map(JsonValue::asObject)
256+
)
257+
.map(stream -> stream.anyMatch(jsonObject -> flatteningArrays ?
258+
jsonObject.containsFlatteningArrays(pointer.nextLevel()) :
259+
jsonObject.contains(pointer.nextLevel()))
260+
)
261+
.orElse(false);
262+
}
263+
245264
private boolean containsKey(final CharSequence key) {
246265
return fieldMap.containsKey(key.toString());
247266
}
248267

249268
@Override
250269
public Optional<JsonValue> getValue(final CharSequence key) {
251270
requireNonNull(key, "The key or pointer of the value to be retrieved must not be null!");
252-
return getValueForPointer(JsonPointer.of(key));
271+
return getValueForPointer(JsonPointer.of(key), false);
272+
}
273+
274+
@Override
275+
public Optional<JsonValue> getValueFlatteningArrays(final CharSequence key) {
276+
requireNonNull(key, "The key or pointer of the value to be retrieved must not be null!");
277+
return getValueForPointer(JsonPointer.of(key), true);
253278
}
254279

255-
private Optional<JsonValue> getValueForPointer(final JsonPointer pointer) {
280+
private Optional<JsonValue> getValueForPointer(final JsonPointer pointer, final boolean flatteningArrays) {
256281
final Optional<JsonValue> result;
257282

258283
final JsonKey rootKey = pointer.getRoot().orElse(ROOT_KEY);
@@ -263,10 +288,31 @@ private Optional<JsonValue> getValueForPointer(final JsonPointer pointer) {
263288
// same as getting a value for a key
264289
result = getValueForKey(rootKey);
265290
} else {
266-
result = getValueForKey(rootKey)
267-
.filter(JsonValue::isObject)
268-
.map(JsonValue::asObject)
269-
.flatMap(jsonObject -> jsonObject.getValue(pointer.nextLevel()));
291+
final AtomicReference<Boolean> valueIsArray = new AtomicReference<>(false);
292+
final List<JsonValue> collected = getValueForKey(rootKey).map(Stream::of).orElse(Stream.empty())
293+
.filter(val -> val.isObject() || (flatteningArrays && val.isArray()))
294+
.flatMap(val -> {
295+
if (val.isObject()) {
296+
return Stream.of(val.asObject());
297+
} else {
298+
valueIsArray.set(true);
299+
return val.asArray().stream().filter(JsonValue::isObject).map(JsonValue::asObject);
300+
}
301+
})
302+
.flatMap(jsonObject -> flatteningArrays ?
303+
jsonObject.getValueFlatteningArrays(pointer.nextLevel())
304+
.map(Stream::of).orElseGet(Stream::empty) :
305+
jsonObject.getValue(pointer.nextLevel())
306+
.map(Stream::of).orElseGet(Stream::empty)
307+
).collect(Collectors.toList());
308+
309+
if (collected.isEmpty()) {
310+
result = Optional.empty();
311+
} else if (Boolean.TRUE.equals(valueIsArray.get())) {
312+
result = Optional.of(collected.stream().collect(JsonCollectors.valuesToArray()));
313+
} else {
314+
result = Optional.of(collected.get(0));
315+
}
270316
}
271317

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

284-
return getValueForPointer(fieldDefinition.getPointer()).map(fieldDefinition::mapValue);
330+
return getValueForPointer(fieldDefinition.getPointer(), false).map(fieldDefinition::mapValue);
285331
}
286332

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

323369
result = rootKeyValue.map(jsonValue -> {
324-
if (jsonValue.isObject()) {
325-
if (containsNextLevelRootKey.test(jsonValue.asObject())) {
326-
return jsonValue.asObject().get(nextPointerLevel); // Recursion
327-
} else {
328-
return null;
329-
}
330-
} else {
331-
return jsonValue;
332-
}
333-
})
370+
if (jsonValue.isObject()) {
371+
if (containsNextLevelRootKey.test(jsonValue.asObject())) {
372+
return jsonValue.asObject().get(nextPointerLevel); // Recursion
373+
} else {
374+
return null;
375+
}
376+
} else {
377+
return jsonValue;
378+
}
379+
})
334380
.map(jsonValue -> JsonField.newInstance(rootKey, jsonValue, rootKeyDefinition.orElse(null)))
335381
.map(jsonField -> Collections.singletonMap(jsonField.getKeyName(), jsonField))
336382
.map(ImmutableJsonObject::of)
@@ -360,7 +406,7 @@ public JsonObject get(final JsonFieldSelector fieldSelector) {
360406

361407
final List<JsonPointer> pointersContainedInThis = fieldSelector.getPointers()
362408
.stream()
363-
.filter(this::contains)
409+
.filter(this::containsFlatteningArrays)
364410
.collect(Collectors.toList());
365411

366412
if (pointersContainedInThis.isEmpty()) {
@@ -381,9 +427,17 @@ private static JsonObject filterByTrie(final JsonObject self, final JsonFieldSel
381427
for (final JsonKey key : trie.getKeys()) {
382428
self.getField(key).ifPresent(child -> {
383429
final JsonValue childValue = child.getValue();
384-
final JsonValue filteredChildValue = childValue.isObject()
385-
? filterByTrie(childValue.asObject(), trie.descend(key))
386-
: childValue;
430+
final JsonValue filteredChildValue;
431+
if (childValue.isObject()) {
432+
filteredChildValue = filterByTrie(childValue.asObject(), trie.descend(key)); // recurse!
433+
} else if (childValue.isArray()) {
434+
filteredChildValue = childValue.asArray().stream()
435+
.filter(JsonValue::isObject)
436+
.map(value -> filterByTrie(value.asObject(), trie.descend(key))) // recurse!
437+
.collect(JsonCollectors.valuesToArray());
438+
} else {
439+
filteredChildValue = childValue;
440+
}
387441
final Optional<JsonFieldDefinition> childFieldDefinition = child.getDefinition();
388442
if (childFieldDefinition.isPresent()) {
389443
builder.set(childFieldDefinition.get(), filteredChildValue);
@@ -422,7 +476,8 @@ private JsonObject removeForPointer(final JsonPointer pointer) {
422476
.map(JsonValue::asObject)
423477
.filter(containsNextLevelRootKey)
424478
.map(jsonObject -> jsonObject.remove(nextPointerLevel)) // Recursion
425-
.map(withoutValue -> JsonField.newInstance(rootKey, withoutValue, getDefinitionForKey(rootKey).orElse(null)))
479+
.map(withoutValue -> JsonField.newInstance(rootKey, withoutValue,
480+
getDefinitionForKey(rootKey).orElse(null)))
426481
.map(this::set)
427482
.orElse(this);
428483
}
@@ -568,7 +623,7 @@ private SoftReferencedFieldMap(final Map<String, JsonField> jsonFieldMap,
568623
if (CBOR_FACTORY.isCborAvailable()) {
569624
try {
570625
this.cborObjectRepresentation = CBOR_FACTORY.createCborRepresentation(jsonFieldMap,
571-
guessSerializedSize());
626+
guessSerializedSize());
572627
} catch (final IOException e) {
573628
assert false; // this should not happen, so assertions will throw during testing
574629
jsonObjectStringRepresentation = createStringRepresentation(jsonFieldMap);

json/src/main/java/org/eclipse/ditto/json/ImmutableJsonObjectNull.java

+10
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ public boolean contains(final CharSequence key) {
112112
return false;
113113
}
114114

115+
@Override
116+
public boolean containsFlatteningArrays(final CharSequence key) {
117+
return false;
118+
}
119+
115120
@Override
116121
public ImmutableJsonObjectNull get(final JsonPointer pointer) {
117122
return this;
@@ -127,6 +132,11 @@ public Optional<JsonValue> getValue(final CharSequence key) {
127132
return Optional.empty();
128133
}
129134

135+
@Override
136+
public Optional<JsonValue> getValueFlatteningArrays(final CharSequence key) {
137+
return Optional.empty();
138+
}
139+
130140
@Override
131141
public <T> Optional<T> getValue(final JsonFieldDefinition<T> fieldDefinition) {
132142
return Optional.empty();

0 commit comments

Comments
 (0)