@@ -78,14 +78,69 @@ public static <Value, ElementValue> BiConsumer<Value, List<ElementValue>> fromLi
78
78
};
79
79
}
80
80
81
+ private interface UnknownFieldParser <Value , Context > {
82
+
83
+ /**
84
+ * Called when an unknown field is encountered
85
+ * @param parserName the parent ObjectParser name
86
+ * @param field the name of the unknown field
87
+ * @param parser the parser to build values from
88
+ */
89
+ void acceptUnknownField (String parserName , String field , XContentParser parser , Value value , Context context ) throws IOException ;
90
+ }
91
+
92
+ private static <Value , Context > UnknownFieldParser <Value , Context > ignoreUnknown () {
93
+ return (n , f , x , v , c ) -> x .skipChildren ();
94
+ }
95
+
96
+ private static <Value , Context > UnknownFieldParser <Value , Context > errorOnUnknown () {
97
+ return (n , f , x , v , c ) -> {
98
+ throw new XContentParseException (x .getTokenLocation (),
99
+ "[" + n + "] unknown field [" + f + "], parser not found" );
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Defines how to consume a parsed undefined field
105
+ */
106
+ public interface UnknownFieldConsumer <Value > {
107
+ void accept (Value target , String field , Object value );
108
+ }
109
+
110
+ private static <Value , Context > UnknownFieldParser <Value , Context > consumeUnknownField (UnknownFieldConsumer <Value > consumer ) {
111
+ return (parserName , field , parser , value , context ) -> {
112
+ XContentParser .Token t = parser .currentToken ();
113
+ switch (t ) {
114
+ case VALUE_STRING :
115
+ consumer .accept (value , field , parser .text ());
116
+ break ;
117
+ case VALUE_NUMBER :
118
+ consumer .accept (value , field , parser .numberValue ());
119
+ break ;
120
+ case VALUE_BOOLEAN :
121
+ consumer .accept (value , field , parser .booleanValue ());
122
+ break ;
123
+ case VALUE_NULL :
124
+ consumer .accept (value , field , null );
125
+ break ;
126
+ case START_OBJECT :
127
+ consumer .accept (value , field , parser .map ());
128
+ break ;
129
+ case START_ARRAY :
130
+ consumer .accept (value , field , parser .list ());
131
+ break ;
132
+ default :
133
+ throw new XContentParseException (parser .getTokenLocation (),
134
+ "[" + parserName + "] cannot parse field [" + field + "] with value type [" + t + "]" );
135
+ }
136
+ };
137
+ }
138
+
81
139
private final Map <String , FieldParser > fieldParserMap = new HashMap <>();
82
140
private final String name ;
83
141
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 ;
142
+
143
+ private final UnknownFieldParser <Value , Context > unknownFieldParser ;
89
144
90
145
/**
91
146
* Creates a new ObjectParser instance with a name. This name is used to reference the parser in exceptions and messages.
@@ -95,25 +150,45 @@ public ObjectParser(String name) {
95
150
}
96
151
97
152
/**
98
- * Creates a new ObjectParser instance which a name.
153
+ * Creates a new ObjectParser instance with a name.
99
154
* @param name the parsers name, used to reference the parser in exceptions and messages.
100
155
* @param valueSupplier a supplier that creates a new Value instance used when the parser is used as an inner object parser.
101
156
*/
102
157
public ObjectParser (String name , @ Nullable Supplier <Value > valueSupplier ) {
103
- this (name , false , valueSupplier );
158
+ this (name , errorOnUnknown () , valueSupplier );
104
159
}
105
160
106
161
/**
107
- * Creates a new ObjectParser instance which a name.
162
+ * Creates a new ObjectParser instance with a name.
108
163
* @param name the parsers name, used to reference the parser in exceptions and messages.
109
164
* @param ignoreUnknownFields Should this parser ignore unknown fields? This should generally be set to true only when parsing
110
165
* responses from external systems, never when parsing requests from users.
111
166
* @param valueSupplier a supplier that creates a new Value instance used when the parser is used as an inner object parser.
112
167
*/
113
168
public ObjectParser (String name , boolean ignoreUnknownFields , @ Nullable Supplier <Value > valueSupplier ) {
169
+ this (name , ignoreUnknownFields ? ignoreUnknown () : errorOnUnknown (), valueSupplier );
170
+ }
171
+
172
+ /**
173
+ * Creates a new ObjectParser instance with a name.
174
+ * @param name the parsers name, used to reference the parser in exceptions and messages.
175
+ * @param unknownFieldConsumer how to consume parsed unknown fields
176
+ * @param valueSupplier a supplier that creates a new Value instance used when the parser is used as an inner object parser.
177
+ */
178
+ public ObjectParser (String name , UnknownFieldConsumer <Value > unknownFieldConsumer , @ Nullable Supplier <Value > valueSupplier ) {
179
+ this (name , consumeUnknownField (unknownFieldConsumer ), valueSupplier );
180
+ }
181
+
182
+ /**
183
+ * Creates a new ObjectParser instance with a name.
184
+ * @param name the parsers name, used to reference the parser in exceptions and messages.
185
+ * @param unknownFieldParser how to parse unknown fields
186
+ * @param valueSupplier a supplier that creates a new Value instance used when the parser is used as an inner object parser.
187
+ */
188
+ private ObjectParser (String name , UnknownFieldParser <Value , Context > unknownFieldParser , @ Nullable Supplier <Value > valueSupplier ) {
114
189
this .name = name ;
115
190
this .valueSupplier = valueSupplier ;
116
- this .ignoreUnknownFields = ignoreUnknownFields ;
191
+ this .unknownFieldParser = unknownFieldParser ;
117
192
}
118
193
119
194
/**
@@ -155,14 +230,13 @@ public Value parse(XContentParser parser, Value value, Context context) throws I
155
230
while ((token = parser .nextToken ()) != XContentParser .Token .END_OBJECT ) {
156
231
if (token == XContentParser .Token .FIELD_NAME ) {
157
232
currentFieldName = parser .currentName ();
158
- fieldParser = getParser (currentFieldName , parser );
233
+ fieldParser = fieldParserMap . get (currentFieldName );
159
234
} else {
160
235
if (currentFieldName == null ) {
161
236
throw new XContentParseException (parser .getTokenLocation (), "[" + name + "] no field found" );
162
237
}
163
238
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
239
+ unknownFieldParser .acceptUnknownField (name , currentFieldName , parser , value , context );
166
240
} else {
167
241
fieldParser .assertSupports (name , parser , currentFieldName );
168
242
parseSub (parser , fieldParser , currentFieldName , value , context );
@@ -363,15 +437,6 @@ private void parseSub(XContentParser parser, FieldParser fieldParser, String cur
363
437
}
364
438
}
365
439
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
-
375
440
private class FieldParser {
376
441
private final Parser <Value , Context > parser ;
377
442
private final EnumSet <XContentParser .Token > supportedTokens ;
0 commit comments