diff --git a/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java index f80d8f369ab..74bb073bce0 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java @@ -37,6 +37,7 @@ import org.elasticsearch.index.mapper.FieldNamesFieldMapper.FieldNamesFieldType; import org.elasticsearch.search.lookup.SourceLookup; import java.io.IOException; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -46,6 +47,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Queue; import java.util.TreeMap; import java.util.stream.StreamSupport; @@ -307,11 +309,17 @@ public abstract class FieldMapper extends Mapper implements Cloneable { List values = new ArrayList<>(); if (parsesArrayValue()) { return (List) parseSourceValue(sourceValue, format); - } else { - List sourceValues = sourceValue instanceof List - ? (List) sourceValue - : org.elasticsearch.common.collect.List.of(sourceValue); - for (Object value : sourceValues) { + } + + // We allow source values to contain multiple levels of arrays, such as `"field": [[1, 2]]`. + // So we need to unwrap these arrays before passing them on to be parsed. + Queue queue = new ArrayDeque<>(); + queue.add(sourceValue); + while (queue.isEmpty() == false) { + Object value = queue.poll(); + if (value instanceof List) { + queue.addAll((List) value); + } else { Object parsedValue = parseSourceValue(value, format); if (parsedValue != null) { values.add(parsedValue); diff --git a/server/src/test/java/org/elasticsearch/search/fetch/subphase/FieldValueRetrieverTests.java b/server/src/test/java/org/elasticsearch/search/fetch/subphase/FieldValueRetrieverTests.java index a0c29db7c49..8cc06c3e620 100644 --- a/server/src/test/java/org/elasticsearch/search/fetch/subphase/FieldValueRetrieverTests.java +++ b/server/src/test/java/org/elasticsearch/search/fetch/subphase/FieldValueRetrieverTests.java @@ -115,6 +115,35 @@ public class FieldValueRetrieverTests extends ESSingleNodeTestCase { assertThat(fields.size(), equalTo(2)); } + public void testNestedArrays() throws IOException { + MapperService mapperService = createMapperService(); + XContentBuilder source = XContentFactory.jsonBuilder().startObject() + .startArray("field") + .startArray().value("first").value("second").endArray() + .endArray() + .endObject(); + + Map fields = retrieveFields(mapperService, source, "field"); + DocumentField field = fields.get("field"); + assertNotNull(field); + assertThat(field.getValues().size(), equalTo(2)); + assertThat(field.getValues(), hasItems("first", "second")); + + source = XContentFactory.jsonBuilder().startObject() + .startArray("object") + .startObject().array("field", "first", "second").endObject() + .startObject().array("field", "third").endObject() + .startObject().field("field", "fourth").endObject() + .endArray() + .endObject(); + + fields = retrieveFields(mapperService, source, "object.field"); + field = fields.get("object.field"); + assertNotNull(field); + assertThat(field.getValues().size(), equalTo(4)); + assertThat(field.getValues(), hasItems("first", "second", "third", "fourth")); + } + public void testArrayValueMappers() throws IOException { MapperService mapperService = createMapperService();