Add generic array support to AbstractObjectParser (#28552)
Adds a generic declareFieldArray that can process arrays of arbitrary elements.
This commit is contained in:
parent
f562c7f15a
commit
da1a10fa92
|
@ -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() + "]");
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ 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;
|
||||
|
||||
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue