/* * Copyright (C) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.gson.stream; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.Strictness; import com.google.gson.internal.JsonReaderInternalAccess; import com.google.gson.internal.TroubleshootingGuide; import com.google.gson.internal.bind.JsonTreeReader; import java.io.Closeable; import java.io.EOFException; import java.io.IOException; import java.io.Reader; import java.util.Arrays; import java.util.Objects; /** * Reads a JSON (RFC 8259) encoded value as a * stream of tokens. This stream includes both literal values (strings, numbers, booleans, and * nulls) as well as the begin and end delimiters of objects and arrays. The tokens are traversed in * depth-first order, the same order that they appear in the JSON document. Within JSON objects, * name/value pairs are represented by a single token. * *

Parsing JSON

* * To create a recursive descent parser for your own JSON streams, first create an entry point * method that creates a {@code JsonReader}. * *

Next, create handler methods for each structure in your JSON text. You'll need a method for * each object type and for each array type. * *

* *

When a nested object or array is encountered, delegate to the corresponding handler method. * *

When an unknown name is encountered, strict parsers should fail with an exception. Lenient * parsers should call {@link #skipValue()} to recursively skip the value's nested tokens, which may * otherwise conflict. * *

If a value may be null, you should first check using {@link #peek()}. Null literals can be * consumed using either {@link #nextNull()} or {@link #skipValue()}. * *

Configuration

* * The behavior of this reader can be customized with the following methods: * * * * The default configuration of {@code JsonReader} instances used internally by the {@link Gson} * class differs, and can be adjusted with the various {@link GsonBuilder} methods. * *

Example

* * Suppose we'd like to parse a stream of messages such as the following: * *
{@code
 * [
 *   {
 *     "id": 912345678901,
 *     "text": "How do I read a JSON stream in Java?",
 *     "geo": null,
 *     "user": {
 *       "name": "json_newb",
 *       "followers_count": 41
 *      }
 *   },
 *   {
 *     "id": 912345678902,
 *     "text": "@json_newb just use JsonReader!",
 *     "geo": [50.454722, -104.606667],
 *     "user": {
 *       "name": "jesse",
 *       "followers_count": 2
 *     }
 *   }
 * ]
 * }
* * This code implements the parser for the above structure: * *
{@code
 * public List readJsonStream(InputStream in) throws IOException {
 *   JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
 *   try {
 *     return readMessagesArray(reader);
 *   } finally {
 *     reader.close();
 *   }
 * }
 *
 * public List readMessagesArray(JsonReader reader) throws IOException {
 *   List messages = new ArrayList<>();
 *
 *   reader.beginArray();
 *   while (reader.hasNext()) {
 *     messages.add(readMessage(reader));
 *   }
 *   reader.endArray();
 *   return messages;
 * }
 *
 * public Message readMessage(JsonReader reader) throws IOException {
 *   long id = -1;
 *   String text = null;
 *   User user = null;
 *   List geo = null;
 *
 *   reader.beginObject();
 *   while (reader.hasNext()) {
 *     String name = reader.nextName();
 *     if (name.equals("id")) {
 *       id = reader.nextLong();
 *     } else if (name.equals("text")) {
 *       text = reader.nextString();
 *     } else if (name.equals("geo") && reader.peek() != JsonToken.NULL) {
 *       geo = readDoublesArray(reader);
 *     } else if (name.equals("user")) {
 *       user = readUser(reader);
 *     } else {
 *       reader.skipValue();
 *     }
 *   }
 *   reader.endObject();
 *   return new Message(id, text, user, geo);
 * }
 *
 * public List readDoublesArray(JsonReader reader) throws IOException {
 *   List doubles = new ArrayList<>();
 *
 *   reader.beginArray();
 *   while (reader.hasNext()) {
 *     doubles.add(reader.nextDouble());
 *   }
 *   reader.endArray();
 *   return doubles;
 * }
 *
 * public User readUser(JsonReader reader) throws IOException {
 *   String username = null;
 *   int followersCount = -1;
 *
 *   reader.beginObject();
 *   while (reader.hasNext()) {
 *     String name = reader.nextName();
 *     if (name.equals("name")) {
 *       username = reader.nextString();
 *     } else if (name.equals("followers_count")) {
 *       followersCount = reader.nextInt();
 *     } else {
 *       reader.skipValue();
 *     }
 *   }
 *   reader.endObject();
 *   return new User(username, followersCount);
 * }
 * }
* *

Number Handling

* * This reader permits numeric values to be read as strings and string values to be read as numbers. * For example, both elements of the JSON array {@code [1, "1"]} may be read using either {@link * #nextInt} or {@link #nextString}. This behavior is intended to prevent lossy numeric conversions: * double is JavaScript's only numeric type and very large values like {@code 9007199254740993} * cannot be represented exactly on that platform. To minimize precision loss, extremely large * values should be written and read as strings in JSON. * *

Non-Execute Prefix

* * Web servers that serve private data using JSON may be vulnerable to Cross-site request * forgery attacks. In such an attack, a malicious site gains access to a private JSON file by * executing it with an HTML {@code