diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index 62d5ca867d..666e5f8bd3 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -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; @@ -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 @@ -97,6 +96,33 @@ *

See the Gson User Guide * for a more complete set of examples.

* + *

Lenient JSON handling

+ * 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: + * + *

Serialization

+ *
    + *
  1. Use {@link #getAdapter(Class)} to obtain the adapter for the type to be serialized + *
  2. When using an existing {@code JsonWriter}, manually apply the writer settings of this + * {@code Gson} instance listed by {@link #newJsonWriter(Writer)}.
    + * Otherwise, when not using an existing {@code JsonWriter}, use {@link #newJsonWriter(Writer)} + * to construct one. + *
  3. Call {@link TypeAdapter#write(JsonWriter, Object)} + *
+ * + *

Deserialization

+ *
    + *
  1. Use {@link #getAdapter(Class)} to obtain the adapter for the type to be deserialized + *
  2. When using an existing {@code JsonReader}, manually apply the reader settings of this + * {@code Gson} instance listed by {@link #newJsonReader(Reader)}.
    + * Otherwise, when not using an existing {@code JsonReader}, use {@link #newJsonReader(Reader)} + * to construct one. + *
  3. Call {@link TypeAdapter#read(JsonReader)} + *
  4. Call {@link JsonReader#peek()} and verify that the result is {@link JsonToken#END_DOCUMENT} + * to make sure there is no trailing data + *
+ * * @see com.google.gson.reflect.TypeToken * * @author Inderjeet Singh @@ -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}. + * + *

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. + * + *

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") @@ -834,6 +869,15 @@ public JsonReader newJsonReader(Reader reader) { /** * Writes the JSON for {@code jsonElement} to {@code writer}. + * + *

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. + * + *

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 { @@ -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. * + *

An exception is thrown if the JSON string has multiple top-level JSON elements, + * or if there is trailing data. + * * @param the type of the desired object * @param json the string from which the object is to be deserialized * @param classOfT the class of T @@ -887,6 +934,9 @@ public T fromJson(String json, Class 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. * + *

An exception is thrown if the JSON string has multiple top-level JSON elements, + * or if there is trailing data. + * * @param 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 @@ -920,6 +970,9 @@ public 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. * + *

An exception is thrown if the JSON data has multiple top-level JSON elements, + * or if there is trailing data. + * * @param 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 @@ -941,6 +994,9 @@ public T fromJson(Reader json, Class 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. * + *

An exception is thrown if the JSON data has multiple top-level JSON elements, + * or if there is trailing data. + * * @param 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 @@ -965,7 +1021,7 @@ public 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); @@ -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. + * + *

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. + * + *

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 diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java index 5e77ac0cb1..4c540ac1c2 100644 --- a/gson/src/main/java/com/google/gson/GsonBuilder.java +++ b/gson/src/main/java/com/google/gson/GsonBuilder.java @@ -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; @@ -425,12 +426,14 @@ public GsonBuilder setPrettyPrinting() { } /** - * By default, Gson is strict and only accepts JSON as specified by - * RFC 4627. 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. + * + *

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; diff --git a/gson/src/main/java/com/google/gson/JsonParser.java b/gson/src/main/java/com/google/gson/JsonParser.java index 5121e4e10a..d3508c1073 100644 --- a/gson/src/main/java/com/google/gson/JsonParser.java +++ b/gson/src/main/java/com/google/gson/JsonParser.java @@ -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 @@ -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. + * + *

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 @@ -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. + * + *

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 { @@ -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. + * + *

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 diff --git a/gson/src/main/java/com/google/gson/TypeAdapter.java b/gson/src/main/java/com/google/gson/TypeAdapter.java index 4646d271d7..ba798537be 100644 --- a/gson/src/main/java/com/google/gson/TypeAdapter.java +++ b/gson/src/main/java/com/google/gson/TypeAdapter.java @@ -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; @@ -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. * + *

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 */ @@ -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. * + *

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 */ @@ -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) { diff --git a/gson/src/main/java/com/google/gson/stream/JsonReader.java b/gson/src/main/java/com/google/gson/stream/JsonReader.java index 2984d19c19..6cb820bef7 100644 --- a/gson/src/main/java/com/google/gson/stream/JsonReader.java +++ b/gson/src/main/java/com/google/gson/stream/JsonReader.java @@ -304,8 +304,6 @@ public JsonReader(Reader in) { * prefix, ")]}'\n". *

  • Streams that include multiple top-level values. With strict parsing, * each stream must contain exactly one top-level value. - *
  • Top-level values of any type. With strict parsing, the top-level - * value must be an object or an array. *
  • Numbers may be {@link Double#isNaN() NaNs} or {@link * Double#isInfinite() infinities}. *
  • End of line comments starting with {@code //} or {@code #} and @@ -321,6 +319,18 @@ public JsonReader(Reader in) { * {@code :}. *
  • Name/value pairs separated by {@code ;} instead of {@code ,}. * + * + *

    Note: Even in strict mode there are slight derivations from the JSON + * specification: + *

    */ public final void setLenient(boolean lenient) { this.lenient = lenient; diff --git a/gson/src/test/java/com/google/gson/TypeAdapterTest.java b/gson/src/test/java/com/google/gson/TypeAdapterTest.java new file mode 100644 index 0000000000..ab44637393 --- /dev/null +++ b/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 adapter = new TypeAdapter() { + @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 adapter = new TypeAdapter() { + @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")); + } +}