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, public <T> void declareObjectArray(BiConsumer<Value, List<T>> consumer, ContextParser<Context, T> objectParser,
ParseField field) { 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) { 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) { 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) { 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) { 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) { 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) { public void declareRawObject(BiConsumer<Value, BytesReference> consumer, ParseField field) {
@ -220,13 +228,18 @@ public abstract class AbstractObjectParser<Value, Context>
private interface IOSupplier<T> { private interface IOSupplier<T> {
T get() throws IOException; T get() throws IOException;
} }
private static <T> List<T> parseArray(XContentParser parser, IOSupplier<T> supplier) throws IOException { private static <T> List<T> parseArray(XContentParser parser, IOSupplier<T> supplier) throws IOException {
List<T> list = new ArrayList<>(); 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 list.add(supplier.get()); // single value
} else { } else {
while (parser.nextToken() != XContentParser.Token.END_ARRAY) { 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()); list.add(supplier.get());
} else { } else {
throw new IllegalStateException("expected value but got [" + parser.currentToken() + "]"); 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_BOOLEAN_OR_STRING(START_OBJECT, START_ARRAY, VALUE_BOOLEAN, VALUE_STRING),
OBJECT_ARRAY_OR_STRING(START_OBJECT, START_ARRAY, 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(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; private final EnumSet<XContentParser.Token> tokens;

View File

@ -34,13 +34,14 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasSize;
public class ObjectParserTests extends ESTestCase { public class ObjectParserTests extends ESTestCase {
public void testBasics() throws IOException { public void testBasics() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent, XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\n" "{\n"
+ " \"test\" : \"foo\",\n" + " \"test\" : \"foo\",\n"
+ " \"test_number\" : 2,\n" + " \"test_number\" : 2,\n"
@ -449,7 +450,7 @@ public class ObjectParserTests extends ESTestCase {
} }
public void testParseNamedObject() throws IOException { public void testParseNamedObject() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent, XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\"named\": {\n" "{\"named\": {\n"
+ " \"a\": {}" + " \"a\": {}"
+ "}}"); + "}}");
@ -460,7 +461,7 @@ public class ObjectParserTests extends ESTestCase {
} }
public void testParseNamedObjectInOrder() throws IOException { public void testParseNamedObjectInOrder() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent, XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\"named\": [\n" "{\"named\": [\n"
+ " {\"a\": {}}" + " {\"a\": {}}"
+ "]}"); + "]}");
@ -471,7 +472,7 @@ public class ObjectParserTests extends ESTestCase {
} }
public void testParseNamedObjectTwoFieldsInArray() throws IOException { public void testParseNamedObjectTwoFieldsInArray() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent, XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\"named\": [\n" "{\"named\": [\n"
+ " {\"a\": {}, \"b\": {}}" + " {\"a\": {}, \"b\": {}}"
+ "]}"); + "]}");
@ -483,7 +484,7 @@ public class ObjectParserTests extends ESTestCase {
} }
public void testParseNamedObjectNoFieldsInArray() throws IOException { public void testParseNamedObjectNoFieldsInArray() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent, XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\"named\": [\n" "{\"named\": [\n"
+ " {}" + " {}"
+ "]}"); + "]}");
@ -495,7 +496,7 @@ public class ObjectParserTests extends ESTestCase {
} }
public void testParseNamedObjectJunkInArray() throws IOException { public void testParseNamedObjectJunkInArray() throws IOException {
XContentParser parser = createParser(JsonXContent.jsonXContent, XContentParser parser = createParser(JsonXContent.jsonXContent,
"{\"named\": [\n" "{\"named\": [\n"
+ " \"junk\"" + " \"junk\""
+ "]}"); + "]}");
@ -594,6 +595,59 @@ public class ObjectParserTests extends ESTestCase {
assertEquals(s.test, "foo"); 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 { static class NamedObjectHolder {
public static final ObjectParser<NamedObjectHolder, Void> PARSER = new ObjectParser<>("named_object_holder", public static final ObjectParser<NamedObjectHolder, Void> PARSER = new ObjectParser<>("named_object_holder",
NamedObjectHolder::new); NamedObjectHolder::new);