Add a ObjectParser helper for stream parsing

This parser prototype allows to decleratively define parsers for XContent
instead of writing messy and error prone while loops. It encapsulates all the error handling logic
and only even tries to parse if the token types match the declaration.
This commit is contained in:
Simon Willnauer 2015-09-16 14:28:45 +02:00
parent de2566a023
commit 5f258b1585
7 changed files with 639 additions and 38 deletions

View File

@ -104,4 +104,16 @@ public class ParseField {
public String toString() {
return getPreferredName();
}
public String getAllReplacedWith() {
return allReplacedWith;
}
public String getCamelCaseName() {
return camelCaseName;
}
public String[] getDeprecatedNames() {
return deprecatedNames;
}
}

View File

@ -0,0 +1,308 @@
package org.elasticsearch.common.xcontent;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.index.Index;
import java.io.IOException;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Supplier;
/**
* A declarative Object parser to parse any kind of XContent structures into existing object with setters.
* The Parser is designed to be declarative and stateless. A single parser is defined for one object level, nested
* elements can be added via {@link #declareObject(BiConsumer, BiFunction, ParseField)} which is commonly done by
* declaring yet another instance of {@link ObjectParser}. Instances of {@link ObjectParser} are thread-safe and can be
* re-used across parsing operations. It's recommended to use the high level declare methods like {@link #declareString(BiConsumer, ParseField)}
* instead of {@link #declareField} which can be used to implement exceptional parsing operations not covered by the high level methods.
*/
public final class ObjectParser<Value, Context> implements BiFunction<XContentParser, Context, Value> {
private final String name;
private final Supplier<Value> valueSupplier;
/**
* Creates a new ObjectParser instance with a name. This name is used to reference the parser in exceptions and messages.
*/
public ObjectParser(String name) {
this(name, null);
}
/**
* Creates a new ObjectParser instance which a name.
* @param name the parsers name, used to reference the parser in exceptions and messages.
* @param valueSupplier a supplier that creates a new Value instance used when the parser is used as an inner object parser.
*/
public ObjectParser(String name, Supplier<Value> valueSupplier) {
this.name = name;
this.valueSupplier = valueSupplier;
}
/**
* Parses a Value from the given {@link XContentParser}
* @param parser the parser to build a value from
* @return a new value instance drawn from the provided value supplier on {@link #ObjectParser(String, Supplier)}
* @throws IOException if an IOException occurs.
*/
public Value parse(XContentParser parser) throws IOException {
if (valueSupplier == null) {
throw new NullPointerException("valueSupplier is not set");
}
return parse(parser, valueSupplier.get(), null);
}
/**
* Parses a Value from the given {@link XContentParser}
* @param parser the parser to build a value from
* @param value the value to fill from the parser
* @return the parsed value
* @throws IOException if an IOException occurs.
*/
public Value parse(XContentParser parser, Value value) throws IOException {
return parse(parser, value, null);
}
/**
* Parses a Value from the given {@link XContentParser}
* @param parser the parser to build a value from
* @param value the value to fill from the parser
* @param context an optional context that is passed along to all declared field parsers
* @return the parsed value
* @throws IOException if an IOException occurs.
*/
public Value parse(XContentParser parser, Value value, Context context) throws IOException {
XContentParser.Token token;
if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
token = parser.currentToken();
} else {
token = parser.nextToken();
if (token != XContentParser.Token.START_OBJECT) {
throw new IllegalStateException("[" + name + "] Expected START_OBJECT but was: " + token);
}
}
FieldParser<Value> fieldParser = null;
String currentFieldName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
fieldParser = getParser(currentFieldName);
} else {
if (currentFieldName == null) {
throw new IllegalStateException("[" + name + "] no field found");
}
assert fieldParser != null;
fieldParser.assertSupports(name, token, currentFieldName, parser.getParseFieldMatcher());
parseSub(parser, fieldParser, currentFieldName, value, context);
fieldParser = null;
}
}
return value;
}
private void parseArray(XContentParser parser, FieldParser<Value> fieldParser, String currentFieldName, Value value, Context context) throws IOException {
assert parser.currentToken() == XContentParser.Token.START_ARRAY : "Token was: " + parser.currentToken();
parseValue(parser, fieldParser, currentFieldName, value, context);
}
private void parseValue(XContentParser parser, FieldParser<Value> fieldParser, String currentFieldName, Value value, Context context) throws IOException {
try {
fieldParser.parser.parse(parser, value, context);
} catch (Exception ex) {
throw new ParsingException(new Index("_na_"), parser, "[" + name + "] failed to parse field [" + currentFieldName + "]", ex);
}
}
private void parseSub(XContentParser parser, FieldParser<Value> fieldParser, String currentFieldName, Value value, Context context) throws IOException {
final XContentParser.Token token = parser.currentToken();
switch (token) {
case START_OBJECT:
parseValue(parser, fieldParser, currentFieldName, value, context);
break;
case START_ARRAY:
parseArray(parser, fieldParser, currentFieldName, value, context);
break;
case END_OBJECT:
case END_ARRAY:
case FIELD_NAME:
throw new IllegalStateException("[" + name + "]" + token + " is unexpected");
case VALUE_STRING:
case VALUE_NUMBER:
case VALUE_BOOLEAN:
case VALUE_EMBEDDED_OBJECT:
case VALUE_NULL:
parseValue(parser, fieldParser, currentFieldName, value, context);
}
}
protected FieldParser getParser(String fieldName) {
FieldParser<Value> parser = fieldParserMap.get(fieldName);
if (parser == null) {
throw new IllegalArgumentException("[" + name + "] unknown field [" + fieldName + "], parser not found");
}
return parser;
}
@Override
public Value apply(XContentParser parser, Context context) {
if (valueSupplier == null) {
throw new NullPointerException("valueSupplier is not set");
}
try {
return parse(parser, valueSupplier.get(), context);
} catch (IOException e) {
throw new ParsingException(new Index("_na_"), parser, "[" + name + "] failed to parse object", e);
}
}
public interface Parser<Value, Context> {
void parse(XContentParser parser, Value value, Context context) throws IOException;
}
private interface IOSupplier<T> {
T get() throws IOException;
}
private final Map<String, FieldParser> fieldParserMap = new HashMap<>();
public void declareField(Parser<Value, Context> p, ParseField parseField, ValueType type) {
FieldParser fieldParser = new FieldParser(p, type.supportedTokens(), parseField, type);
for (String fieldValue : parseField.getAllNamesIncludedDeprecated()) {
fieldParserMap.putIfAbsent(fieldValue, fieldParser);
}
}
public void declareStringArray(BiConsumer<Value, List<String>> consumer, ParseField field) {
declareField((p, v, c) -> consumer.accept(v, parseArray(p, p::text)), field, ValueType.STRING_ARRAY);
}
public void declareDoubleArray(BiConsumer<Value, List<Double>> consumer, ParseField field) {
declareField((p, v, c) -> consumer.accept(v, parseArray(p, p::doubleValue)), field, ValueType.DOUBLE_ARRAY);
}
public void declareFloatArray(BiConsumer<Value, List<Float>> consumer, ParseField field) {
declareField((p, v, c) -> consumer.accept(v, parseArray(p, p::floatValue)), field, ValueType.FLOAT_ARRAY);
}
public void declareLongArray(BiConsumer<Value, List<Long>> consumer, ParseField field) {
declareField((p, v, c) -> consumer.accept(v, parseArray(p, p::longValue)), field, ValueType.LONG_ARRAY);
}
public void declareIntArray(BiConsumer<Value, List<Integer>> consumer, ParseField field) {
declareField((p, v, c) -> consumer.accept(v, parseArray(p, p::intValue)), field, ValueType.INT_ARRAY);
}
private final <T> List<T> parseArray(XContentParser parser, IOSupplier<T> supplier) throws IOException {
List<T> list = new ArrayList<>();
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
list.add(supplier.get());
}
return list;
}
public <T> void declareObject(BiConsumer<Value, T> consumer, BiFunction<XContentParser, Context, T> objectParser, ParseField field) {
declareField((p, v, c) -> consumer.accept(v, objectParser.apply(p, c)), field, ValueType.OBJECT);
}
public void declareFloat(BiConsumer<Value, Float> consumer, ParseField field) {
declareField((p, v, c) -> consumer.accept(v, p.floatValue()), field, ValueType.FLOAT);
}
public void declareDouble(BiConsumer<Value, Double> consumer, ParseField field) {
declareField((p, v, c) -> consumer.accept(v, p.doubleValue()), field, ValueType.DOUBLE);
}
public void declareLong(BiConsumer<Value, Long> consumer, ParseField field) {
declareField((p, v, c) -> consumer.accept(v, p.longValue()), field, ValueType.LONG);
}
public void declareInt(BiConsumer<Value, Integer> consumer, ParseField field) {
declareField((p, v, c) -> consumer.accept(v, p.intValue()), field, ValueType.INT);
}
public void declareString(BiConsumer<Value, String> consumer, ParseField field) {
declareField((p, v, c) -> consumer.accept(v, p.text()), field, ValueType.STRING);
}
public void declareStringOrNull(BiConsumer<Value, String> consumer, ParseField field) {
declareField((p, v, c) -> consumer.accept(v, p.currentToken() == XContentParser.Token.VALUE_NULL ? null : p.text()), field, ValueType.STRING_OR_NULL);
}
public void declareBoolean(BiConsumer<Value, Boolean> consumer, ParseField field) {
declareField((p, v, c) -> consumer.accept(v, p.booleanValue()), field, ValueType.BOOLEAN);
}
public static class FieldParser<T> {
private final Parser parser;
private final EnumSet<XContentParser.Token> supportedTokens;
private final ParseField parseField;
private final ValueType type;
public FieldParser(Parser parser, EnumSet<XContentParser.Token> supportedTokens, ParseField parseField, ValueType type) {
this.parser = parser;
this.supportedTokens = supportedTokens;
this.parseField = parseField;
this.type = type;
}
public void assertSupports(String parserName, XContentParser.Token token, String currentFieldName, ParseFieldMatcher matcher) {
if (matcher.match(currentFieldName, parseField) == false) {
throw new IllegalStateException("[" + parserName + "] parsefield doesn't accept: " + currentFieldName);
}
if (supportedTokens.contains(token) == false) {
throw new IllegalArgumentException("[" + parserName + "] " + currentFieldName + " doesn't support values of type: " + token);
}
}
@Override
public String toString() {
String[] deprecatedNames = parseField.getDeprecatedNames();
String allReplacedWith = parseField.getAllReplacedWith();
return "FieldParser{" +
"preferred_name=" + parseField.getPreferredName() +
", supportedTokens=" + supportedTokens +
(deprecatedNames == null || deprecatedNames.length == 0 ? "" : ", deprecated_names=" + Arrays.toString(deprecatedNames )) +
(allReplacedWith == null ? "" : ", replaced_with=" + allReplacedWith) +
", type=" + type.name() +
'}';
}
}
public enum ValueType {
STRING(EnumSet.of(XContentParser.Token.VALUE_STRING)),
STRING_OR_NULL(EnumSet.of(XContentParser.Token.VALUE_STRING, XContentParser.Token.VALUE_NULL)),
FLOAT(EnumSet.of(XContentParser.Token.VALUE_NUMBER, XContentParser.Token.VALUE_STRING)),
DOUBLE(EnumSet.of(XContentParser.Token.VALUE_NUMBER, XContentParser.Token.VALUE_STRING)),
LONG(EnumSet.of(XContentParser.Token.VALUE_NUMBER, XContentParser.Token.VALUE_STRING)),
INT(EnumSet.of(XContentParser.Token.VALUE_NUMBER, XContentParser.Token.VALUE_STRING)),
BOOLEAN(EnumSet.of(XContentParser.Token.VALUE_BOOLEAN)), STRING_ARRAY(EnumSet.of(XContentParser.Token.START_ARRAY)),
FLOAT_ARRAY(EnumSet.of(XContentParser.Token.START_ARRAY)),
DOUBLE_ARRAY(EnumSet.of(XContentParser.Token.START_ARRAY)),
LONG_ARRAY(EnumSet.of(XContentParser.Token.START_ARRAY)),
INT_ARRAY(EnumSet.of(XContentParser.Token.START_ARRAY)),
BOOLEAN_ARRAY(EnumSet.of(XContentParser.Token.START_ARRAY)),
OBJECT(EnumSet.of(XContentParser.Token.START_OBJECT));
private final EnumSet<XContentParser.Token> tokens;
ValueType(EnumSet<XContentParser.Token> tokens) {
this.tokens = tokens;
}
public EnumSet<XContentParser.Token> supportedTokens() {
return this.tokens;
}
}
@Override
public String toString() {
return "ObjectParser{" +
"name='" + name + '\'' +
", fields=" + fieldParserMap.values() +
'}';
}
}

