Search code examples
springelasticsearchspring-data-elasticsearch

Spring data elasticsearch embedded field mapping


I'm struggling with mappings some field. I looked for an answer but couldn't find anything solving my case. Let's cut to the chase.

I have my document class

@Doucment
public class DocumentClass {
  @Field(type = FieldType.Nested)
  private EmployeeId employeeId;
}

An EmployeeId is wrapper for my uuid identifier. This object has nothing but just getters and setters and jackson annotations. The thing is that object extends some base class so such objects like EmployeeId can inherit this object. This super class has field id and this causes the problem. When I post some data to elasticsearch then it looks like this:

{
  "employeeId": {
    "id": "someUUID"
  }
}

But I want to map this to be like:

{
  "employeeId": "someUUID"
}

I wonder if there is a way to flatten this object.


Solution

  • If I get it right, you want to convert your EmployeeId class to a String and back. You have 2 possibilities to do that:

    Using a property converter

    If you only want to convert an EmployeeId in this entity and might keep it as it is in another, you should use a property converter that is only registered for this property:

    import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
    
    public class EmployeeIdConverter implements PropertyValueConverter {
        @Override
        public Object write(Object value) {
            return value instanceof EmployeeId employeeId ? employeeId.getId() : value.toString();
        }
    
        @Override
        public Object read(Object value) {
            return new EmployeeId(value.toString());
        }
    }
    

    This converter must be registered on the property, notice that the field type is set to Keyword as it probably should not be analysed:

    import org.springframework.data.elasticsearch.annotations.ValueConverter;
    
    @Document
    public class DocumentClass {
      @Field(type = FieldType.Keyword)
      @ValueConverter(EmployeeIdConverter.class)
      private EmployeeId employeeId;
    }
    

    Using a global converter

    If you are using this EmployeeId at several places you might want register globally 2 converters for the two conversion directions:

    @WritingConverter
    public class EmployeeIdToString implements Converter<EmployeeId, String>{
    
        @Nullable
        @Override
        public String convert(EmployeeId employeeId) {
            return employeeId.getId();
        }
    }
    
    @ReadingConverter
    public class StringToEmployeeId implements Converter<String, EmployeeId>{
    
        @Nullable
        @Override
        public EmployeeId convert(String id) {
            return new EmployeeId(id);
        }
    }
    

    To register these, you need to provide a custom client configuration (see the documentation):

    @Configuration
    public class MyClientConfig extends ElasticsearchConfiguration {
    
        @Override
        public ClientConfiguration clientConfiguration() {
            return ClientConfiguration.builder()           
                .connectedTo("localhost:9200")
                .build();
        }
    
        @Override
        public ElasticsearchCustomConversions elasticsearchCustomConversions() {
            Collection<Converter<?, ?>> converters = new ArrayList<>();
            converters.add(new EmployeeIdToString());
            converters.add(new StringToEmployeeId());
            return new ElasticsearchCustomConversions(converters);
        }
    
    }
    

    In this case, only the field type needs to be adjusted

    @Document
    public class DocumentClass {
      @Field(type = FieldType.Keyword)
      private EmployeeId employeeId;
    }