Skip to content

Commit

Permalink
Strictness follow-up (#2408)
Browse files Browse the repository at this point in the history
* Strictness mode follow-up

- Remove mentions of `null` Gson strictness; this is an implementation detail
- Fix incorrect / outdated documentation
- Reduce links to RFC; if there is already a link to it in a previous sentence
  don't link to it again
- Extend and update tests
- Minor punctuation changes in documentation for consistency

* Deprecate `setLenient` methods
  • Loading branch information
Marcono1234 committed Jun 27, 2023
1 parent d2795b9 commit 2a2b640
Show file tree
Hide file tree
Showing 25 changed files with 394 additions and 291 deletions.
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

0 comments on commit 2a2b640

Please sign in to comment.