@@ -78,14 +78,63 @@ public static <Value, ElementValue> BiConsumer<Value, List<ElementValue>> fromLi
7878 };
7979 }
8080
81+ private interface UnknownFieldParser <Value , Context > {
82+
83+ void acceptUnknownField (String parserName , String field , XContentLocation location , XContentParser parser ,
84+ Value value , Context context ) throws IOException ;
85+ }
86+
87+ private static <Value , Context > UnknownFieldParser <Value , Context > ignoreUnknown () {
88+ return (n , f , l , p , v , c ) -> p .skipChildren ();
89+ }
90+
91+ private static <Value , Context > UnknownFieldParser <Value , Context > errorOnUnknown () {
92+ return (n , f , l , p , v , c ) -> {
93+ throw new XContentParseException (l , "[" + n + "] unknown field [" + f + "], parser not found" );
94+ };
95+ }
96+
97+ /**
98+ * Defines how to consume a parsed undefined field
99+ */
100+ public interface UnknownFieldConsumer <Value > {
101+ void accept (Value target , String field , Object value );
102+ }
103+
104+ private static <Value , Context > UnknownFieldParser <Value , Context > consumeUnknownField (UnknownFieldConsumer <Value > consumer ) {
105+ return (parserName , field , location , parser , value , context ) -> {
106+ XContentParser .Token t = parser .currentToken ();
107+ switch (t ) {
108+ case VALUE_STRING :
109+ consumer .accept (value , field , parser .text ());
110+ break ;
111+ case VALUE_NUMBER :
112+ consumer .accept (value , field , parser .numberValue ());
113+ break ;
114+ case VALUE_BOOLEAN :
115+ consumer .accept (value , field , parser .booleanValue ());
116+ break ;
117+ case VALUE_NULL :
118+ consumer .accept (value , field , null );
119+ break ;
120+ case START_OBJECT :
121+ consumer .accept (value , field , parser .map ());
122+ break ;
123+ case START_ARRAY :
124+ consumer .accept (value , field , parser .list ());
125+ break ;
126+ default :
127+ throw new XContentParseException (parser .getTokenLocation (),
128+ "[" + parserName + "] cannot parse field [" + field + "] with value type [" + t + "]" );
129+ }
130+ };
131+ }
132+
81133 private final Map <String , FieldParser > fieldParserMap = new HashMap <>();
82134 private final String name ;
83135 private final Supplier <Value > valueSupplier ;
84- /**
85- * Should this parser ignore unknown fields? This should generally be set to true only when parsing responses from external systems,
86- * never when parsing requests from users.
87- */
88- private final boolean ignoreUnknownFields ;
136+
137+ private final UnknownFieldParser <Value , Context > unknownFieldParser ;
89138
90139 /**
91140 * Creates a new ObjectParser instance with a name. This name is used to reference the parser in exceptions and messages.
@@ -95,25 +144,45 @@ public ObjectParser(String name) {
95144 }
96145
97146 /**
98- * Creates a new ObjectParser instance which a name.
147+ * Creates a new ObjectParser instance with a name.
99148 * @param name the parsers name, used to reference the parser in exceptions and messages.
100149 * @param valueSupplier a supplier that creates a new Value instance used when the parser is used as an inner object parser.
101150 */
102151 public ObjectParser (String name , @ Nullable Supplier <Value > valueSupplier ) {
103- this (name , false , valueSupplier );
152+ this (name , errorOnUnknown () , valueSupplier );
104153 }
105154
106155 /**
107- * Creates a new ObjectParser instance which a name.
156+ * Creates a new ObjectParser instance with a name.
108157 * @param name the parsers name, used to reference the parser in exceptions and messages.
109158 * @param ignoreUnknownFields Should this parser ignore unknown fields? This should generally be set to true only when parsing
110159 * responses from external systems, never when parsing requests from users.
111160 * @param valueSupplier a supplier that creates a new Value instance used when the parser is used as an inner object parser.
112161 */
113162 public ObjectParser (String name , boolean ignoreUnknownFields , @ Nullable Supplier <Value > valueSupplier ) {
163+ this (name , ignoreUnknownFields ? ignoreUnknown () : errorOnUnknown (), valueSupplier );
164+ }
165+
166+ /**
167+ * Creates a new ObjectParser instance with a name.
168+ * @param name the parsers name, used to reference the parser in exceptions and messages.
169+ * @param unknownFieldConsumer how to consume parsed unknown fields
170+ * @param valueSupplier a supplier that creates a new Value instance used when the parser is used as an inner object parser.
171+ */
172+ public ObjectParser (String name , UnknownFieldConsumer <Value > unknownFieldConsumer , @ Nullable Supplier <Value > valueSupplier ) {
173+ this (name , consumeUnknownField (unknownFieldConsumer ), valueSupplier );
174+ }
175+
176+ /**
177+ * Creates a new ObjectParser instance with a name.
178+ * @param name the parsers name, used to reference the parser in exceptions and messages.
179+ * @param unknownFieldParser how to parse unknown fields
180+ * @param valueSupplier a supplier that creates a new Value instance used when the parser is used as an inner object parser.
181+ */
182+ private ObjectParser (String name , UnknownFieldParser <Value , Context > unknownFieldParser , @ Nullable Supplier <Value > valueSupplier ) {
114183 this .name = name ;
115184 this .valueSupplier = valueSupplier ;
116- this .ignoreUnknownFields = ignoreUnknownFields ;
185+ this .unknownFieldParser = unknownFieldParser ;
117186 }
118187
119188 /**
@@ -152,17 +221,18 @@ public Value parse(XContentParser parser, Value value, Context context) throws I
152221
153222 FieldParser fieldParser = null ;
154223 String currentFieldName = null ;
224+ XContentLocation currentPosition = null ;
155225 while ((token = parser .nextToken ()) != XContentParser .Token .END_OBJECT ) {
156226 if (token == XContentParser .Token .FIELD_NAME ) {
157227 currentFieldName = parser .currentName ();
158- fieldParser = getParser (currentFieldName , parser );
228+ currentPosition = parser .getTokenLocation ();
229+ fieldParser = fieldParserMap .get (currentFieldName );
159230 } else {
160231 if (currentFieldName == null ) {
161232 throw new XContentParseException (parser .getTokenLocation (), "[" + name + "] no field found" );
162233 }
163234 if (fieldParser == null ) {
164- assert ignoreUnknownFields : "this should only be possible if configured to ignore known fields" ;
165- parser .skipChildren (); // noop if parser points to a value, skips children if parser is start object or start array
235+ unknownFieldParser .acceptUnknownField (name , currentFieldName , currentPosition , parser , value , context );
166236 } else {
167237 fieldParser .assertSupports (name , parser , currentFieldName );
168238 parseSub (parser , fieldParser , currentFieldName , value , context );
@@ -363,15 +433,6 @@ private void parseSub(XContentParser parser, FieldParser fieldParser, String cur
363433 }
364434 }
365435
366- private FieldParser getParser (String fieldName , XContentParser xContentParser ) {
367- FieldParser parser = fieldParserMap .get (fieldName );
368- if (parser == null && false == ignoreUnknownFields ) {
369- throw new XContentParseException (xContentParser .getTokenLocation (),
370- "[" + name + "] unknown field [" + fieldName + "], parser not found" );
371- }
372- return parser ;
373- }
374-
375436 private class FieldParser {
376437 private final Parser <Value , Context > parser ;
377438 private final EnumSet <XContentParser .Token > supportedTokens ;
0 commit comments