Nested fields

In some cases it can be useful to store data in the Elasticsearch index as nested objects so that they can be queried independently of each other.

The following example defines a field article with a number of “nested” attributes:

@Bean
List<FieldConfiguration> fieldConfigurations() {
    return Arrays.asList(
            FieldConfiguration.ID_FIELD,
            FieldConfiguration.FULLTEXT_FIELD,
            StandardFieldConfiguration.builder("article", ElasticsearchType.NESTED)
                    .innerFields(
                            StandardFieldConfiguration.builder("title", ElasticsearchType.TEXT).copyToFulltext(true).sortable(true).build(),
                            StandardFieldConfiguration.builder("caption", ElasticsearchType.TEXT).copyToFulltext(true).build(),
                            StandardFieldConfiguration.builder("author", ElasticsearchType.TEXT).copyToFulltext(true).build(),
                            StandardFieldConfiguration.builder("page", ElasticsearchType.INTEGER).sortable(true).build(),
                            StandardFieldConfiguration.builder("date", ElasticsearchType.DATE).sortable(true).build()
                    ).build()
    );
}

Above FieldConfiguration results in the following Elasticsearch mapping:

{
  "picturesafe-search-sample-20200331-182229-140": {
    "mappings": {
      "properties": {
        "article": {
          "type": "nested",
          "properties": {
            "author": {
              "type": "text",
              "copy_to": [
                "fulltext"
              ]
            },
            "caption": {
              "type": "text",
              "copy_to": [
                "fulltext"
              ]
            },
            "date": {
              "type": "date"
            },
            "page": {
              "type": "integer"
            },
            "title": {
              "type": "text",
              "fields": {
                "keyword": {
                  "type": "keyword"
                }
              },
              "copy_to": [
                "fulltext"
              ]
            }
          }
        },
        "fulltext": {
          "type": "text"
        },
        "id": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            }
          }
        }
      }
    }
  }
}

Nested documents can be added to a document using the DocumentBuilder:

singleIndexElasticsearchService
    .addToIndex(DataChangeProcessingMode.BLOCKING, Arrays.asList(
    DocumentBuilder.id(1)
            .put("article", Collections.singletonList(DocumentBuilder.withoutId()
                    .put("title", "This is a test title")
                    .put("caption", "This is a test caption")
                    .put("author", "John Doe")
                    .put("page", 1)
                    .put("date", getDate("10.03.2020")).build()
            )).build(),
    DocumentBuilder.id(2)
            .put("article", Collections.singletonList(DocumentBuilder.withoutId()
                    .put("title", "This is another test title")
                    .put("caption", "This is another test caption")
                    .put("author", "Jane Doe")
                    .put("page", 2)
                    .put("date", getDate("12.03.2020")).build()
            )).build(),
    DocumentBuilder.id(3)
            .put("article", Collections.singletonList(DocumentBuilder.withoutId()
                    .put("title", "This is one more test title")
                    .put("caption", "This is one more test caption")
                    .put("author", "Jane Doe")
                    .put("page", 3)
                    .put("date", getDate("12.03.2020")).build()
            )).build()
));

Above example data results in following Elasticsearch index documents:

{
  "docs": [
    {
      "_index": "picturesafe-search-sample-20200331-182229-140",
      "_type": "_doc",
      "_id": "1",
      "_version": 1,
      "_seq_no": 0,
      "_primary_term": 1,
      "found": true,
      "_source": {
        "id": "1",
        "article": [
          {
            "date": "2020-03-09T23:00:00.000Z",
            "author": "John Doe",
            "caption": "This is a test caption",
            "page": 1,
            "title": "This is a test title"
          }
        ]
      }
    },
    {
      "_index": "picturesafe-search-sample-20200331-182229-140",
      "_type": "_doc",
      "_id": "2",
      "_version": 1,
      "_seq_no": 1,
      "_primary_term": 1,
      "found": true,
      "_source": {
        "id": "2",
        "article": [
          {
            "date": "2020-03-11T23:00:00.000Z",
            "author": "Jane Doe",
            "caption": "This is another test caption",
            "page": 2,
            "title": "This is another test title"
          }
        ]
      }
    },
    {
      "_index": "picturesafe-search-sample-20200331-182229-140",
      "_type": "_doc",
      "_id": "3",
      "_version": 1,
      "_seq_no": 2,
      "_primary_term": 1,
      "found": true,
      "_source": {
        "id": "3",
        "article": [
          {
            "date": "2020-03-11T23:00:00.000Z",
            "author": "Jane Doe",
            "caption": "This is one more test caption",
            "page": 3,
            "title": "This is one more test title"
          }
        ]
      }
    }
  ]
}

Nested fields can be searched via Expression in the same way as normal fields. The nested attribute must be added to the corresponding field name, separated with a “.”:

Expression expression = new ValueExpression("article.author", "Jane Doe");
SearchResult searchResult = singleIndexElasticsearchService
    .search(expression, SearchParameter.DEFAULT);

A nested search sample can be found here.