diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java index 84d6b240e..0459857d3 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java @@ -210,4 +210,12 @@ * @since 4.3 */ boolean excludeFromSource() default false; + + /** + * when this field is a {{@link String}}, a {{@link java.util.Collection}} or a {{@link java.util.Map}} that is empty + * this property controlls whether the empty value is sent to Elasticsearch. + * + * @since 5.1 + */ + boolean storeEmptyValue() default true; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java index e1513b454..0d7861366 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java @@ -960,6 +960,10 @@ private void writeProperties(ElasticsearchPersistentEntity entity, Persistent continue; } + if (!property.storeEmptyValue() && hasEmptyValue(value)) { + continue; + } + if (property.hasPropertyValueConverter()) { value = propertyConverterWrite(property, value); sink.set(property, value); @@ -988,6 +992,16 @@ private void writeProperties(ElasticsearchPersistentEntity entity, Persistent } } + private static boolean hasEmptyValue(Object value) { + + if (value instanceof String s && s.isEmpty() || value instanceof Collection c && c.isEmpty() + || value instanceof Map m && m.isEmpty()) { + return true; + } + + return false; + } + @SuppressWarnings("unchecked") protected void writeProperty(ElasticsearchPersistentProperty property, Object value, MapValueAccessor sink) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java index 1d65a8093..f82353892 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java @@ -74,6 +74,13 @@ public interface ElasticsearchPersistentProperty extends PersistentProperty owner, SimpleTypeHolder simpleTypeHolder) { @@ -106,6 +107,7 @@ public SimpleElasticsearchPersistentProperty(Property property, initPropertyValueConverter(); storeNullValue = isField && getRequiredAnnotation(Field.class).storeNullValue(); + storeEmptyValue = isField ? getRequiredAnnotation(Field.class).storeEmptyValue() : true; } @Override @@ -134,6 +136,11 @@ public boolean storeNullValue() { return storeNullValue; } + @Override + public boolean storeEmptyValue() { + return storeEmptyValue; + } + protected boolean hasExplicitFieldName() { return StringUtils.hasText(getAnnotatedFieldName()); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java index 43599f0db..636243291 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java @@ -1893,6 +1893,35 @@ void shouldNotWriteIdPropertyToDocumentSourceIfConfiguredSo() throws JSONExcepti assertEquals(expected, json, true); } + @Test // #2290 + @DisplayName("should respect field setting for empty properties") + void shouldRespectFieldSettingForEmptyProperties() throws JSONException { + @Language("JSON") + var expected = """ + { + "_class": "org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverterUnitTests$EntityWithPropertiesThatMightBeEmpty", + "id": "42", + "stringToWriteWhenEmpty": "", + "listToWriteWhenEmpty": [], + "mapToWriteWhenEmpty": {} + } + """; + var entity = new EntityWithPropertiesThatMightBeEmpty(); + entity.setId("42"); + entity.setStringToWriteWhenEmpty(""); + entity.setStringToNotWriteWhenEmpty(""); + entity.setListToWriteWhenEmpty(emptyList()); + entity.setListToNotWriteWhenEmpty(emptyList()); + entity.setMapToWriteWhenEmpty(emptyMap()); + entity.setMapToNotWriteWhenEmpty(emptyMap()); + + Document document = Document.create(); + mappingElasticsearchConverter.write(entity, document); + String json = document.toJson(); + + assertEquals(expected, json, true); + } + // region entities public static class Sample { @Nullable public @ReadOnlyProperty String readOnly; @@ -2974,6 +3003,91 @@ public void setText(@Nullable String text) { this.text = text; } } + + static class EntityWithPropertiesThatMightBeEmpty { + @Nullable private String id; + + @Field(type = FieldType.Text) + @Nullable private String stringToWriteWhenEmpty; + + @Field(type = FieldType.Text, storeEmptyValue = false) + @Nullable private String stringToNotWriteWhenEmpty; + + @Field(type = FieldType.Nested) + @Nullable private List listToWriteWhenEmpty; + + @Field(type = FieldType.Nested, storeEmptyValue = false) + @Nullable private List listToNotWriteWhenEmpty; + + @Field(type = FieldType.Nested) + @Nullable private Map mapToWriteWhenEmpty; + + @Field(type = FieldType.Nested, storeEmptyValue = false) + @Nullable private Map mapToNotWriteWhenEmpty; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getStringToWriteWhenEmpty() { + return stringToWriteWhenEmpty; + } + + public void setStringToWriteWhenEmpty(@Nullable String stringToWriteWhenEmpty) { + this.stringToWriteWhenEmpty = stringToWriteWhenEmpty; + } + + @Nullable + public String getStringToNotWriteWhenEmpty() { + return stringToNotWriteWhenEmpty; + } + + public void setStringToNotWriteWhenEmpty(@Nullable String stringToNotWriteWhenEmpty) { + this.stringToNotWriteWhenEmpty = stringToNotWriteWhenEmpty; + } + + @Nullable + public List getListToWriteWhenEmpty() { + return listToWriteWhenEmpty; + } + + public void setListToWriteWhenEmpty(@Nullable List listToWriteWhenEmpty) { + this.listToWriteWhenEmpty = listToWriteWhenEmpty; + } + + @Nullable + public List getListToNotWriteWhenEmpty() { + return listToNotWriteWhenEmpty; + } + + public void setListToNotWriteWhenEmpty(@Nullable List listToNotWriteWhenEmpty) { + this.listToNotWriteWhenEmpty = listToNotWriteWhenEmpty; + } + + @Nullable + public Map getMapToWriteWhenEmpty() { + return mapToWriteWhenEmpty; + } + + public void setMapToWriteWhenEmpty(@Nullable Map mapToWriteWhenEmpty) { + this.mapToWriteWhenEmpty = mapToWriteWhenEmpty; + } + + @Nullable + public Map getMapToNotWriteWhenEmpty() { + return mapToNotWriteWhenEmpty; + } + + public void setMapToNotWriteWhenEmpty(@Nullable Map mapToNotWriteWhenEmpty) { + this.mapToNotWriteWhenEmpty = mapToNotWriteWhenEmpty; + } + } // endregion private static String reverse(Object o) {