View File

@ -20,6 +20,7 @@
package org.elasticsearch.common.xcontent;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.lease.Releasable;
import java.io.IOException;
@ -252,4 +253,15 @@ public interface XContentParser extends Releasable {
XContentLocation getTokenLocation();
boolean isClosed();
/**
* Returns this parsers {@link ParseFieldMatcher}
*/
ParseFieldMatcher getParseFieldMatcher();
/**
* Sets this parsers {@link ParseFieldMatcher}
*/
void setParseFieldMatcher(ParseFieldMatcher matcher) ;
}

View File

@ -21,6 +21,7 @@ package org.elasticsearch.common.xcontent.support;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.Booleans;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
@ -31,6 +32,8 @@ import java.util.*;
*/
public abstract class AbstractXContentParser implements XContentParser {
private ParseFieldMatcher matcher = ParseFieldMatcher.STRICT;
//Currently this is not a setting that can be changed and is a policy
// that relates to how parsing of things like "boost" are done across
// the whole of Elasticsearch (eg if String "1.0" is a valid float).
@ -322,4 +325,12 @@ public abstract class AbstractXContentParser implements XContentParser {
@Override
public abstract boolean isClosed();
public ParseFieldMatcher getParseFieldMatcher() {
return matcher;
}
public void setParseFieldMatcher(ParseFieldMatcher matcher) {
this.matcher = matcher;
}
}

View File

@ -95,7 +95,7 @@ public class QueryParseContext {
private XContentParser parser;
private ParseFieldMatcher parseFieldMatcher;
private ParseFieldMatcher parseFieldMatcher = ParseFieldMatcher.EMPTY;
private boolean allowUnmappedFields;
@ -112,6 +112,9 @@ public class QueryParseContext {
}
public void parseFieldMatcher(ParseFieldMatcher parseFieldMatcher) {
if (parseFieldMatcher == null) {
throw new IllegalArgumentException("parseFieldMatcher must not be null");
}
this.parseFieldMatcher = parseFieldMatcher;
}
@ -124,6 +127,7 @@ public class QueryParseContext {
this.parseFieldMatcher = ParseFieldMatcher.EMPTY;
this.lookup = null;
this.parser = jp;
this.parser.setParseFieldMatcher(parseFieldMatcher);
this.namedQueries.clear();
this.nestedScope = new NestedScope();
this.isFilter = false;

View File

@ -24,8 +24,9 @@ import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParser.Token;
import org.elasticsearch.index.query.ParsedQuery;
import org.elasticsearch.search.internal.ContextIndexSearcher;
import org.elasticsearch.search.internal.SearchContext;
@ -179,44 +180,18 @@ public final class QueryRescorer implements Rescorer {
}
}
private static final ObjectParser<QueryRescoreContext, SearchContext> RESCORE_PARSER = new ObjectParser<>("query", null);
static {
RESCORE_PARSER.declareObject(QueryRescoreContext::setParsedQuery, (p, c) -> c.queryParserService().parse(p), new ParseField("rescore_query"));
RESCORE_PARSER.declareFloat(QueryRescoreContext::setQueryWeight, new ParseField("query_weight"));
RESCORE_PARSER.declareFloat(QueryRescoreContext::setRescoreQueryWeight, new ParseField("rescore_query_weight"));
RESCORE_PARSER.declareString(QueryRescoreContext::setScoreMode, new ParseField("score_mode"));
}
@Override
public RescoreSearchContext parse(XContentParser parser, SearchContext context) throws IOException {
Token token;
String fieldName = null;
QueryRescoreContext rescoreContext = new QueryRescoreContext(this);
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
fieldName = parser.currentName();
if ("rescore_query".equals(fieldName)) {
ParsedQuery parsedQuery = context.queryParserService().parse(parser);
rescoreContext.setParsedQuery(parsedQuery);
}
} else if (token.isValue()) {
if ("query_weight".equals(fieldName)) {
rescoreContext.setQueryWeight(parser.floatValue());
} else if ("rescore_query_weight".equals(fieldName)) {
rescoreContext.setRescoreQueryWeight(parser.floatValue());
} else if ("score_mode".equals(fieldName)) {
String sScoreMode = parser.text();
if ("avg".equals(sScoreMode)) {
rescoreContext.setScoreMode(ScoreMode.Avg);
} else if ("max".equals(sScoreMode)) {
rescoreContext.setScoreMode(ScoreMode.Max);
} else if ("min".equals(sScoreMode)) {
rescoreContext.setScoreMode(ScoreMode.Min);
} else if ("total".equals(sScoreMode)) {
rescoreContext.setScoreMode(ScoreMode.Total);
} else if ("multiply".equals(sScoreMode)) {
rescoreContext.setScoreMode(ScoreMode.Multiply);
} else {
throw new IllegalArgumentException("[rescore] illegal score_mode [" + sScoreMode + "]");
}
} else {
throw new IllegalArgumentException("rescore doesn't support [" + fieldName + "]");
}
}
}
return rescoreContext;
return RESCORE_PARSER.parse(parser, new QueryRescoreContext(this), context);
}
private final static Comparator<ScoreDoc> SCORE_DOC_COMPARATOR = new Comparator<ScoreDoc>() {
@ -305,6 +280,22 @@ public final class QueryRescorer implements Rescorer {
this.scoreMode = scoreMode;
}
public void setScoreMode(String scoreMode) {
if ("avg".equals(scoreMode)) {
setScoreMode(ScoreMode.Avg);
} else if ("max".equals(scoreMode)) {
setScoreMode(ScoreMode.Max);
} else if ("min".equals(scoreMode)) {
setScoreMode(ScoreMode.Min);
} else if ("total".equals(scoreMode)) {
setScoreMode(ScoreMode.Total);
} else if ("multiply".equals(scoreMode)) {
setScoreMode(ScoreMode.Multiply);
} else {
throw new IllegalArgumentException("illegal score_mode [" + scoreMode + "]");
}
}
}
@Override

