diff --git a/server/src/main/java/org/elasticsearch/common/xcontent/AbstractObjectParser.java b/server/src/main/java/org/elasticsearch/common/xcontent/AbstractObjectParser.java index f68cf0abe5a..0f25231634d 100644 --- a/server/src/main/java/org/elasticsearch/common/xcontent/AbstractObjectParser.java +++ b/server/src/main/java/org/elasticsearch/common/xcontent/AbstractObjectParser.java @@ -183,27 +183,35 @@ public abstract class AbstractObjectParser public void declareObjectArray(BiConsumer> consumer, ContextParser objectParser, ParseField field) { - declareField(consumer, (p, c) -> parseArray(p, () -> objectParser.parse(p, c)), field, ValueType.OBJECT_ARRAY); + declareFieldArray(consumer, (p, c) -> objectParser.parse(p, c), field, ValueType.OBJECT_ARRAY); } public void declareStringArray(BiConsumer> consumer, ParseField field) { - declareField(consumer, (p, c) -> parseArray(p, p::text), field, ValueType.STRING_ARRAY); + declareFieldArray(consumer, (p, c) -> p.text(), field, ValueType.STRING_ARRAY); } public void declareDoubleArray(BiConsumer> consumer, ParseField field) { - declareField(consumer, (p, c) -> parseArray(p, p::doubleValue), field, ValueType.DOUBLE_ARRAY); + declareFieldArray(consumer, (p, c) -> p.doubleValue(), field, ValueType.DOUBLE_ARRAY); } public void declareFloatArray(BiConsumer> consumer, ParseField field) { - declareField(consumer, (p, c) -> parseArray(p, p::floatValue), field, ValueType.FLOAT_ARRAY); + declareFieldArray(consumer, (p, c) -> p.floatValue(), field, ValueType.FLOAT_ARRAY); } public void declareLongArray(BiConsumer> consumer, ParseField field) { - declareField(consumer, (p, c) -> parseArray(p, p::longValue), field, ValueType.LONG_ARRAY); + declareFieldArray(consumer, (p, c) -> p.longValue(), field, ValueType.LONG_ARRAY); } public void declareIntArray(BiConsumer> consumer, ParseField field) { - declareField(consumer, (p, c) -> parseArray(p, p::intValue), field, ValueType.INT_ARRAY); + declareFieldArray(consumer, (p, c) -> p.intValue(), field, ValueType.INT_ARRAY); + } + + /** + * Declares a field that can contain an array of elements listed in the type ValueType enum + */ + public void declareFieldArray(BiConsumer> consumer, ContextParser itemParser, + ParseField field, ValueType type) { + declareField(consumer, (p, c) -> parseArray(p, () -> itemParser.parse(p, c)), field, type); } public void declareRawObject(BiConsumer consumer, ParseField field) { @@ -220,13 +228,18 @@ public abstract class AbstractObjectParser private interface IOSupplier { T get() throws IOException; } + private static List parseArray(XContentParser parser, IOSupplier supplier) throws IOException { List list = new ArrayList<>(); - if (parser.currentToken().isValue() || parser.currentToken() == XContentParser.Token.START_OBJECT) { + if (parser.currentToken().isValue() + || parser.currentToken() == XContentParser.Token.VALUE_NULL + || parser.currentToken() == XContentParser.Token.START_OBJECT) { list.add(supplier.get()); // single value } else { while (parser.nextToken() != XContentParser.Token.END_ARRAY) { - if (parser.currentToken().isValue() || parser.currentToken() == XContentParser.Token.START_OBJECT) { + if (parser.currentToken().isValue() + || parser.currentToken() == XContentParser.Token.VALUE_NULL + || parser.currentToken() == XContentParser.Token.START_OBJECT) { list.add(supplier.get()); } else { throw new IllegalStateException("expected value but got [" + parser.currentToken() + "]"); diff --git a/server/src/main/java/org/elasticsearch/common/xcontent/ObjectParser.java b/server/src/main/java/org/elasticsearch/common/xcontent/ObjectParser.java index 74b9a4f172c..1a3be1a5a7b 100644 --- a/server/src/main/java/org/elasticsearch/common/xcontent/ObjectParser.java +++ b/server/src/main/java/org/elasticsearch/common/xcontent/ObjectParser.java @@ -416,7 +416,8 @@ public final class ObjectParser extends AbstractObjectParser tokens; diff --git a/server/src/test/java/org/elasticsearch/common/xcontent/ObjectParserTests.java b/server/src/test/java/org/elasticsearch/common/xcontent/ObjectParserTests.java index baa2b3bcb36..7b6f14518fe 100644 --- a/server/src/test/java/org/elasticsearch/common/xcontent/ObjectParserTests.java +++ b/server/src/test/java/org/elasticsearch/common/xcontent/ObjectParserTests.java @@ -34,13 +34,14 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasSize; public class ObjectParserTests extends ESTestCase { public void testBasics() throws IOException { - XContentParser parser = createParser(JsonXContent.jsonXContent, + XContentParser parser = createParser(JsonXContent.jsonXContent, "{\n" + " \"test\" : \"foo\",\n" + " \"test_number\" : 2,\n" @@ -449,7 +450,7 @@ public class ObjectParserTests extends ESTestCase { } public void testParseNamedObject() throws IOException { - XContentParser parser = createParser(JsonXContent.jsonXContent, + XContentParser parser = createParser(JsonXContent.jsonXContent, "{\"named\": {\n" + " \"a\": {}" + "}}"); @@ -460,7 +461,7 @@ public class ObjectParserTests extends ESTestCase { } public void testParseNamedObjectInOrder() throws IOException { - XContentParser parser = createParser(JsonXContent.jsonXContent, + XContentParser parser = createParser(JsonXContent.jsonXContent, "{\"named\": [\n" + " {\"a\": {}}" + "]}"); @@ -471,7 +472,7 @@ public class ObjectParserTests extends ESTestCase { } public void testParseNamedObjectTwoFieldsInArray() throws IOException { - XContentParser parser = createParser(JsonXContent.jsonXContent, + XContentParser parser = createParser(JsonXContent.jsonXContent, "{\"named\": [\n" + " {\"a\": {}, \"b\": {}}" + "]}"); @@ -483,7 +484,7 @@ public class ObjectParserTests extends ESTestCase { } public void testParseNamedObjectNoFieldsInArray() throws IOException { - XContentParser parser = createParser(JsonXContent.jsonXContent, + XContentParser parser = createParser(JsonXContent.jsonXContent, "{\"named\": [\n" + " {}" + "]}"); @@ -495,7 +496,7 @@ public class ObjectParserTests extends ESTestCase { } public void testParseNamedObjectJunkInArray() throws IOException { - XContentParser parser = createParser(JsonXContent.jsonXContent, + XContentParser parser = createParser(JsonXContent.jsonXContent, "{\"named\": [\n" + " \"junk\"" + "]}"); @@ -594,6 +595,59 @@ public class ObjectParserTests extends ESTestCase { assertEquals(s.test, "foo"); } + public void testArraysOfGenericValues() throws IOException { + XContentParser parser = createParser(JsonXContent.jsonXContent, + "{\n" + + " \"test_array\": [ 1, null, \"3\", 4.2],\n" + + " \"int_array\": [ 1, 2, 3]\n" + + "}"); + class TestStruct { + List testArray = new ArrayList<>(); + + List ints = new ArrayList<>(); + + public void setInts(List ints) { + this.ints = ints; + } + + public void setArray(List testArray) { + this.testArray = testArray; + } + } + ObjectParser objectParser = new ObjectParser<>("foo"); + TestStruct s = new TestStruct(); + + objectParser.declareFieldArray(TestStruct::setArray, (p, c) -> XContentParserUtils.parseFieldsValue(p), + new ParseField("test_array"), ValueType.VALUE_ARRAY); + objectParser.declareIntArray(TestStruct::setInts, new ParseField("int_array")); + objectParser.parse(parser, s, null); + assertEquals(s.testArray, Arrays.asList(1, null, "3", 4.2)); + assertEquals(s.ints, Arrays.asList(1, 2, 3)); + + parser = createParser(JsonXContent.jsonXContent, "{\"test_array\": 42}"); + s = new TestStruct(); + objectParser.parse(parser, s, null); + assertEquals(s.testArray, Collections.singletonList(42)); + + parser = createParser(JsonXContent.jsonXContent, "{\"test_array\": [null]}"); + s = new TestStruct(); + objectParser.parse(parser, s, null); + assertThat(s.testArray, hasSize(1)); + assertNull(s.testArray.get(0)); + + parser = createParser(JsonXContent.jsonXContent, "{\"test_array\": null}"); + s = new TestStruct(); + objectParser.parse(parser, s, null); + assertThat(s.testArray, hasSize(1)); + assertNull(s.testArray.get(0)); + + // Make sure that we didn't break the null handling in arrays that shouldn't support nulls + XContentParser parser2 = createParser(JsonXContent.jsonXContent, "{\"int_array\": [1, null, 3]}"); + TestStruct s2 = new TestStruct(); + ParsingException ex = expectThrows(ParsingException.class, () -> objectParser.parse(parser2, s2, null)); + assertThat(ex.getMessage(), startsWith("[foo] failed to parse field [int_array]")); + } + static class NamedObjectHolder { public static final ObjectParser PARSER = new ObjectParser<>("named_object_holder", NamedObjectHolder::new);