/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.common.xcontent;
import org.elasticsearch.common.CheckedFunction;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.xcontent.ObjectParserTests.NamedObject;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.ESTestCase;
import org.hamcrest.Matcher;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import static java.util.Collections.unmodifiableList;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.nullValue;
public class ConstructingObjectParserTests extends ESTestCase {
public void testNullDeclares() {
ConstructingObjectParser<Void, Void> objectParser = new ConstructingObjectParser<>("foo", a -> null);
Exception e = expectThrows(IllegalArgumentException.class,
() -> objectParser.declareField(null, (r, c) -> null, new ParseField("test"), ObjectParser.ValueType.STRING));
assertEquals("[consumer] is required", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> objectParser.declareField(
(o, v) -> {}, (ContextParser<Void, Object>) null,
new ParseField("test"), ObjectParser.ValueType.STRING));
assertEquals("[parser] is required", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> objectParser.declareField(
(o, v) -> {}, (CheckedFunction<XContentParser, Object, IOException>) null,
new ParseField("test"), ObjectParser.ValueType.STRING));
assertEquals("[parser] is required", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> objectParser.declareField(
(o, v) -> {}, (r, c) -> null, null, ObjectParser.ValueType.STRING));
assertEquals("[parseField] is required", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> objectParser.declareField(
(o, v) -> {}, (r, c) -> null, new ParseField("test"), null));
assertEquals("[type] is required", e.getMessage());
}
/**
* Builds the object in random order and parses it.
*/
public void testRandomOrder() throws Exception {
HasCtorArguments expected = new HasCtorArguments(randomAlphaOfLength(5), randomInt());
expected.setMineral(randomInt());
expected.setFruit(randomInt());
expected.setA(randomBoolean() ? null : randomAlphaOfLength(5));
expected.setB(randomBoolean() ? null : randomAlphaOfLength(5));
expected.setC(randomBoolean() ? null : randomAlphaOfLength(5));
expected.setD(randomBoolean());
XContentBuilder builder = XContentFactory.jsonBuilder().prettyPrint();
expected.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder = shuffleXContent(builder);
BytesReference bytes = builder.bytes();
try (XContentParser parser = createParser(JsonXContent.jsonXContent, bytes)) {
HasCtorArguments parsed = randomFrom(HasCtorArguments.ALL_PARSERS).apply(parser, null);
assertEquals(expected.animal, parsed.animal);
assertEquals(expected.vegetable, parsed.vegetable);
assertEquals(expected.mineral, parsed.mineral);
assertEquals(expected.fruit, parsed.fruit);
assertEquals(expected.a, parsed.a);
assertEquals(expected.b, parsed.b);
assertEquals(expected.c, parsed.c);
assertEquals(expected.d, parsed.d);
} catch (Exception e) {
// It is convenient to decorate the error message with the json
throw new Exception("Error parsing: [" + builder.string() + "]", e);
}
}
public void testMissingAllConstructorArgs() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\n"
+ " \"mineral\": 1\n"
+ "}");
ConstructingObjectParser<HasCtorArguments, Void> objectParser = randomBoolean() ? HasCtorArguments.PARSER
: HasCtorArguments.PARSER_VEGETABLE_OPTIONAL;
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> objectParser.apply(parser, null));
if (objectParser == HasCtorArguments.PARSER) {
assertEquals("Required [animal, vegetable]", e.getMessage());
} else {
assertEquals("Required [animal]", e.getMessage());
}
}
public void testMissingAllConstructorArgsButNotRequired() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\n"
+ " \"mineral\": 1\n"
+ "}");
HasCtorArguments parsed = HasCtorArguments.PARSER_ALL_OPTIONAL.apply(parser, null);
assertEquals(1, parsed.mineral);
}
public void testMissingSecondConstructorArg() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\n"
+ " \"mineral\": 1,\n"
+ " \"animal\": \"cat\"\n"
+ "}");
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> HasCtorArguments.PARSER.apply(parser, null));
assertEquals("Required [vegetable]", e.getMessage());
}
public void testMissingSecondConstructorArgButNotRequired() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\n"
+ " \"mineral\": 1,\n"
+ " \"animal\": \"cat\"\n"
+ "}");
@SuppressWarnings("unchecked")
HasCtorArguments parsed = randomFrom(HasCtorArguments.PARSER_VEGETABLE_OPTIONAL, HasCtorArguments.PARSER_ALL_OPTIONAL).apply(parser,
null);
assertEquals(1, parsed.mineral);
assertEquals("cat", parsed.animal);
}
public void testMissingFirstConstructorArg() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\n"
+ " \"mineral\": 1,\n"
+ " \"vegetable\": 2\n"
+ "}");
@SuppressWarnings("unchecked")
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> randomFrom(HasCtorArguments.PARSER, HasCtorArguments.PARSER_VEGETABLE_OPTIONAL).apply(parser, null));
assertEquals("Required [animal]", e.getMessage());
}
public void testMissingFirstConstructorArgButNotRequired() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\n"
+ " \"mineral\": 1,\n"
+ " \"vegetable\": 2\n"
+ "}");
HasCtorArguments parsed = HasCtorArguments.PARSER_ALL_OPTIONAL.apply(parser, null);
assertEquals(1, parsed.mineral);
assertEquals((Integer) 2, parsed.vegetable);
}
public void testRepeatedConstructorParam() throws IOException {
assumeFalse("Test only makes sense if XContent parser doesn't have strict duplicate checks enabled",
XContent.isStrictDuplicateDetectionEnabled());
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\n"
+ " \"vegetable\": 1,\n"
+ " \"vegetable\": 2\n"
+ "}");
Throwable e = expectThrows(ParsingException.class, () -> randomFrom(HasCtorArguments.ALL_PARSERS).apply(parser, null));
assertEquals("[has_required_arguments] failed to parse field [vegetable]", e.getMessage());
e = e.getCause();
assertThat(e, instanceOf(IllegalArgumentException.class));
assertEquals("Can't repeat param [vegetable]", e.getMessage());
}
public void testBadParam() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\n"
+ " \"animal\": \"cat\",\n"
+ " \"vegetable\": 2,\n"
+ " \"a\": \"supercalifragilisticexpialidocious\"\n"
+ "}");
ParsingException e = expectThrows(ParsingException.class, () -> randomFrom(HasCtorArguments.ALL_PARSERS).apply(parser, null));
assertEquals("[has_required_arguments] failed to parse field [a]", e.getMessage());
assertEquals(4, e.getLineNumber());
assertEquals("[a] must be less than 10 characters in length but was [supercalifragilisticexpialidocious]",
e.getCause().getMessage());
}
public void testBadParamBeforeObjectBuilt() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\n"
+ " \"a\": \"supercalifragilisticexpialidocious\",\n"
+ " \"animal\": \"cat\"\n,"
+ " \"vegetable\": 2\n"
+ "}");
ParsingException e = expectThrows(ParsingException.class, () -> randomFrom(HasCtorArguments.ALL_PARSERS).apply(parser, null));
assertEquals("[has_required_arguments] failed to parse field [vegetable]", e.getMessage());
assertEquals(4, e.getLineNumber());
e = (ParsingException) e.getCause();
assertEquals("failed to build [has_required_arguments] after last required field arrived", e.getMessage());
assertEquals(2, e.getLineNumber());
e = (ParsingException) e.getCause();
assertEquals("[has_required_arguments] failed to parse field [a]", e.getMessage());
assertEquals(2, e.getLineNumber());
assertEquals("[a] must be less than 10 characters in length but was [supercalifragilisticexpialidocious]",
e.getCause().getMessage());
}
public void testConstructorArgsMustBeConfigured() throws IOException {
class NoConstructorArgs {
}
ConstructingObjectParser<NoConstructorArgs, Void> parser = new ConstructingObjectParser<>(
"constructor_args_required", (a) -> new NoConstructorArgs());
try {
parser.apply(createParser(JsonXContent.jsonXContent, "{}"), null);
fail("Expected AssertionError");
} catch (AssertionError e) {
assertEquals("[constructor_args_required] must configure at least on constructor argument. If it doesn't have any it should "
+ "use ObjectParser instead of ConstructingObjectParser. This is a bug in the parser declaration.", e.getMessage());
}
}
/**
* Tests the non-constructor fields are only set on time.
*/
public void testCalledOneTime() throws IOException {
boolean ctorArgOptional = randomBoolean();
class CalledOneTime {
CalledOneTime(String yeah) {
Matcher<String> yeahMatcher = equalTo("!");
if (ctorArgOptional) {
// either(yeahMatcher).or(nullValue) is broken by https://github.com/hamcrest/JavaHamcrest/issues/49
yeahMatcher = anyOf(yeahMatcher, nullValue());
}
assertThat(yeah, yeahMatcher);
}
boolean fooSet = false;
void setFoo(String foo) {
assertFalse(fooSet);
fooSet = true;
}
}
ConstructingObjectParser<CalledOneTime, Void> parser = new ConstructingObjectParser<>("one_time_test",
(a) -> new CalledOneTime((String) a[0]));
parser.declareString(CalledOneTime::setFoo, new ParseField("foo"));
parser.declareString(ctorArgOptional ? optionalConstructorArg() : constructorArg(), new ParseField("yeah"));
// ctor arg first so we can test for the bug we found one time
XContentParser xcontent = createParser(JsonXContent.jsonXContent,
"{\n"
+ " \"yeah\": \"!\",\n"
+ " \"foo\": \"foo\"\n"
+ "}");
CalledOneTime result = parser.apply(xcontent, null);
assertTrue(result.fooSet);
// and ctor arg second just in case
xcontent = createParser(JsonXContent.jsonXContent,
"{\n"
+ " \"foo\": \"foo\",\n"
+ " \"yeah\": \"!\"\n"
+ "}");
result = parser.apply(xcontent, null);
assertTrue(result.fooSet);
if (ctorArgOptional) {
// and without the constructor arg if we've made it optional
xcontent = createParser(JsonXContent.jsonXContent,
"{\n"
+ " \"foo\": \"foo\"\n"
+ "}");
result = parser.apply(xcontent, null);
}
assertTrue(result.fooSet);
}
public void testIgnoreUnknownFields() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\n"
+ " \"test\" : \"foo\",\n"
+ " \"junk\" : 2\n"
+ "}");
class TestStruct {
public final String test;
TestStruct(String test) {
this.test = test;
}
}
ConstructingObjectParser<TestStruct, Void> objectParser = new ConstructingObjectParser<>("foo", true, a ->
new TestStruct((String) a[0]));
objectParser.declareString(constructorArg(), new ParseField("test"));
TestStruct s = objectParser.apply(parser, null);
assertEquals(s.test, "foo");
}
public void testConstructObjectUsingContext() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\n"
+ " \"animal\": \"dropbear\",\n"
+ " \"mineral\": -8\n"
+ "}");
HasCtorArguments parsed = HasCtorArguments.PARSER_INT_CONTEXT.apply(parser, 42);
assertEquals(Integer.valueOf(42), parsed.vegetable);
assertEquals("dropbear", parsed.animal);
assertEquals(-8, parsed.mineral);
}
private static class HasCtorArguments implements ToXContent {
@Nullable
final String animal;
@Nullable
final Integer vegetable;
int mineral;
int fruit;
String a;
String b;
String c;
boolean d;
HasCtorArguments(@Nullable String animal, @Nullable Integer vegetable) {
this.animal = animal;
this.vegetable = vegetable;
}
public void setMineral(int mineral) {
this.mineral = mineral;
}
public void setFruit(int fruit) {
this.fruit = fruit;
}
public void setA(String a) {
if (a != null && a.length() > 9) {
throw new IllegalArgumentException("[a] must be less than 10 characters in length but was [" + a + "]");
}
this.a = a;
}
public void setB(String b) {
this.b = b;
}
public void setC(String c) {
this.c = c;
}
public void setD(boolean d) {
this.d = d;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field("animal", animal);
builder.field("vegetable", vegetable);
if (mineral != 0) { // We're just using 0 as the default because it is easy for testing
builder.field("mineral", mineral);
}
if (fruit != 0) {
builder.field("fruit", fruit);
}
if (a != null) {
builder.field("a", a);
}
if (b != null) {
builder.field("b", b);
}
if (c != null) {
builder.field("c", c);
}
if (d) {
builder.field("d", d);
}
builder.endObject();
return builder;
}
/*
* It is normal just to declare a single PARSER but we use a couple of different parsers for testing so we have all of these. Don't
* this this style is normal just because it is in the test.
*/
public static final ConstructingObjectParser<HasCtorArguments, Void> PARSER = buildParser(true, true);
public static final ConstructingObjectParser<HasCtorArguments, Void> PARSER_VEGETABLE_OPTIONAL = buildParser(true, false);
public static final ConstructingObjectParser<HasCtorArguments, Void> PARSER_ALL_OPTIONAL = buildParser(false, false);
public static final List<ConstructingObjectParser<HasCtorArguments, Void>> ALL_PARSERS = unmodifiableList(
Arrays.asList(PARSER, PARSER_VEGETABLE_OPTIONAL, PARSER_ALL_OPTIONAL));
public static final ConstructingObjectParser<HasCtorArguments, Integer> PARSER_INT_CONTEXT = buildContextParser();
private static ConstructingObjectParser<HasCtorArguments, Void> buildParser(boolean animalRequired,
boolean vegetableRequired) {
ConstructingObjectParser<HasCtorArguments, Void> parser = new ConstructingObjectParser<>(
"has_required_arguments", a -> new HasCtorArguments((String) a[0], (Integer) a[1]));
parser.declareString(animalRequired ? constructorArg() : optionalConstructorArg(), new ParseField("animal"));
parser.declareInt(vegetableRequired ? constructorArg() : optionalConstructorArg(), new ParseField("vegetable"));
declareSetters(parser);
return parser;
}
private static ConstructingObjectParser<HasCtorArguments, Integer> buildContextParser() {
ConstructingObjectParser<HasCtorArguments, Integer> parser = new ConstructingObjectParser<>(
"has_required_arguments", false, (args, ctx) -> new HasCtorArguments((String) args[0], ctx));
parser.declareString(constructorArg(), new ParseField("animal"));
declareSetters(parser);
return parser;
}
private static void declareSetters(ConstructingObjectParser<HasCtorArguments, ?> parser) {
parser.declareInt(HasCtorArguments::setMineral, new ParseField("mineral"));
parser.declareInt(HasCtorArguments::setFruit, new ParseField("fruit"));
parser.declareString(HasCtorArguments::setA, new ParseField("a"));
parser.declareString(HasCtorArguments::setB, new ParseField("b"));
parser.declareString(HasCtorArguments::setC, new ParseField("c"));
parser.declareBoolean(HasCtorArguments::setD, new ParseField("d"));
}
}
public void testParseNamedObject() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\"named\": {\n"
+ " \"a\": {}"
+ "},\"named_in_constructor\": {\n"
+ " \"b\": {}"
+ "}}");
NamedObjectHolder h = NamedObjectHolder.PARSER.apply(parser, null);
assertThat(h.named, hasSize(1));
assertEquals("a", h.named.get(0).name);
assertThat(h.namedInConstructor, hasSize(1));
assertEquals("b", h.namedInConstructor.get(0).name);
assertFalse(h.namedSuppliedInOrder);
}
public void testParseNamedObjectInOrder() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\"named\": [\n"
+ " {\"a\": {}}"
+ "],\"named_in_constructor\": [\n"
+ " {\"b\": {}}"
+ "]}");
NamedObjectHolder h = NamedObjectHolder.PARSER.apply(parser, null);
assertThat(h.named, hasSize(1));
assertEquals("a", h.named.get(0).name);
assertThat(h.namedInConstructor, hasSize(1));
assertEquals("b", h.namedInConstructor.get(0).name);
assertTrue(h.namedSuppliedInOrder);
}
public void testParseNamedObjectTwoFieldsInArray() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\"named\": [\n"
+ " {\"a\": {}, \"b\": {}}"
+ "],\"named_in_constructor\": [\n"
+ " {\"c\": {}}"
+ "]}");
ParsingException e = expectThrows(ParsingException.class, () -> NamedObjectHolder.PARSER.apply(parser, null));
assertEquals("[named_object_holder] failed to parse field [named]", e.getMessage());
assertEquals(
"[named] can be a single object with any number of fields or an array where each entry is an object with a single field",
e.getCause().getMessage());
}
public void testParseNamedObjectTwoFieldsInArrayConstructorArg() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\"named\": [\n"
+ " {\"a\": {}}"
+ "],\"named_in_constructor\": [\n"
+ " {\"c\": {}, \"d\": {}}"
+ "]}");
ParsingException e = expectThrows(ParsingException.class, () -> NamedObjectHolder.PARSER.apply(parser, null));
assertEquals("[named_object_holder] failed to parse field [named_in_constructor]", e.getMessage());
assertEquals(
"[named_in_constructor] can be a single object with any number of fields or an array where each entry is an object with a "
+ "single field", e.getCause().getMessage());
}
public void testParseNamedObjectNoFieldsInArray() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\"named\": [\n"
+ " {}"
+ "],\"named_in_constructor\": [\n"
+ " {\"a\": {}}"
+ "]}");
ParsingException e = expectThrows(ParsingException.class, () -> NamedObjectHolder.PARSER.apply(parser, null));
assertEquals("[named_object_holder] failed to parse field [named]", e.getMessage());
assertEquals(
"[named] can be a single object with any number of fields or an array where each entry is an object with a single field",
e.getCause().getMessage());
}
public void testParseNamedObjectNoFieldsInArrayConstructorArg() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\"named\": [\n"
+ " {\"a\": {}}"
+ "],\"named_in_constructor\": [\n"
+ " {}"
+ "]}");
ParsingException e = expectThrows(ParsingException.class, () -> NamedObjectHolder.PARSER.apply(parser, null));
assertEquals("[named_object_holder] failed to parse field [named_in_constructor]", e.getMessage());
assertEquals(
"[named_in_constructor] can be a single object with any number of fields or an array where each entry is an object with a "
+ "single field", e.getCause().getMessage());
}
public void testParseNamedObjectJunkInArray() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\"named\": [\n"
+ " \"junk\""
+ "],\"named_in_constructor\": [\n"
+ " {\"a\": {}}"
+ "]}");
ParsingException e = expectThrows(ParsingException.class, () -> NamedObjectHolder.PARSER.apply(parser, null));
assertEquals("[named_object_holder] failed to parse field [named]", e.getMessage());
assertEquals(
"[named] can be a single object with any number of fields or an array where each entry is an object with a single field",
e.getCause().getMessage());
}
public void testParseNamedObjectJunkInArrayConstructorArg() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\"named\": [\n"
+ " {\"a\": {}}"
+ "],\"named_in_constructor\": [\n"
+ " \"junk\""
+ "]}");
ParsingException e = expectThrows(ParsingException.class, () -> NamedObjectHolder.PARSER.apply(parser, null));
assertEquals("[named_object_holder] failed to parse field [named_in_constructor]", e.getMessage());
assertEquals(
"[named_in_constructor] can be a single object with any number of fields or an array where each entry is an object with a "
+ "single field", e.getCause().getMessage());
}
public void testParseNamedObjectInOrderNotSupported() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\"named\": [\n"
+ " {\"a\": {}}"
+ "],\"named_in_constructor\": {\"b\": {}}"
+ "}");
// Create our own parser for this test so we can disable support for the "ordered" mode specified by the array above
@SuppressWarnings("unchecked")
ConstructingObjectParser<NamedObjectHolder, Void> objectParser = new ConstructingObjectParser<>("named_object_holder",
a -> new NamedObjectHolder(((List<NamedObject>) a[0])));
objectParser.declareNamedObjects(ConstructingObjectParser.constructorArg(), NamedObject.PARSER,
new ParseField("named_in_constructor"));
objectParser.declareNamedObjects(NamedObjectHolder::setNamed, NamedObject.PARSER, new ParseField("named"));
// Now firing the xml through it fails
ParsingException e = expectThrows(ParsingException.class, () -> objectParser.apply(parser, null));
assertEquals("[named_object_holder] failed to parse field [named]", e.getMessage());
assertEquals("[named] doesn't support arrays. Use a single object with multiple fields.", e.getCause().getMessage());
}
public void testParseNamedObjectInOrderNotSupportedConstructorArg() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\"named\": {\"a\": {}}"
+ ",\"named_in_constructor\": [\n"
+ " {\"b\": {}}"
+ "]}");
// Create our own parser for this test so we can disable support for the "ordered" mode specified by the array above
@SuppressWarnings("unchecked")
ConstructingObjectParser<NamedObjectHolder, Void> objectParser = new ConstructingObjectParser<>("named_object_holder",
a -> new NamedObjectHolder(((List<NamedObject>) a[0])));
objectParser.declareNamedObjects(ConstructingObjectParser.constructorArg(), NamedObject.PARSER,
new ParseField("named_in_constructor"));
objectParser.declareNamedObjects(NamedObjectHolder::setNamed, NamedObject.PARSER, new ParseField("named"));
// Now firing the xml through it fails
ParsingException e = expectThrows(ParsingException.class, () -> objectParser.apply(parser, null));
assertEquals("[named_object_holder] failed to parse field [named_in_constructor]", e.getMessage());
assertEquals("[named_in_constructor] doesn't support arrays. Use a single object with multiple fields.", e.getCause().getMessage());
}
static class NamedObjectHolder {
@SuppressWarnings("unchecked")
public static final ConstructingObjectParser<NamedObjectHolder, Void> PARSER = new ConstructingObjectParser<>("named_object_holder",
a -> new NamedObjectHolder(((List<NamedObject>) a[0])));
static {
PARSER.declareNamedObjects(ConstructingObjectParser.constructorArg(), NamedObject.PARSER, NamedObjectHolder::keepNamedInOrder,
new ParseField("named_in_constructor"));
PARSER.declareNamedObjects(NamedObjectHolder::setNamed, NamedObject.PARSER, NamedObjectHolder::keepNamedInOrder,
new ParseField("named"));
}
private List<NamedObject> named;
private List<NamedObject> namedInConstructor;
private boolean namedSuppliedInOrder = false;
NamedObjectHolder(List<NamedObject> namedInConstructor) {
this.namedInConstructor = namedInConstructor;
}
public void setNamed(List<NamedObject> named) {
this.named = named;
}
public void keepNamedInOrder() {
namedSuppliedInOrder = true;
}
}
}