Skip to content

Commit

Permalink
Improve lenient mode documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcono1234 committed May 22, 2022
1 parent 66c3f48 commit cbd2f84
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 36 deletions.
105 changes: 84 additions & 21 deletions gson/src/main/java/com/google/gson/Gson.java
Expand Up @@ -16,25 +16,6 @@

package com.google.gson;

import java.io.EOFException;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongArray;

import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.Excluder;
import com.google.gson.internal.GsonBuildConfig;
Expand All @@ -58,6 +39,24 @@
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.google.gson.stream.MalformedJsonException;
import java.io.EOFException;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongArray;

/**
* This is the main class for using Gson. Gson is typically used by first constructing a
Expand Down Expand Up @@ -97,6 +96,33 @@
* <p>See the <a href="https://sites.google.com/site/gson/gson-user-guide">Gson User Guide</a>
* for a more complete set of examples.</p>
*
* <h2>Lenient JSON handling</h2>
* For legacy reasons most of the {@code Gson} methods allow JSON data which does not
* comply with the JSON specification, regardless of whether {@link GsonBuilder#setLenient()}
* is used or not. If this behavior is not desired, the following workarounds can be used:
*
* <h3>Serialization</h3>
* <ol>
* <li>Use {@link #getAdapter(Class)} to obtain the adapter for the type to be serialized
* <li>When using an existing {@code JsonWriter}, manually apply the writer settings of this
* {@code Gson} instance listed by {@link #newJsonWriter(Writer)}.<br>
* Otherwise, when not using an existing {@code JsonWriter}, use {@link #newJsonWriter(Writer)}
* to construct one.
* <li>Call {@link TypeAdapter#write(JsonWriter, Object)}
* </ol>
*
* <h3>Deserialization</h3>
* <ol>
* <li>Use {@link #getAdapter(Class)} to obtain the adapter for the type to be deserialized
* <li>When using an existing {@code JsonReader}, manually apply the reader settings of this
* {@code Gson} instance listed by {@link #newJsonReader(Reader)}.<br>
* Otherwise, when not using an existing {@code JsonReader}, use {@link #newJsonReader(Reader)}
* to construct one.
* <li>Call {@link TypeAdapter#read(JsonReader)}
* <li>Call {@link JsonReader#peek()} and verify that the result is {@link JsonToken#END_DOCUMENT}
* to make sure there is no trailing data
* </ol>
*
* @see com.google.gson.reflect.TypeToken
*
* @author Inderjeet Singh
Expand Down Expand Up @@ -736,6 +762,15 @@ public void toJson(Object src, Type typeOfSrc, Appendable writer) throws JsonIOE
/**
* Writes the JSON representation of {@code src} of type {@code typeOfSrc} to
* {@code writer}.
*
* <p>The JSON data is written in {@linkplain JsonWriter#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided writer. The lenient mode setting
* of the writer is restored once this method returns.
*
* <p>The 'HTML-safe' and 'serialize {@code null}' settings of this {@code Gson} instance
* (configured by the {@link GsonBuilder}) are applied, and the original settings of the
* writer are restored once this method returns.
*
* @throws JsonIOException if there was a problem writing to the writer
*/
@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -834,6 +869,15 @@ public JsonReader newJsonReader(Reader reader) {

/**
* Writes the JSON for {@code jsonElement} to {@code writer}.
*
* <p>The JSON data is written in {@linkplain JsonWriter#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided writer. The lenient mode setting
* of the writer is restored once this method returns.
*
* <p>The 'HTML-safe' and 'serialize {@code null}' settings of this {@code Gson} instance
* (configured by the {@link GsonBuilder}) are applied, and the original settings of the
* writer are restored once this method returns.
*
* @throws JsonIOException if there was a problem writing to the writer
*/
public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOException {
Expand Down Expand Up @@ -868,6 +912,9 @@ public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOExce
* {@link #fromJson(String, Type)}. If you have the Json in a {@link Reader} instead of
* a String, use {@link #fromJson(Reader, Class)} instead.
*
* <p>An exception is thrown if the JSON string has multiple top-level JSON elements,
* or if there is trailing data.
*
* @param <T> the type of the desired object
* @param json the string from which the object is to be deserialized
* @param classOfT the class of T
Expand All @@ -887,6 +934,9 @@ public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException
* {@link #fromJson(String, Class)} instead. If you have the Json in a {@link Reader} instead of
* a String, use {@link #fromJson(Reader, Type)} instead.
*
* <p>An exception is thrown if the JSON string has multiple top-level JSON elements,
* or if there is trailing data.
*
* @param <T> the type of the desired object
* @param json the string from which the object is to be deserialized
* @param typeOfT The specific genericized type of src. You can obtain this type by using the
Expand Down Expand Up @@ -920,6 +970,9 @@ public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
* invoke {@link #fromJson(Reader, Type)}. If you have the Json in a String form instead of a
* {@link Reader}, use {@link #fromJson(String, Class)} instead.
*
* <p>An exception is thrown if the JSON data has multiple top-level JSON elements,
* or if there is trailing data.
*
* @param <T> the type of the desired object
* @param json the reader producing the Json from which the object is to be deserialized.
* @param classOfT the class of T
Expand All @@ -941,6 +994,9 @@ public <T> T fromJson(Reader json, Class<T> classOfT) throws JsonSyntaxException
* non-generic objects, use {@link #fromJson(Reader, Class)} instead. If you have the Json in a
* String form instead of a {@link Reader}, use {@link #fromJson(String, Type)} instead.
*
* <p>An exception is thrown if the JSON data has multiple top-level JSON elements,
* or if there is trailing data.
*
* @param <T> the type of the desired object
* @param json the reader producing Json from which the object is to be deserialized
* @param typeOfT The specific genericized type of src. You can obtain this type by using the
Expand All @@ -965,7 +1021,7 @@ public <T> T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyn
private static void assertFullConsumption(Object obj, JsonReader reader) {
try {
if (obj != null && reader.peek() != JsonToken.END_DOCUMENT) {
throw new JsonIOException("JSON document was not fully consumed.");
throw new JsonSyntaxException("JSON document was not fully consumed.");
}
} catch (MalformedJsonException e) {
throw new JsonSyntaxException(e);
Expand All @@ -977,7 +1033,14 @@ private static void assertFullConsumption(Object obj, JsonReader reader) {
/**
* Reads the next JSON value from {@code reader} and convert it to an object
* of type {@code typeOfT}. Returns {@code null}, if the {@code reader} is at EOF.
* Since Type is not parameterized by T, this method is type unsafe and should be used carefully
* Since Type is not parameterized by T, this method is type unsafe and should be used carefully.
*
* <p>Unlike the other {@code fromJson} methods, no exception is thrown if the JSON data has
* multiple top-level JSON elements, or if there is trailing data.
*
* <p>The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided reader. The lenient mode setting
* of the reader is restored once this method returns.
*
* @throws JsonIOException if there was a problem writing to the Reader
* @throws JsonSyntaxException if json is not a valid representation for an object of type
Expand Down
9 changes: 6 additions & 3 deletions gson/src/main/java/com/google/gson/GsonBuilder.java
Expand Up @@ -34,6 +34,7 @@
import com.google.gson.internal.sql.SqlTypesSupport;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import static com.google.gson.Gson.DEFAULT_COMPLEX_MAP_KEYS;
import static com.google.gson.Gson.DEFAULT_DATE_PATTERN;
Expand Down Expand Up @@ -425,12 +426,14 @@ public GsonBuilder setPrettyPrinting() {
}

/**
* By default, Gson is strict and only accepts JSON as specified by
* <a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. This option makes the parser
* liberal in what it accepts.
* Configures Gson to allow JSON data which does not strictly comply with the JSON specification.
*
* <p>Note: Due to legacy reasons most methods of Gson are always lenient, regardless of
* whether this builder method is used.
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @see JsonReader#setLenient(boolean)
* @see JsonWriter#setLenient(boolean)
*/
public GsonBuilder setLenient() {
lenient = true;
Expand Down
30 changes: 22 additions & 8 deletions gson/src/main/java/com/google/gson/JsonParser.java
Expand Up @@ -15,17 +15,16 @@
*/
package com.google.gson;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;

import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.MalformedJsonException;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;

/**
* A parser to parse Json into a parse tree of {@link JsonElement}s
* A parser to parse JSON into a parse tree of {@link JsonElement}s.
*
* @author Inderjeet Singh
* @author Joel Leitch
Expand All @@ -37,7 +36,11 @@ public final class JsonParser {
public JsonParser() {}

/**
* Parses the specified JSON string into a parse tree
* Parses the specified JSON string into a parse tree.
* An exception is thrown if the JSON string has multiple top-level JSON elements,
* or if there is trailing data.
*
* <p>The JSON string is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode}.
*
* @param json JSON text
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
Expand All @@ -48,11 +51,16 @@ public static JsonElement parseString(String json) throws JsonSyntaxException {
}

/**
* Parses the specified JSON string into a parse tree
* Parses the complete JSON string provided by the reader into a parse tree.
* An exception is thrown if the JSON string has multiple top-level JSON elements,
* or if there is trailing data.
*
* <p>The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode}.
*
* @param reader JSON text
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
* @throws JsonParseException if the specified text is not valid JSON
* @throws JsonParseException if there is an IOException or if the specified
* text is not valid JSON
*/
public static JsonElement parseReader(Reader reader) throws JsonIOException, JsonSyntaxException {
try {
Expand All @@ -73,6 +81,12 @@ public static JsonElement parseReader(Reader reader) throws JsonIOException, Jso

/**
* Returns the next value from the JSON stream as a parse tree.
* Unlike the other {@code parse} methods, no exception is thrown if the JSON data has
* multiple top-level JSON elements, or if there is trailing data.
*
* <p>The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided reader. The lenient mode setting
* of the reader is restored once this method returns.
*
* @throws JsonParseException if there is an IOException or if the specified
* text is not valid JSON
Expand Down
11 changes: 9 additions & 2 deletions gson/src/main/java/com/google/gson/TypeAdapter.java
Expand Up @@ -16,8 +16,8 @@

package com.google.gson;

import com.google.gson.internal.bind.JsonTreeWriter;
import com.google.gson.internal.bind.JsonTreeReader;
import com.google.gson.internal.bind.JsonTreeWriter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
Expand Down Expand Up @@ -252,6 +252,9 @@ public final JsonElement toJsonTree(T value) {
* read is strict. Create a {@link JsonReader#setLenient(boolean) lenient}
* {@code JsonReader} and call {@link #read(JsonReader)} for lenient reading.
*
* <p>No exception is thrown if the JSON data has multiple top-level JSON elements,
* or if there is trailing data.
*
* @return the converted Java object. May be null.
* @since 2.2
*/
Expand All @@ -266,6 +269,9 @@ public final T fromJson(Reader in) throws IOException {
* strict. Create a {@link JsonReader#setLenient(boolean) lenient} {@code
* JsonReader} and call {@link #read(JsonReader)} for lenient reading.
*
* <p>No exception is thrown if the JSON data has multiple top-level JSON elements,
* or if there is trailing data.
*
* @return the converted Java object. May be null.
* @since 2.2
*/
Expand All @@ -276,7 +282,8 @@ public final T fromJson(String json) throws IOException {
/**
* Converts {@code jsonTree} to a Java object.
*
* @param jsonTree the Java object to convert. May be {@link JsonNull}.
* @param jsonTree the JSON element to convert. May be {@link JsonNull}.
* @return the converted Java object. May be null.
* @since 2.2
*/
public final T fromJsonTree(JsonElement jsonTree) {
Expand Down
14 changes: 12 additions & 2 deletions gson/src/main/java/com/google/gson/stream/JsonReader.java
Expand Up @@ -304,8 +304,6 @@ public JsonReader(Reader in) {
* prefix</a>, <code>")]}'\n"</code>.
* <li>Streams that include multiple top-level values. With strict parsing,
* each stream must contain exactly one top-level value.
* <li>Top-level values of any type. With strict parsing, the top-level
* value must be an object or an array.
* <li>Numbers may be {@link Double#isNaN() NaNs} or {@link
* Double#isInfinite() infinities}.
* <li>End of line comments starting with {@code //} or {@code #} and
Expand All @@ -321,6 +319,18 @@ public JsonReader(Reader in) {
* {@code :}.
* <li>Name/value pairs separated by {@code ;} instead of {@code ,}.
* </ul>
*
* <p>Note: Even in strict mode there are slight derivations from the JSON
* specification:
* <ul>
* <li>JsonReader allows the literals {@code true}, {@code false} and {@code null}
* to have any capitalization, for example {@code fAlSe}
* <li>JsonReader supports the escape sequence {@code \'}, representing a {@code '}
* <li>JsonReader supports the escape sequence <code>\<i>LF</i></code> (with {@code LF}
* being the Unicode character U+000A), resulting in a {@code LF} within the
* read JSON string
* <li>JsonReader allows unescaped control characters (U+0000 through U+001F)
* </ul>
*/
public final void setLenient(boolean lenient) {
this.lenient = lenient;
Expand Down
52 changes: 52 additions & 0 deletions gson/src/test/java/com/google/gson/TypeAdapterTest.java
@@ -0,0 +1,52 @@
package com.google.gson;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.StringReader;
import org.junit.Test;

public class TypeAdapterTest {
@Test
public void testNullSafe() throws IOException {
TypeAdapter<String> adapter = new TypeAdapter<String>() {
@Override public void write(JsonWriter out, String value) {
throw new AssertionError("unexpected call");
}

@Override public String read(JsonReader in) {
throw new AssertionError("unexpected call");
}
}.nullSafe();

assertEquals("null", adapter.toJson(null));
assertNull(adapter.fromJson("null"));
}

private static final TypeAdapter<String> adapter = new TypeAdapter<String>() {
@Override public void write(JsonWriter out, String value) throws IOException {
out.value(value);
}

@Override public String read(JsonReader in) throws IOException {
return in.nextString();
}
};

// Note: This test just verifies the current behavior; it is a bit questionable
// whether that behavior is actually desired
@Test
public void testFromJson_Reader_TrailingData() throws IOException {
assertEquals("a", adapter.fromJson(new StringReader("\"a\"1")));
}

// Note: This test just verifies the current behavior; it is a bit questionable
// whether that behavior is actually desired
@Test
public void testFromJson_String_TrailingData() throws IOException {
assertEquals("a", adapter.fromJson("\"a\"1"));
}
}

0 comments on commit cbd2f84

Please sign in to comment.