Skip to content

Commit c4a1dfc

Browse files
author
Christoph Büscher
authored
Collect multiple paths from XContentMapValues (#68540)
Currently, when a document source mixed json object and dotted syntax like e.g. { "foo" : { "bar" : 0 }, "foo.bar" : 1}, extracting the values from the source map via XContentMapValues#extractValue returns after the first value for a path has been found. Instead we should exhaust all possibilities and return a list of objects of we find more than one value when extending the lookup path. Closes #68215
1 parent 5529b3d commit c4a1dfc

File tree

3 files changed

+69
-6
lines changed

3 files changed

+69
-6
lines changed

server/src/main/java/org/elasticsearch/common/xcontent/support/XContentMapValues.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -180,19 +180,27 @@ private static Object extractValue(String[] pathElements,
180180
Map<?, ?> map = (Map<?, ?>) currentValue;
181181
String key = pathElements[index];
182182
int nextIndex = index + 1;
183+
List<Object> extractedValues = new ArrayList<>();
183184
while (true) {
184185
if (map.containsKey(key)) {
185186
Object mapValue = map.get(key);
186187
if (mapValue == null) {
187-
return nullValue;
188-
}
189-
Object val = extractValue(pathElements, nextIndex, mapValue, nullValue);
190-
if (val != null) {
191-
return val;
188+
extractedValues.add(nullValue);
189+
} else {
190+
Object val = extractValue(pathElements, nextIndex, mapValue, nullValue);
191+
if (val != null) {
192+
extractedValues.add(val);
193+
}
192194
}
193195
}
194196
if (nextIndex == pathElements.length) {
195-
return null;
197+
if (extractedValues.size() == 0) {
198+
return null;
199+
} else if (extractedValues.size() == 1) {
200+
return extractedValues.get(0);
201+
} else {
202+
return extractedValues;
203+
}
196204
}
197205
key += "." + pathElements[nextIndex];
198206
nextIndex++;

server/src/test/java/org/elasticsearch/common/xcontent/support/XContentMapValuesTests.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import static org.elasticsearch.common.xcontent.XContentHelper.convertToMap;
3434
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
3535
import static org.hamcrest.Matchers.contains;
36+
import static org.hamcrest.Matchers.containsInAnyOrder;
3637
import static org.hamcrest.Matchers.hasEntry;
3738
import static org.hamcrest.Matchers.hasKey;
3839
import static org.hamcrest.Matchers.hasSize;
@@ -195,6 +196,17 @@ public void testExtractValueMixedObjects() throws IOException {
195196
}
196197
}
197198

199+
public void testExtractValueMixedDottedObjectNotation() throws IOException {
200+
XContentBuilder builder = XContentFactory.jsonBuilder().startObject()
201+
.startObject("foo").field("cat", "meow").endObject()
202+
.field("foo.cat", "miau")
203+
.endObject();
204+
try (XContentParser parser = createParser(JsonXContent.jsonXContent, Strings.toString(builder))) {
205+
Map<String, Object> map = parser.map();
206+
assertThat((List<?>) XContentMapValues.extractValue("foo.cat", map), containsInAnyOrder("meow", "miau"));
207+
}
208+
}
209+
198210
public void testExtractRawValue() throws Exception {
199211
XContentBuilder builder = XContentFactory.jsonBuilder().startObject()
200212
.field("test", "value")

server/src/test/java/org/elasticsearch/search/fetch/subphase/FieldFetcherTests.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,49 @@ public void testMixedObjectValues() throws IOException {
9999
DocumentField field = fields.get("foo.bar");
100100
assertThat(field.getValues().size(), equalTo(1));
101101
assertThat(field.getValue(), equalTo("baz"));
102+
103+
source = XContentFactory.jsonBuilder().startObject()
104+
.startObject("foo").field("cat", "meow").endObject()
105+
.field("foo.cat", "miau")
106+
.endObject();
107+
108+
doc = mapperService.documentMapper().parse(source(Strings.toString(source)));
109+
110+
fields = fetchFields(mapperService, source, "foo.cat");
111+
assertThat(fields.size(), equalTo(1));
112+
113+
field = fields.get("foo.cat");
114+
assertThat(field.getValues().size(), equalTo(2));
115+
assertThat(field.getValues(), containsInAnyOrder("meow", "miau"));
116+
117+
source = XContentFactory.jsonBuilder().startObject()
118+
.startObject("foo").field("cat", "meow").endObject()
119+
.array("foo.cat", "miau", "purr")
120+
.endObject();
121+
122+
doc = mapperService.documentMapper().parse(source(Strings.toString(source)));
123+
124+
fields = fetchFields(mapperService, source, "foo.cat");
125+
assertThat(fields.size(), equalTo(1));
126+
127+
field = fields.get("foo.cat");
128+
assertThat(field.getValues().size(), equalTo(3));
129+
assertThat(field.getValues(), containsInAnyOrder("meow", "miau", "purr"));
130+
}
131+
132+
public void testMixedDottedObjectSyntax() throws IOException {
133+
MapperService mapperService = createMapperService();
134+
XContentBuilder source = XContentFactory.jsonBuilder().startObject()
135+
.startObject("object").field("field", "value").endObject()
136+
.field("object.field", "value2")
137+
.endObject();
138+
139+
Map<String, DocumentField> fields = fetchFields(mapperService, source, "*");
140+
assertThat(fields.size(), equalTo(1));
141+
142+
DocumentField field = fields.get("object.field");
143+
assertThat(field.getValues().size(), equalTo(2));
144+
assertThat(field.getValues(), containsInAnyOrder("value", "value2"));
102145
}
103146

104147
public void testNonExistentField() throws IOException {

0 commit comments

Comments
 (0)