diff --git a/metafix/src/main/java/org/metafacture/metafix/Value.java b/metafix/src/main/java/org/metafacture/metafix/Value.java index c7d5d3a7..d4f0dc79 100644 --- a/metafix/src/main/java/org/metafacture/metafix/Value.java +++ b/metafix/src/main/java/org/metafacture/metafix/Value.java @@ -320,6 +320,20 @@ private TypeMatcher match(final Type type, final Consumer consumer, final private abstract static class AbstractValueType { + protected static final Predicate REMOVE_EMPTY_VALUES = v -> v.extractType((m, c) -> m + .ifArray(a -> { + a.removeEmptyValues(); + c.accept(a.isEmpty()); + }) + .ifHash(h -> { + h.removeEmptyValues(); + c.accept(h.isEmpty()); + }) + // TODO: Catmandu considers whitespace-only strings empty (`$v !~ /\S/`) + .ifString(s -> c.accept(s.isEmpty())) + .orElseThrow() + ); + @Override public abstract boolean equals(Object object); @@ -369,6 +383,10 @@ public void add(final Value value) { } } + private boolean isEmpty() { + return list.isEmpty(); + } + public int size() { return list.size(); } @@ -381,6 +399,10 @@ public Stream stream() { return list.stream(); } + private void removeEmptyValues() { + list.removeIf(REMOVE_EMPTY_VALUES); + } + public void forEach(final Consumer consumer) { list.forEach(consumer); } @@ -927,16 +949,10 @@ public void retainFields(final Collection fields) { } /** - * Removes all field/value pairs from this hash whose value is empty. + * Recursively removes all field/value pairs from this hash whose value is empty. */ public void removeEmptyValues() { - // TODO: - // - // - Remove empty arrays/hashes? - // - Remove empty strings(/arrays/hashes) recursively? - // - // => Compare Catmandu behaviour - map.values().removeIf(v -> v.isString() && v.asString().isEmpty()); + map.values().removeIf(REMOVE_EMPTY_VALUES); } /** diff --git a/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java b/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java index a72bd56e..c2fbeb7c 100644 --- a/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java +++ b/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java @@ -45,7 +45,7 @@ public MetafixRecordTest() { @Test public void entitiesPassThrough() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( - "vacuum()"), + "nothing()"), i -> { i.startRecord("1"); i.startEntity("deep"); @@ -67,7 +67,7 @@ public void entitiesPassThrough() { @Test public void shouldNotEmitVirtualFieldsByDefault() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( - "vacuum()" + "nothing()" ), i -> { i.startRecord("1"); @@ -134,7 +134,7 @@ public void shouldEmitVirtualFieldsWhenAdded() { @Test public void entitiesPassThroughRepeatNestedEntity() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( - "vacuum()"), + "nothing()"), i -> { i.startRecord("1"); i.startEntity("deep"); @@ -1527,9 +1527,74 @@ public void retain() { } @Test - public void vacuum() { + public void shouldDeleteEmptyArrays() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "vacuum()" + ), + i -> { + i.startRecord("1"); + i.startEntity("1[]"); + i.literal("1", "one"); + i.endEntity(); + i.startEntity("2[]"); + i.endEntity(); + i.startEntity("3[]"); + i.literal("1", "tre"); + i.endEntity(); + i.startEntity("4[]"); + i.endEntity(); + i.endRecord(); + }, + o -> { + o.get().startRecord("1"); + o.get().startEntity("1[]"); + o.get().literal("1", "one"); + o.get().endEntity(); + o.get().startEntity("3[]"); + o.get().literal("1", "tre"); + o.get().endEntity(); + o.get().endRecord(); + } + ); + } + + @Test + public void shouldDeleteEmptyHashes() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "vacuum()" + ), + i -> { + i.startRecord("1"); + i.startEntity("1"); + i.literal("1", "one"); + i.endEntity(); + i.startEntity("2"); + i.endEntity(); + i.startEntity("3"); + i.literal("1", "tre"); + i.endEntity(); + i.startEntity("4"); + i.endEntity(); + i.endRecord(); + }, + o -> { + o.get().startRecord("1"); + o.get().startEntity("1"); + o.get().literal("1", "one"); + o.get().endEntity(); + o.get().startEntity("3"); + o.get().literal("1", "tre"); + o.get().endEntity(); + o.get().endRecord(); + } + ); + } + + @Test + public void shouldDeleteEmptyStrings() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( - "vacuum()"), + "vacuum()" + ), i -> { i.startRecord("1"); i.literal("1", "one"); @@ -1537,12 +1602,161 @@ public void vacuum() { i.literal("3", "tre"); i.literal("4", ""); i.endRecord(); - }, o -> { + }, + o -> { o.get().startRecord("1"); o.get().literal("1", "one"); o.get().literal("3", "tre"); o.get().endRecord(); - }); + } + ); + } + + @Test + public void shouldDeleteEmptyNestedArrays() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "vacuum()" + ), + i -> { + i.startRecord("1"); + i.startEntity("arrays"); + i.startEntity("1[]"); + i.literal("1", "one"); + i.endEntity(); + i.startEntity("2[]"); + i.endEntity(); + i.startEntity("3[]"); + i.literal("1", "tre"); + i.endEntity(); + i.startEntity("4[]"); + i.endEntity(); + i.endEntity(); + i.endRecord(); + }, + (o, f) -> { + o.get().startRecord("1"); + o.get().startEntity("arrays"); + o.get().startEntity("1[]"); + o.get().literal("1", "one"); + o.get().endEntity(); + o.get().startEntity("3[]"); + o.get().literal("1", "tre"); + f.apply(2).endEntity(); + o.get().endRecord(); + } + ); + } + + @Test + public void shouldDeleteEmptyNestedHashes() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "vacuum()" + ), + i -> { + i.startRecord("1"); + i.startEntity("hashes"); + i.startEntity("1"); + i.literal("1", "one"); + i.endEntity(); + i.startEntity("2"); + i.endEntity(); + i.startEntity("3"); + i.literal("1", "tre"); + i.endEntity(); + i.startEntity("4"); + i.endEntity(); + i.endEntity(); + i.endRecord(); + }, + (o, f) -> { + o.get().startRecord("1"); + o.get().startEntity("hashes"); + o.get().startEntity("1"); + o.get().literal("1", "one"); + o.get().endEntity(); + o.get().startEntity("3"); + o.get().literal("1", "tre"); + f.apply(2).endEntity(); + o.get().endRecord(); + } + ); + } + + @Test + public void shouldDeleteEmptyNestedStrings() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "vacuum()" + ), + i -> { + i.startRecord("1"); + i.startEntity("nested"); + i.literal("1", "one"); + i.literal("2", ""); + i.literal("3", "tre"); + i.literal("4", ""); + i.endEntity(); + i.endRecord(); + }, + o -> { + o.get().startRecord("1"); + o.get().startEntity("nested"); + o.get().literal("1", "one"); + o.get().literal("3", "tre"); + o.get().endEntity(); + o.get().endRecord(); + } + ); + } + + @Test + public void shouldDeleteEmptyDeeplyNestedArrays() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "vacuum()" + ), + i -> { + i.startRecord("1"); + i.startEntity("arrays[]"); + i.endEntity(); + i.startEntity("hashes"); + i.startEntity("foo[]"); + i.endEntity(); + i.endEntity(); + i.literal("me", "1"); + i.endRecord(); + }, + o -> { + o.get().startRecord("1"); + o.get().literal("me", "1"); + o.get().endRecord(); + } + ); + } + + @Test + public void shouldDeleteEmptyArraysInArrays() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "vacuum()" + ), + i -> { + i.startRecord("1"); + i.startEntity("arrays[]"); + i.startEntity("1[]"); + i.endEntity(); + i.startEntity("2[]"); + i.literal("1", ":-P yuck"); + i.endEntity(); + i.endEntity(); + i.endRecord(); + }, + (o, f) -> { + o.get().startRecord("1"); + o.get().startEntity("arrays[]"); + o.get().startEntity("1"); // TODO: Preserve array!? (`1[]`) + o.get().literal("1", ":-P yuck"); + f.apply(2).endEntity(); + o.get().endRecord(); + } + ); } @Test @@ -1566,7 +1780,7 @@ public void nulls() { @Test public void repeatToArray() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( - "vacuum()"), + "nothing()"), i -> { i.startRecord("1"); i.literal("name", "max"); @@ -1644,7 +1858,7 @@ public void shouldAccessArrayByWildcard() { @Test public void repeatToArrayOfObjects() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( - "vacuum()"), + "nothing()"), i -> { i.startRecord("1"); i.startEntity("author"); @@ -1697,8 +1911,7 @@ public void accessArrayOfObjectsByIndex() { // TODO: implement implicit iteration? public void accessArrayOfObjectsByWildcard() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( - "upcase('author.*.name')", - "vacuum()"), + "upcase('author.*.name')"), i -> { i.startRecord("1"); i.startEntity("author"); @@ -1726,8 +1939,7 @@ public void accessArrayOfObjectsByDoListBind() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( "do list('path':'author','var':'a')", " upcase('a.name')", - "end", - "vacuum()"), + "end"), i -> { i.startRecord("1"); i.startEntity("author");