Skip to content

Add option to specify if empty property should not be sent to Elasticsearch. #2482

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ public interface ElasticsearchPersistentProperty extends PersistentProperty<Elas
*/
boolean storeNullValue();

/**
* @return true if empty values ({{@link String}}, or {{@link java.util.Collection}} or {{@link java.util.Map}})
* should be store in Elasticsearch.
* @since 5.1
*/
boolean storeEmptyValue();

/**
* @return {@literal true} if this is a GeoPoint property
* @since 4.1
Expand All @@ -100,7 +107,7 @@ public interface ElasticsearchPersistentProperty extends PersistentProperty<Elas

/**
* @return {@literal true} if this is a property annotated with
* {@link org.springframework.data.elasticsearch.annotations.IndexedIndexName}.
* {@link org.springframework.data.elasticsearch.annotations.IndexedIndexName}.
* @since 5.1
*/
boolean isIndexedIndexNameProperty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public class SimpleElasticsearchPersistentProperty extends
private final @Nullable String annotatedFieldName;
@Nullable private PropertyValueConverter propertyValueConverter;
private final boolean storeNullValue;
private final boolean storeEmptyValue;

public SimpleElasticsearchPersistentProperty(Property property,
PersistentEntity<?, ElasticsearchPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
Expand All @@ -106,6 +107,7 @@ public SimpleElasticsearchPersistentProperty(Property property,
initPropertyValueConverter();

storeNullValue = isField && getRequiredAnnotation(Field.class).storeNullValue();
storeEmptyValue = isField ? getRequiredAnnotation(Field.class).storeEmptyValue() : true;
}

@Override
Expand Down Expand Up @@ -134,6 +136,11 @@ public boolean storeNullValue() {
return storeNullValue;
}

@Override
public boolean storeEmptyValue() {
return storeEmptyValue;
}

protected boolean hasExplicitFieldName() {
return StringUtils.hasText(getAnnotatedFieldName());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> listToWriteWhenEmpty;

@Field(type = FieldType.Nested, storeEmptyValue = false)
@Nullable private List<String> listToNotWriteWhenEmpty;

@Field(type = FieldType.Nested)
@Nullable private Map<String, String> mapToWriteWhenEmpty;

@Field(type = FieldType.Nested, storeEmptyValue = false)
@Nullable private Map<String, String> 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<String> getListToWriteWhenEmpty() {
return listToWriteWhenEmpty;
}

public void setListToWriteWhenEmpty(@Nullable List<String> listToWriteWhenEmpty) {
this.listToWriteWhenEmpty = listToWriteWhenEmpty;
}

@Nullable
public List<String> getListToNotWriteWhenEmpty() {
return listToNotWriteWhenEmpty;
}

public void setListToNotWriteWhenEmpty(@Nullable List<String> listToNotWriteWhenEmpty) {
this.listToNotWriteWhenEmpty = listToNotWriteWhenEmpty;
}

@Nullable
public Map<String, String> getMapToWriteWhenEmpty() {
return mapToWriteWhenEmpty;
}

public void setMapToWriteWhenEmpty(@Nullable Map<String, String> mapToWriteWhenEmpty) {
this.mapToWriteWhenEmpty = mapToWriteWhenEmpty;
}

@Nullable
public Map<String, String> getMapToNotWriteWhenEmpty() {
return mapToNotWriteWhenEmpty;
}

public void setMapToNotWriteWhenEmpty(@Nullable Map<String, String> mapToNotWriteWhenEmpty) {
this.mapToNotWriteWhenEmpty = mapToNotWriteWhenEmpty;
}
}
// endregion

private static String reverse(Object o) {
Expand Down