|
11 | 11 | import java.util.LinkedList; |
12 | 12 | import java.util.List; |
13 | 13 | import java.util.Map; |
| 14 | +import java.util.Objects; |
14 | 15 |
|
15 | 16 | /** |
16 | 17 | * Reads a JSON encoded value as a stream of tokens. |
@@ -233,8 +234,12 @@ public final <T> T getNullable(ReadValueCallback<JsonReader, T> nonNullGetter) t |
233 | 234 | * Reads and returns the current JSON object the {@link JsonReader} is pointing to. This will mutate the current |
234 | 235 | * location of this {@link JsonReader}. |
235 | 236 | * <p> |
236 | | - * If the {@link #currentToken()} isn't {@link JsonToken#START_OBJECT} or {@link JsonToken#FIELD_NAME} followed by |
237 | | - * {@link JsonToken#START_OBJECT} an {@link IllegalStateException} will be thrown. |
| 237 | + * If the {@link #currentToken()} isn't {@link JsonToken#START_OBJECT} or {@link JsonToken#FIELD_NAME} an |
| 238 | + * {@link IllegalStateException} will be thrown. |
| 239 | + * <p> |
| 240 | + * If the {@link #currentToken()} is {@link JsonToken#FIELD_NAME} this will create a JSON object where the first |
| 241 | + * JSON field is the {@link #currentToken()} field, meaning this can be called from the middle of a JSON object to |
| 242 | + * create a new JSON object with only a subset of fields (those remaining from when the method is called). |
238 | 243 | * <p> |
239 | 244 | * The returned {@link JsonReader} is able to be {@link #reset()} to replay the underlying JSON stream. |
240 | 245 | * |
@@ -268,37 +273,87 @@ public final <T> T getNullable(ReadValueCallback<JsonReader, T> nonNullGetter) t |
268 | 273 | * Recursively reads the JSON token sub-stream if the current token is either {@link JsonToken#START_ARRAY} or |
269 | 274 | * {@link JsonToken#START_OBJECT}. |
270 | 275 | * <p> |
271 | | - * If the current token isn't the beginning of an array or object this method is a no-op. |
| 276 | + * If the {@link #currentToken()} isn't {@link JsonToken#START_OBJECT} or {@link JsonToken#START_ARRAY} nothing will |
| 277 | + * be read. |
272 | 278 | * |
273 | 279 | * @return The raw textual value of the JSON token sub-stream. |
274 | 280 | * @throws IOException If the children cannot be read. |
275 | 281 | */ |
276 | 282 | public final String readChildren() throws IOException { |
277 | | - return readChildrenInternal(new StringBuilder()).toString(); |
| 283 | + return readInternal(new StringBuilder(), true, false).toString(); |
278 | 284 | } |
279 | 285 |
|
280 | 286 | /** |
281 | 287 | * Recursively reads the JSON token sub-stream if the current token is either {@link JsonToken#START_ARRAY} or |
282 | 288 | * {@link JsonToken#START_OBJECT} into the passed {@link StringBuilder}. |
283 | 289 | * <p> |
284 | | - * If the current token isn't the beginning of an array or object this method is a no-op. |
| 290 | + * If the {@link #currentToken()} isn't {@link JsonToken#START_OBJECT} or {@link JsonToken#START_ARRAY} nothing will |
| 291 | + * be read. |
285 | 292 | * |
286 | 293 | * @param buffer The {@link StringBuilder} where the read sub-stream will be written. |
| 294 | + * @throws NullPointerException If {@code buffer} is null. |
287 | 295 | * @throws IOException If the children cannot be read. |
288 | 296 | */ |
289 | 297 | public final void readChildren(StringBuilder buffer) throws IOException { |
290 | | - readChildrenInternal(buffer); |
| 298 | + readInternal(buffer, true, false); |
| 299 | + } |
| 300 | + |
| 301 | + /** |
| 302 | + * Reads the remaining fields in the current JSON object as a JSON object. |
| 303 | + * <p> |
| 304 | + * If the {@link #currentToken()} is {@link JsonToken#START_OBJECT} this functions the same as |
| 305 | + * {@link #readChildren()}. If the {@link #currentToken()} is {@link JsonToken#FIELD_NAME} this creates a JSON |
| 306 | + * object where the first field is the current field and reads the remaining fields in the JSON object. |
| 307 | + * <p> |
| 308 | + * If the {@link #currentToken()} isn't {@link JsonToken#START_OBJECT} or {@link JsonToken#FIELD_NAME} nothing will |
| 309 | + * be read. |
| 310 | + * |
| 311 | + * @return The raw textual value of the remaining JSON fields. |
| 312 | + * @throws IOException If the remaining JSON fields cannot be read. |
| 313 | + */ |
| 314 | + public final String readRemainingFieldsAsJsonObject() throws IOException { |
| 315 | + return readInternal(new StringBuilder(), false, true).toString(); |
| 316 | + } |
| 317 | + |
| 318 | + /** |
| 319 | + * Reads the remaining fields in the current JSON object as a JSON object. |
| 320 | + * <p> |
| 321 | + * If the {@link #currentToken()} is {@link JsonToken#START_OBJECT} this functions the same as |
| 322 | + * {@link #readChildren(StringBuilder)}. If the {@link #currentToken()} is {@link JsonToken#FIELD_NAME} this creates |
| 323 | + * a JSON object where the first field is the current field and reads the remaining fields in the JSON object. |
| 324 | + * <p> |
| 325 | + * If the {@link #currentToken()} isn't {@link JsonToken#START_OBJECT} or {@link JsonToken#FIELD_NAME} nothing will |
| 326 | + * be read. |
| 327 | + * |
| 328 | + * @param buffer The {@link StringBuilder} where the remaining JSON fields will be written. |
| 329 | + * @throws NullPointerException If {@code buffer} is null. |
| 330 | + * @throws IOException If the remaining JSON fields cannot be read. |
| 331 | + */ |
| 332 | + public final void readRemainingFieldsAsJsonObject(StringBuilder buffer) throws IOException { |
| 333 | + readInternal(buffer, false, true); |
291 | 334 | } |
292 | 335 |
|
293 | | - private StringBuilder readChildrenInternal(StringBuilder buffer) throws IOException { |
| 336 | + private StringBuilder readInternal(StringBuilder buffer, boolean canStartAtArray, boolean canStartAtFieldName) |
| 337 | + throws IOException { |
| 338 | + Objects.requireNonNull(buffer, "The 'buffer' used to read the JSON object cannot be null."); |
| 339 | + |
294 | 340 | JsonToken token = currentToken(); |
295 | 341 |
|
296 | | - // Not pointing to an array or object start, no-op. |
297 | | - if (!isStartArrayOrObject(token)) { |
| 342 | + boolean canRead = (token == JsonToken.START_OBJECT) |
| 343 | + || (canStartAtArray && token == JsonToken.START_ARRAY) |
| 344 | + || (canStartAtFieldName && token == JsonToken.FIELD_NAME); |
| 345 | + |
| 346 | + // Not a valid starting poing. |
| 347 | + if (!canRead) { |
298 | 348 | return buffer; |
299 | 349 | } |
300 | 350 |
|
301 | | - buffer.append(getText()); |
| 351 | + if (token == JsonToken.FIELD_NAME) { |
| 352 | + buffer.append("{\"").append(getText()).append("\":"); |
| 353 | + token = nextToken(); |
| 354 | + } |
| 355 | + |
| 356 | + appendJson(buffer, token); |
302 | 357 |
|
303 | 358 | // Initial array or object depth is 1. |
304 | 359 | int depth = 1; |
@@ -328,18 +383,31 @@ private StringBuilder readChildrenInternal(StringBuilder buffer) throws IOExcept |
328 | 383 | buffer.append(','); |
329 | 384 | } |
330 | 385 |
|
331 | | - if (token == JsonToken.FIELD_NAME) { |
332 | | - buffer.append("\"").append(getFieldName()).append("\":"); |
333 | | - } else if (token == JsonToken.STRING) { |
334 | | - buffer.append("\"").append(getString()).append("\""); |
335 | | - } else { |
336 | | - buffer.append(getText()); |
337 | | - } |
| 386 | + appendJson(buffer, token); |
338 | 387 | } |
339 | 388 |
|
340 | 389 | return buffer; |
341 | 390 | } |
342 | 391 |
|
| 392 | + /** |
| 393 | + * Convenience method to read a JSON element into a buffer. |
| 394 | + * |
| 395 | + * @param buffer The buffer where the JSON element value will be written. |
| 396 | + * @param token The type of the JSON element. |
| 397 | + * @throws IOException If an error occurs while reading the JSON element. |
| 398 | + */ |
| 399 | + private void appendJson(StringBuilder buffer, JsonToken token) throws IOException { |
| 400 | + // TODO (alzimmer): Think of making this a protected method. This will allow for optimizations such as where |
| 401 | + // Jackson can read text directly into a StringBuilder which removes a String copy. |
| 402 | + if (token == JsonToken.FIELD_NAME) { |
| 403 | + buffer.append("\"").append(getFieldName()).append("\":"); |
| 404 | + } else if (token == JsonToken.STRING) { |
| 405 | + buffer.append("\"").append(getString()).append("\""); |
| 406 | + } else { |
| 407 | + buffer.append(getText()); |
| 408 | + } |
| 409 | + } |
| 410 | + |
343 | 411 | /** |
344 | 412 | * Reads a JSON object. |
345 | 413 | * <p> |
|
0 commit comments