Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strictness follow-up #2408

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ For example, let's assume you want to deserialize the following JSON data:
}
```

This will fail with an exception similar to this one: `MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 5 column 4 path $.languages[2]`
This will fail with an exception similar to this one: `MalformedJsonException: Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON at line 5 column 4 path $.languages[2]`
The problem here is the trailing comma (`,`) after `"French"`, trailing commas are not allowed by the JSON specification. The location information "line 5 column 4" points to the `]` in the JSON data (with some slight inaccuracies) because Gson expected another value after `,` instead of the closing `]`. The JSONPath `$.languages[2]` in the exception message also points there: `$.` refers to the root object, `languages` refers to its member of that name and `[2]` refers to the (missing) third value in the JSON array value of that member (numbering starts at 0, so it is `[2]` instead of `[3]`).
The proper solution here is to fix the malformed JSON data.

Expand Down
87 changes: 48 additions & 39 deletions gson/src/main/java/com/google/gson/Gson.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,11 @@
*
* <h2 id="default-lenient">JSON Strictness handling</h2>
* For legacy reasons most of the {@code Gson} methods allow JSON data which does not
* comply with the JSON specification when the strictness is set to {@code null} (the default value).
* To specify the {@linkplain Strictness strictness} of a {@code Gson} instance, you should set it through
* {@link GsonBuilder#setStrictness(Strictness)}. If the strictness of a {@code Gson} instance is set to a not-null
* value, the strictness will always be enforced.
* comply with the JSON specification when no explicit {@linkplain Strictness strictness} is set (the default).
* To specify the strictness of a {@code Gson} instance, you should set it through
* {@link GsonBuilder#setStrictness(Strictness)}.
*
* <p>For older Gson versions which don't have the {@link Strictness} mode API the following
* <p>For older Gson versions, which don't have the strictness mode API, the following
* workarounds can be used:
*
* <h3>Serialization</h3>
Expand Down Expand Up @@ -145,6 +144,7 @@
*/
public final class Gson {
static final boolean DEFAULT_JSON_NON_EXECUTABLE = false;
// Strictness of `null` is the legacy mode where some Gson APIs are always lenient
static final Strictness DEFAULT_STRICTNESS = null;
static final FormattingStyle DEFAULT_FORMATTING_STYLE = FormattingStyle.COMPACT;
static final boolean DEFAULT_ESCAPE_HTML = true;
Expand Down Expand Up @@ -236,7 +236,8 @@ public final class Gson {
* <li>By default, Gson excludes <code>transient</code> or <code>static</code> fields from
* consideration for serialization and deserialization. You can change this behavior through
* {@link GsonBuilder#excludeFieldsWithModifiers(int...)}.</li>
* <li>The strictness is set to {@code null}.</li>
* <li>No explicit strictness is set. You can change this by calling
* {@link GsonBuilder#setStrictness(Strictness)}.</li>
* </ul>
*/
public Gson() {
Expand Down Expand Up @@ -808,7 +809,7 @@ public void toJson(Object src, Appendable writer) throws JsonIOException {
* <pre>
* Type typeOfSrc = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType();
* </pre>
* @param writer Writer to which the JSON representation of src needs to be written.
* @param writer Writer to which the JSON representation of src needs to be written
* @throws JsonIOException if there was a problem writing to the writer
* @since 1.2
*
Expand All @@ -828,19 +829,20 @@ 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> If the {@code Gson} instance has a not-null strictness setting, this setting will be used for reading the JSON
* regardless of the {@linkplain JsonReader#getStrictness() strictness} of the provided {@link JsonReader}. For legacy
* reasons, if the {@code Gson} instance has {@code null} as its strictness setting and the provided {@link JsonReader}
* has a strictness of {@link Strictness#LEGACY_STRICT}, the JSON will be read in {@linkplain Strictness#LENIENT}
* mode. Note that in both cases the old strictness value of the reader will be restored when this method returns.
* <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
* this setting will be used for writing the JSON regardless of the {@linkplain JsonWriter#getStrictness() strictness}
* of the provided {@link JsonWriter}. For legacy reasons, if the {@code Gson} instance has no explicit strictness setting
* and the writer does not have the strictness {@link Strictness#STRICT}, the JSON will be written in {@link Strictness#LENIENT}
* mode.<br>
* Note that in all cases the old strictness setting of the writer will be restored when 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.
*
* @param src the object to be written.
* @param typeOfSrc the type of the object to be written.
* @param writer the {@link JsonWriter} writer to which the provided object will be written.
* @param src the object for which JSON representation is to be created
* @param typeOfSrc the type of the object to be written
* @param writer Writer to which the JSON representation of src needs to be written
*
* @throws JsonIOException if there was a problem writing to the writer
*/
Expand All @@ -851,7 +853,7 @@ public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOE
Strictness oldStrictness = writer.getStrictness();
if (this.strictness != null) {
writer.setStrictness(this.strictness);
} else if (writer.getStrictness() == Strictness.LEGACY_STRICT){
} else if (writer.getStrictness() != Strictness.STRICT) {
writer.setStrictness(Strictness.LENIENT);
}

Expand Down Expand Up @@ -911,9 +913,10 @@ public void toJson(JsonElement jsonElement, Appendable writer) throws JsonIOExce
* <li>{@link GsonBuilder#disableHtmlEscaping()}</li>
* <li>{@link GsonBuilder#generateNonExecutableJson()}</li>
* <li>{@link GsonBuilder#serializeNulls()}</li>
* <li>{@link GsonBuilder#setStrictness(Strictness)}. If the strictness of this {@code Gson} instance
* is set to {@code null}, the created writer will have a strictness of {@link Strictness#LEGACY_STRICT}.
* If the strictness is set to a non-null value, this strictness will be used for the created writer.</li>
* <li>{@link GsonBuilder#setStrictness(Strictness)}. If no
* {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness has been set} the created
* writer will have a strictness of {@link Strictness#LEGACY_STRICT}. Otherwise, this strictness of
* the {@code Gson} instance will be used for the created writer.</li>
* <li>{@link GsonBuilder#setPrettyPrinting()}</li>
* <li>{@link GsonBuilder#setFormattingStyle(FormattingStyle)}</li>
* </ul>
Expand All @@ -935,9 +938,10 @@ public JsonWriter newJsonWriter(Writer writer) throws IOException {
*
* <p>The following settings are considered:
* <ul>
* <li>{@link GsonBuilder#setStrictness(Strictness)}. If the strictness of this {@code Gson} instance
* is set to {@code null}, the created reader will have a strictness of {@link Strictness#LEGACY_STRICT}.
* If the strictness is set to a non-null value, this strictness will be used for the created reader.</li>
* <li>{@link GsonBuilder#setStrictness(Strictness)}. If no
* {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness has been set} the created
* reader will have a strictness of {@link Strictness#LEGACY_STRICT}. Otherwise, this strictness of
* the {@code Gson} instance will be used for the created reader.</li>
* </ul>
*/
public JsonReader newJsonReader(Reader reader) {
Expand All @@ -949,18 +953,19 @@ public JsonReader newJsonReader(Reader reader) {
/**
* Writes the JSON for {@code jsonElement} to {@code writer}.
*
* <p> If the {@code Gson} instance has a not-null strictness setting, this setting will be used for writing the JSON
* regardless of the {@linkplain JsonWriter#getStrictness() strictness} of the provided {@link JsonWriter}. For legacy
* reasons, if the {@code Gson} instance has {@code null} as its strictness setting and the provided {@link JsonWriter}
* has a strictness of {@link Strictness#LEGACY_STRICT}, the JSON will be written in {@linkplain Strictness#LENIENT}
* mode. Note that in both cases the old strictness value of the writer will be restored when this method returns.
* <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
* this setting will be used for writing the JSON regardless of the {@linkplain JsonWriter#getStrictness() strictness}
* of the provided {@link JsonWriter}. For legacy reasons, if the {@code Gson} instance has no explicit strictness setting
* and the writer does not have the strictness {@link Strictness#STRICT}, the JSON will be written in {@link Strictness#LENIENT}
* mode.<br>
* Note that in all cases the old strictness setting of the writer will be restored when 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.
*
* @param jsonElement the JSON element to be written.
* @param writer the JSON writer to which the provided element will be written.
* @param jsonElement the JSON element to be written
* @param writer the JSON writer to which the provided element will be written
* @throws JsonIOException if there was a problem writing to the writer
*/
public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOException {
Expand All @@ -973,7 +978,7 @@ public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOExce

if (this.strictness != null) {
writer.setStrictness(this.strictness);
} else if (writer.getStrictness() == Strictness.LEGACY_STRICT) {
} else if (writer.getStrictness() != Strictness.STRICT) {
writer.setStrictness(Strictness.LENIENT);
}

Expand Down Expand Up @@ -1203,9 +1208,12 @@ private static void assertFullConsumption(Object obj, JsonReader reader) {
* <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#setStrictness(Strictness) lenient mode},
* regardless of the strictness setting of the provided reader. The strictness setting
* of the reader is restored once this method returns.
* <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
* this setting will be used for reading the JSON regardless of the {@linkplain JsonReader#getStrictness() strictness}
* of the provided {@link JsonReader}. For legacy reasons, if the {@code Gson} instance has no explicit strictness setting
* and the reader does not have the strictness {@link Strictness#STRICT}, the JSON will be written in {@link Strictness#LENIENT}
* mode.<br>
* Note that in all cases the old strictness setting of the reader will be restored when this method returns.
*
* @param <T> the type of the desired object
* @param reader the reader whose next JSON value should be deserialized
Expand All @@ -1232,11 +1240,12 @@ public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, J
* <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> If the {@code Gson} instance has a not-null strictness setting, this setting will be used for reading the JSON
* regardless of the {@linkplain JsonReader#getStrictness() strictness} of the provided {@link JsonReader}. For legacy
* reasons, if the {@code Gson} instance has {@code null} as its strictness setting and the provided {@link JsonReader}
* has a strictness of {@link Strictness#LEGACY_STRICT}, the JSON will be read in {@linkplain Strictness#LENIENT}
* mode. Note that in both cases the old strictness value of the reader will be restored when this method returns.
* <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
* this setting will be used for reading the JSON regardless of the {@linkplain JsonReader#getStrictness() strictness}
* of the provided {@link JsonReader}. For legacy reasons, if the {@code Gson} instance has no explicit strictness setting
* and the reader does not have the strictness {@link Strictness#STRICT}, the JSON will be written in {@link Strictness#LENIENT}
* mode.<br>
* Note that in all cases the old strictness setting of the reader will be restored when this method returns.
*
* @param <T> the type of the desired object
* @param reader the reader whose next JSON value should be deserialized
Expand All @@ -1260,7 +1269,7 @@ public <T> T fromJson(JsonReader reader, TypeToken<T> typeOfT) throws JsonIOExce

if (this.strictness != null) {
reader.setStrictness(this.strictness);
} else if (reader.getStrictness() == Strictness.LEGACY_STRICT){
} else if (reader.getStrictness() != Strictness.STRICT) {
reader.setStrictness(Strictness.LENIENT);
}

Expand Down
24 changes: 15 additions & 9 deletions gson/src/main/java/com/google/gson/GsonBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import static com.google.gson.Gson.DEFAULT_USE_JDK_UNSAFE;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.InlineMe;
import com.google.gson.annotations.Since;
import com.google.gson.annotations.Until;
import com.google.gson.internal.$Gson$Preconditions;
Expand All @@ -51,7 +52,6 @@
import java.util.Map;
import java.util.Objects;


/**
* <p>Use this builder to construct a {@link Gson} instance when you need to set configuration
* options other than the default. For {@link Gson} with default configuration, it is simpler to
Expand All @@ -73,12 +73,16 @@
* .create();
* </pre>
*
* <p>NOTES:
* <p>Notes:
* <ul>
* <li> the order of invocation of configuration methods does not matter.</li>
* <li> The default serialization of {@link Date} and its subclasses in Gson does
* <li>The order of invocation of configuration methods does not matter.</li>
* <li>The default serialization of {@link Date} and its subclasses in Gson does
* not contain time-zone information. So, if you are using date/time instances,
* use {@code GsonBuilder} and its {@code setDateFormat} methods.</li>
* <li>By default no explicit {@link Strictness} is set; some of the {@link Gson} methods
* behave as if {@link Strictness#LEGACY_STRICT} was used whereas others behave as
* if {@link Strictness#LENIENT} was used. Prefer explicitly setting a strictness
* with {@link #setStrictness(Strictness)} to avoid this legacy behavior.
* </ul>
*
* @author Inderjeet Singh
Expand Down Expand Up @@ -525,18 +529,19 @@ public GsonBuilder setFormattingStyle(FormattingStyle formattingStyle) {
/**
* Sets the strictness of this builder to {@link Strictness#LENIENT}.
*
* <p>This method has been deprecated. Please use {@link GsonBuilder#setStrictness(Strictness)} instead.
* Calling this method is equivalent to {@code setStrictness(Strictness.LENIENT)}</p>
* @deprecated This method is equivalent to calling {@link #setStrictness(Strictness)} with
* {@link Strictness#LENIENT}: {@code setStrictness(Strictness.LENIENT)}
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern.
* @see JsonReader#setStrictness(Strictness)
* @see JsonWriter#setStrictness(Strictness)
* @see #setStrictness(Strictness)
*/
@Deprecated
@InlineMe(replacement = "this.setStrictness(Strictness.LENIENT)", imports = "com.google.gson.Strictness")
@CanIgnoreReturnValue
public GsonBuilder setLenient() {
strictness = Strictness.LENIENT;
return this;
return setStrictness(Strictness.LENIENT);
}

/**
Expand All @@ -549,10 +554,11 @@ public GsonBuilder setLenient() {
*
* @param strictness the new strictness mode. May not be {@code null}.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern.
* @see JsonWriter#setStrictness(Strictness)
* @see JsonReader#setStrictness(Strictness)
* @see JsonWriter#setStrictness(Strictness)
* @since $next-version$
*/
@CanIgnoreReturnValue
public GsonBuilder setStrictness(Strictness strictness) {
this.strictness = Objects.requireNonNull(strictness);
return this;
Expand Down
3 changes: 2 additions & 1 deletion gson/src/main/java/com/google/gson/JsonElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,8 @@ public String toString() {
try {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setLenient(true);
// Make writer lenient because toString() must not fail, even if for example JsonPrimitive contains NaN
jsonWriter.setStrictness(Strictness.LENIENT);
Streams.write(this, jsonWriter);
return stringWriter.toString();
} catch (IOException e) {
Expand Down
14 changes: 7 additions & 7 deletions gson/src/main/java/com/google/gson/JsonParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public JsonParser() {}
* 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}.
* <p>The JSON string is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode}.
*
* @param json JSON text
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
Expand All @@ -57,7 +57,7 @@ public static JsonElement parseString(String json) throws JsonSyntaxException {
* 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}.
* <p>The JSON data is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode}.
*
* @param reader JSON text
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
Expand Down Expand Up @@ -87,8 +87,8 @@ public static JsonElement parseReader(Reader reader) throws JsonIOException, Jso
* 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
* <p>The JSON data is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode},
* regardless of the strictness setting of the provided reader. The strictness setting
* of the reader is restored once this method returns.
*
* @throws JsonParseException if there is an IOException or if the specified
Expand All @@ -97,16 +97,16 @@ public static JsonElement parseReader(Reader reader) throws JsonIOException, Jso
*/
public static JsonElement parseReader(JsonReader reader)
throws JsonIOException, JsonSyntaxException {
boolean lenient = reader.isLenient();
reader.setLenient(true);
Strictness strictness = reader.getStrictness();
reader.setStrictness(Strictness.LENIENT);
try {
return Streams.parse(reader);
} catch (StackOverflowError e) {
throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e);
} catch (OutOfMemoryError e) {
throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e);
} finally {
reader.setLenient(lenient);
reader.setStrictness(strictness);
}
}

Expand Down
Loading