Skip to content

Commit c1aa708

Browse files
author
Christoph Büscher
committed
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 ee0d2e2 commit c1aa708

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
@@ -154,19 +154,27 @@ private static Object extractValue(String[] pathElements,
154154
Map<?, ?> map = (Map<?, ?>) currentValue;
155155
String key = pathElements[index];
156156
int nextIndex = index + 1;
157+
List<Object> extractedValues = new ArrayList<>();
157158
while (true) {
158159
if (map.containsKey(key)) {
159160
Object mapValue = map.get(key);
160161
if (mapValue == null) {
161-
return nullValue;
162-
}
163-
Object val = extractValue(pathElements, nextIndex, mapValue, nullValue);
164-
if (val != null) {
165-
return val;
162+
extractedValues.add(nullValue);
163+
} else {
164+
Object val = extractValue(pathElements, nextIndex, mapValue, nullValue);
165+
if (val != null) {
166+
extractedValues.add(val);
167+
}
166168
}
167169
}
168170
if (nextIndex == pathElements.length) {
169-
return null;
171+
if (extractedValues.size() == 0) {
172+
return null;
173+
} else if (extractedValues.size() == 1) {
174+
return extractedValues.get(0);
175+
} else {
176+
return extractedValues;
177+
}
170178
}
171179
key += "." + pathElements[nextIndex];
172180
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;
@@ -197,6 +198,17 @@ public void testExtractValueMixedObjects() throws IOException {
197198
}
198199
}
199200

201+
public void testExtractValueMixedDottedObjectNotation() throws IOException {
202+
XContentBuilder builder = XContentFactory.jsonBuilder().startObject()
203+
.startObject("foo").field("cat", "meow").endObject()
204+
.field("foo.cat", "miau")
205+
.endObject();
206+
try (XContentParser parser = createParser(JsonXContent.jsonXContent, Strings.toString(builder))) {
207+
Map<String, Object> map = parser.map();
208+
assertThat((List<?>) XContentMapValues.extractValue("foo.cat", map), containsInAnyOrder("meow", "miau"));
209+
}
210+
}
211+
200212
public void testExtractRawValue() throws Exception {
201213
XContentBuilder builder = XContentFactory.jsonBuilder().startObject()
202214
.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
@@ -98,6 +98,49 @@ public void testMixedObjectValues() throws IOException {
9898
DocumentField field = fields.get("foo.bar");
9999
assertThat(field.getValues().size(), equalTo(1));
100100
assertThat(field.getValue(), equalTo("baz"));
101+
102+
source = XContentFactory.jsonBuilder().startObject()
103+
.startObject("foo").field("cat", "meow").endObject()
104+
.field("foo.cat", "miau")
105+
.endObject();
106+
107+
doc = mapperService.documentMapper().parse(source(Strings.toString(source)));
108+
109+
fields = fetchFields(mapperService, source, "foo.cat");
110+
assertThat(fields.size(), equalTo(1));
111+
112+
field = fields.get("foo.cat");
113+
assertThat(field.getValues().size(), equalTo(2));
114+
assertThat(field.getValues(), containsInAnyOrder("meow", "miau"));
115+
116+
source = XContentFactory.jsonBuilder().startObject()
117+
.startObject("foo").field("cat", "meow").endObject()
118+
.array("foo.cat", "miau", "purr")
119+
.endObject();
120+
121+
doc = mapperService.documentMapper().parse(source(Strings.toString(source)));
122+
123+
fields = fetchFields(mapperService, source, "foo.cat");
124+
assertThat(fields.size(), equalTo(1));
125+
126+
field = fields.get("foo.cat");
127+
assertThat(field.getValues().size(), equalTo(3));
128+
assertThat(field.getValues(), containsInAnyOrder("meow", "miau", "purr"));
129+
}
130+
131+
public void testMixedDottedObjectSyntax() throws IOException {
132+
MapperService mapperService = createMapperService();
133+
XContentBuilder source = XContentFactory.jsonBuilder().startObject()
134+
.startObject("object").field("field", "value").endObject()
135+
.field("object.field", "value2")
136+
.endObject();
137+
138+
Map<String, DocumentField> fields = fetchFields(mapperService, source, "*");
139+
assertThat(fields.size(), equalTo(1));
140+
141+
DocumentField field = fields.get("object.field");
142+
assertThat(field.getValues().size(), equalTo(2));
143+
assertThat(field.getValues(), containsInAnyOrder("value", "value2"));
101144
}
102145

103146
public void testNonExistentField() throws IOException {

0 commit comments

Comments
 (0)