Add generic array support to AbstractObjectParser (#28552)

Adds a generic declareFieldArray that can process arrays of arbitrary elements.
This commit is contained in:
Igor Motov 2018-02-08 19:46:12 -05:00 committed by GitHub
parent f562c7f15a
commit da1a10fa92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 83 additions and 15 deletions

View File

@ -183,27 +183,35 @@ public abstract class AbstractObjectParser<Value, Context>
public <T> void declareObjectArray(BiConsumer<Value, List<T>> consumer, ContextParser<Context, T> 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<Value, List<String>> 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<Value, List<Double>> 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<Value, List<Float>> 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<Value, List<Long>> 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<Value, List<Integer>> 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 <T> void declareFieldArray(BiConsumer<Value, List<T>> consumer, ContextParser<Context, T> itemParser,
ParseField field, ValueType type) {
declareField(consumer, (p, c) -> parseArray(p, () -> itemParser.parse(p, c)), field, type);
}
public void declareRawObject(BiConsumer<Value, BytesReference> consumer, ParseField field) {
@ -220,13 +228,18 @@ public abstract class AbstractObjectParser<Value, Context>
private interface IOSupplier<T> {
T get() throws IOException;
}
private static <T> List<T> parseArray(XContentParser parser, IOSupplier<T> supplier) throws IOException {
List<T> 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() + "]");

View File

@ -416,7 +416,8 @@ public final class ObjectParser<Value, Context> extends AbstractObjectParser<Val
OBJECT_ARRAY_BOOLEAN_OR_STRING(START_OBJECT, START_ARRAY, VALUE_BOOLEAN, VALUE_STRING),
OBJECT_ARRAY_OR_STRING(START_OBJECT, START_ARRAY, VALUE_STRING),
VALUE(VALUE_BOOLEAN, VALUE_NULL, VALUE_EMBEDDED_OBJECT, VALUE_NUMBER, VALUE_STRING),
VALUE_OBJECT_ARRAY(VALUE_BOOLEAN, VALUE_NULL, VALUE_EMBEDDED_OBJECT, VALUE_NUMBER, VALUE_STRING, START_OBJECT, START_ARRAY);
VALUE_OBJECT_ARRAY(VALUE_BOOLEAN, VALUE_NULL, VALUE_EMBEDDED_OBJECT, VALUE_NUMBER, VALUE_STRING, START_OBJECT, START_ARRAY),
VALUE_ARRAY(VALUE_BOOLEAN, VALUE_NULL, VALUE_NUMBER, VALUE_STRING, START_ARRAY);
private final EnumSet<XContentParser.Token> tokens;

View File

@ -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<Object> testArray = new ArrayList<>();
List<Integer> ints = new ArrayList<>();
public void setInts(List<Integer> ints) {
this.ints = ints;
}
public void setArray(List<Object> testArray) {
this.testArray = testArray;
}
}
ObjectParser<TestStruct, Void> 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<NamedObjectHolder, Void> PARSER = new ObjectParser<>("named_object_holder",
NamedObjectHolder::new);