View File

@ -0,0 +1,263 @@
package org.elasticsearch.common.xcontent;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ObjectParserTests extends ESTestCase {
public void testBasics() throws IOException {
XContentParser parser = XContentType.JSON.xContent().createParser("{\"test\" : \"foo\", \"test_number\" : 2, \"testArray\": [1,2,3,4]}");
class TestStruct {
public String test;
int testNumber;
List<Integer> ints = new ArrayList<>();
public void setTestNumber(int testNumber) {
this.testNumber = testNumber;
}
public void setInts(List<Integer> ints) {
this.ints = ints;
}
}
ObjectParser<TestStruct, Void> objectParser = new ObjectParser("foo");
TestStruct s = new TestStruct();
objectParser.declareField((i, c, x) -> c.test = i.text(), new ParseField("test"), ObjectParser.ValueType.STRING);
objectParser.declareInt(TestStruct::setTestNumber, new ParseField("test_number"));
objectParser.declareIntArray(TestStruct::setInts, new ParseField("test_array"));
parser.setParseFieldMatcher(ParseFieldMatcher.STRICT);
objectParser.parse(parser, s);
assertEquals(s.test, "foo");
assertEquals(s.testNumber, 2);
assertEquals(s.ints, Arrays.asList(1, 2, 3, 4));
assertEquals(objectParser.toString(), "ObjectParser{name='foo', fields=[FieldParser{preferred_name=test, supportedTokens=[VALUE_STRING], type=STRING}, FieldParser{preferred_name=test_number, supportedTokens=[VALUE_STRING, VALUE_NUMBER], type=INT}, FieldParser{preferred_name=test_array, supportedTokens=[START_ARRAY], type=INT_ARRAY}, FieldParser{preferred_name=test_array, supportedTokens=[START_ARRAY], type=INT_ARRAY}, FieldParser{preferred_name=test_number, supportedTokens=[VALUE_STRING, VALUE_NUMBER], type=INT}]}");
}
public void testExceptions() throws IOException {
XContentParser parser = XContentType.JSON.xContent().createParser("{\"test\" : \"foo\"}");
class TestStruct {
public int test;
public void setTest(int test) {
this.test = test;
}
}
ObjectParser<TestStruct, TestStruct> objectParser = new ObjectParser("the_parser");
TestStruct s = new TestStruct();
objectParser.declareInt(TestStruct::setTest, new ParseField("test"));
try {
objectParser.parse(parser, s);
fail("numeric value expected");
} catch (ParsingException ex) {
assertEquals(ex.getMessage(), "[the_parser] failed to parse field [test]");
assertTrue(ex.getCause() instanceof NumberFormatException);
}
parser = XContentType.JSON.xContent().createParser("{\"not_supported_field\" : \"foo\"}");
try {
objectParser.parse(parser, s);
fail("field not supported");
} catch (IllegalArgumentException ex) {
assertEquals(ex.getMessage(), "[the_parser] unknown field [not_supported_field], parser not found");
}
}
public void testDeprecationFail() throws IOException {
XContentParser parser = XContentType.JSON.xContent().createParser("{\"old_test\" : \"foo\"}");
class TestStruct {
public String test;
}
ObjectParser<TestStruct, Void> objectParser = new ObjectParser("foo");
TestStruct s = new TestStruct();
objectParser.declareField((i, v, c) -> v.test = i.text(), new ParseField("test", "old_test"), ObjectParser.ValueType.STRING);
parser.setParseFieldMatcher(ParseFieldMatcher.STRICT);
try {
objectParser.parse(parser, s);
fail("deprecated value");
} catch (IllegalArgumentException ex) {
assertEquals(ex.getMessage(), "Deprecated field [old_test] used, expected [test] instead");
}
assertNull(s.test);
parser = XContentType.JSON.xContent().createParser("{\"old_test\" : \"foo\"}");
parser.setParseFieldMatcher(ParseFieldMatcher.EMPTY);
objectParser.parse(parser, s);
assertEquals("foo", s.test);
}
public void testFailOnValueType() throws IOException {
XContentParser parser = XContentType.JSON.xContent().createParser("{\"numeric_value\" : false}");
class TestStruct {
public String test;
}
ObjectParser<TestStruct, Void> objectParser = new ObjectParser("foo");
TestStruct s = new TestStruct();
objectParser.declareField((i, c, x) -> c.test = i.text(), new ParseField("numeric_value"), ObjectParser.ValueType.FLOAT);
parser.setParseFieldMatcher(ParseFieldMatcher.STRICT);
try {
objectParser.parse(parser, s);
fail("wrong type - must be number");
} catch (IllegalArgumentException ex) {
assertEquals(ex.getMessage(), "[foo] numeric_value doesn't support values of type: VALUE_BOOLEAN");
}
}
public void testParseNested() throws IOException {
XContentParser parser = XContentType.JSON.xContent().createParser("{ \"test\" : 1, \"object\" : { \"test\": 2}}");
class TestStruct {
public int test;
TestStruct object;
}
ObjectParser<TestStruct, Void> objectParser = new ObjectParser("foo");
TestStruct s = new TestStruct();
s.object = new TestStruct();
objectParser.declareField((i, c, x) -> c.test = i.intValue(), new ParseField("test"), ObjectParser.ValueType.INT);
objectParser.declareField((i, c, x) -> objectParser.parse(parser, c.object), new ParseField("object"), ObjectParser.ValueType.OBJECT);
objectParser.parse(parser, s);
assertEquals(s.test, 1);
assertEquals(s.object.test, 2);
}
public void testParseNestedShortcut() throws IOException {
XContentParser parser = XContentType.JSON.xContent().createParser("{ \"test\" : 1, \"object\" : { \"test\": 2}}");
ObjectParser<StaticTestStruct, Void> objectParser = new ObjectParser("foo", StaticTestStruct::new);
objectParser.declareInt(StaticTestStruct::setTest, new ParseField("test"));
objectParser.declareObject(StaticTestStruct::setObject, objectParser, new ParseField("object"));
StaticTestStruct s = objectParser.parse(parser);
assertEquals(s.test, 1);
assertEquals(s.object.test, 2);
}
static class StaticTestStruct {
public int test;
StaticTestStruct object;
public void setTest(int test) {
this.test = test;
}
public void setObject(StaticTestStruct object) {
this.object = object;
}
}
public void testAllVariants() throws IOException {
XContentBuilder builder = XContentBuilder.builder(XContentType.JSON.xContent());
builder.startObject();
builder.field("int_field", randomBoolean() ? "1" : 1);
builder.array("int_array_field", randomBoolean() ? "1" : 1);
builder.field("double_field", randomBoolean() ? "2.1" : 2.1d);
builder.array("double_array_field", randomBoolean() ? "2.1" : 2.1d);
builder.field("float_field", randomBoolean() ? "3.1" : 3.1f);
builder.array("float_array_field", randomBoolean() ? "3.1" : 3.1);
builder.field("long_field", randomBoolean() ? "4" : 4);
builder.array("long_array_field", randomBoolean() ? "4" : 4);
builder.field("string_field", "5");
builder.array("string_array_field", "5");
boolean nullValue = randomBoolean();
builder.field("boolean_field", nullValue);
builder.field("string_or_null", nullValue ? null : "5");
builder.endObject();
XContentParser parser = XContentType.JSON.xContent().createParser(builder.string());
class TestStruct {
int int_field;
long long_field;
float float_field;
double double_field;
String string_field;
List<Integer> int_array_field;
List<Long> long_array_field;
List<Float> float_array_field;
List<Double> double_array_field;
List<String> string_array_field;
boolean null_value;
String string_or_null = "adsfsa";
public void setInt_field(int int_field) {
this.int_field = int_field;
}
public void setLong_field(long long_field) {
this.long_field = long_field;
}
public void setFloat_field(float float_field) {
this.float_field = float_field;
}
public void setDouble_field(double double_field) {
this.double_field = double_field;
}
public void setString_field(String string_field) {
this.string_field = string_field;
}
public void setInt_array_field(List<Integer> int_array_field) {
this.int_array_field = int_array_field;
}
public void setLong_array_field(List<Long> long_array_field) {
this.long_array_field = long_array_field;
}
public void setFloat_array_field(List<Float> float_array_field) {
this.float_array_field = float_array_field;
}
public void setDouble_array_field(List<Double> double_array_field) {
this.double_array_field = double_array_field;
}
public void setString_array_field(List<String> string_array_field) {
this.string_array_field = string_array_field;
}
public void setNull_value(boolean null_value) {
this.null_value = null_value;
}
public void setString_or_null(String string_or_null) {
this.string_or_null = string_or_null;
}
}
ObjectParser<TestStruct, Void> objectParser = new ObjectParser("foo");
objectParser.declareInt(TestStruct::setInt_field, new ParseField("int_field"));
objectParser.declareIntArray(TestStruct::setInt_array_field, new ParseField("int_array_field"));
objectParser.declareLong(TestStruct::setLong_field, new ParseField("long_field"));
objectParser.declareLongArray(TestStruct::setLong_array_field, new ParseField("long_array_field"));
objectParser.declareDouble(TestStruct::setDouble_field, new ParseField("double_field"));
objectParser.declareDoubleArray(TestStruct::setDouble_array_field, new ParseField("double_array_field"));
objectParser.declareFloat(TestStruct::setFloat_field, new ParseField("float_field"));
objectParser.declareFloatArray(TestStruct::setFloat_array_field, new ParseField("float_array_field"));
objectParser.declareString(TestStruct::setString_field, new ParseField("string_field"));
objectParser.declareStringArray(TestStruct::setString_array_field, new ParseField("string_array_field"));
objectParser.declareStringOrNull(TestStruct::setString_or_null, new ParseField("string_or_null"));
objectParser.declareBoolean(TestStruct::setNull_value, new ParseField("boolean_field"));
TestStruct parse = objectParser.parse(parser, new TestStruct());
assertArrayEquals(parse.double_array_field.toArray(), Arrays.asList(2.1d).toArray());
assertEquals(parse.double_field, 2.1d, 0.0d);
assertArrayEquals(parse.long_array_field.toArray(), Arrays.asList(4l).toArray());
assertEquals(parse.long_field, 4l);
assertArrayEquals(parse.string_array_field.toArray(), Arrays.asList("5").toArray());
assertEquals(parse.string_field, "5");
assertArrayEquals(parse.int_array_field.toArray(), Arrays.asList(1).toArray());
assertEquals(parse.int_field, 1);
assertArrayEquals(parse.float_array_field.toArray(), Arrays.asList(3.1f).toArray());
assertEquals(parse.float_field, 3.1f, 0.0f);
assertEquals(nullValue, parse.null_value);
if (nullValue) {
assertNull(parse.string_or_null);
} else {
assertEquals(parse.string_field, "5");
}
}
}