Skip to content

Commit 2a2b640

Browse files
authored
Strictness follow-up (#2408)
* 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
1 parent d2795b9 commit 2a2b640

25 files changed

+394
-291
lines changed

Troubleshooting.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ For example, let's assume you want to deserialize the following JSON data:
127127
}
128128
```
129129

130-
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]`
130+
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]`
131131
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]`).
132132
The proper solution here is to fix the malformed JSON data.
133133

gson/src/main/java/com/google/gson/Gson.java

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,11 @@
107107
*
108108
* <h2 id="default-lenient">JSON Strictness handling</h2>
109109
* For legacy reasons most of the {@code Gson} methods allow JSON data which does not
110-
* comply with the JSON specification when the strictness is set to {@code null} (the default value).
111-
* To specify the {@linkplain Strictness strictness} of a {@code Gson} instance, you should set it through
112-
* {@link GsonBuilder#setStrictness(Strictness)}. If the strictness of a {@code Gson} instance is set to a not-null
113-
* value, the strictness will always be enforced.
110+
* comply with the JSON specification when no explicit {@linkplain Strictness strictness} is set (the default).
111+
* To specify the strictness of a {@code Gson} instance, you should set it through
112+
* {@link GsonBuilder#setStrictness(Strictness)}.
114113
*
115-
* <p>For older Gson versions which don't have the {@link Strictness} mode API the following
114+
* <p>For older Gson versions, which don't have the strictness mode API, the following
116115
* workarounds can be used:
117116
*
118117
* <h3>Serialization</h3>
@@ -145,6 +144,7 @@
145144
*/
146145
public final class Gson {
147146
static final boolean DEFAULT_JSON_NON_EXECUTABLE = false;
147+
// Strictness of `null` is the legacy mode where some Gson APIs are always lenient
148148
static final Strictness DEFAULT_STRICTNESS = null;
149149
static final FormattingStyle DEFAULT_FORMATTING_STYLE = FormattingStyle.COMPACT;
150150
static final boolean DEFAULT_ESCAPE_HTML = true;
@@ -236,7 +236,8 @@ public final class Gson {
236236
* <li>By default, Gson excludes <code>transient</code> or <code>static</code> fields from
237237
* consideration for serialization and deserialization. You can change this behavior through
238238
* {@link GsonBuilder#excludeFieldsWithModifiers(int...)}.</li>
239-
* <li>The strictness is set to {@code null}.</li>
239+
* <li>No explicit strictness is set. You can change this by calling
240+
* {@link GsonBuilder#setStrictness(Strictness)}.</li>
240241
* </ul>
241242
*/
242243
public Gson() {
@@ -808,7 +809,7 @@ public void toJson(Object src, Appendable writer) throws JsonIOException {
808809
* <pre>
809810
* Type typeOfSrc = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType();
810811
* </pre>
811-
* @param writer Writer to which the JSON representation of src needs to be written.
812+
* @param writer Writer to which the JSON representation of src needs to be written
812813
* @throws JsonIOException if there was a problem writing to the writer
813814
* @since 1.2
814815
*
@@ -828,19 +829,20 @@ public void toJson(Object src, Type typeOfSrc, Appendable writer) throws JsonIOE
828829
* Writes the JSON representation of {@code src} of type {@code typeOfSrc} to
829830
* {@code writer}.
830831
*
831-
<p> If the {@code Gson} instance has a not-null strictness setting, this setting will be used for reading the JSON
832-
* regardless of the {@linkplain JsonReader#getStrictness() strictness} of the provided {@link JsonReader}. For legacy
833-
* reasons, if the {@code Gson} instance has {@code null} as its strictness setting and the provided {@link JsonReader}
834-
* has a strictness of {@link Strictness#LEGACY_STRICT}, the JSON will be read in {@linkplain Strictness#LENIENT}
835-
* mode. Note that in both cases the old strictness value of the reader will be restored when this method returns.
832+
* <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
833+
* this setting will be used for writing the JSON regardless of the {@linkplain JsonWriter#getStrictness() strictness}
834+
* of the provided {@link JsonWriter}. For legacy reasons, if the {@code Gson} instance has no explicit strictness setting
835+
* and the writer does not have the strictness {@link Strictness#STRICT}, the JSON will be written in {@link Strictness#LENIENT}
836+
* mode.<br>
837+
* Note that in all cases the old strictness setting of the writer will be restored when this method returns.
836838
*
837839
* <p>The 'HTML-safe' and 'serialize {@code null}' settings of this {@code Gson} instance
838840
* (configured by the {@link GsonBuilder}) are applied, and the original settings of the
839841
* writer are restored once this method returns.
840842
*
841-
* @param src the object to be written.
842-
* @param typeOfSrc the type of the object to be written.
843-
* @param writer the {@link JsonWriter} writer to which the provided object will be written.
843+
* @param src the object for which JSON representation is to be created
844+
* @param typeOfSrc the type of the object to be written
845+
* @param writer Writer to which the JSON representation of src needs to be written
844846
*
845847
* @throws JsonIOException if there was a problem writing to the writer
846848
*/
@@ -851,7 +853,7 @@ public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOE
851853
Strictness oldStrictness = writer.getStrictness();
852854
if (this.strictness != null) {
853855
writer.setStrictness(this.strictness);
854-
} else if (writer.getStrictness() == Strictness.LEGACY_STRICT){
856+
} else if (writer.getStrictness() != Strictness.STRICT) {
855857
writer.setStrictness(Strictness.LENIENT);
856858
}
857859

@@ -911,9 +913,10 @@ public void toJson(JsonElement jsonElement, Appendable writer) throws JsonIOExce
911913
* <li>{@link GsonBuilder#disableHtmlEscaping()}</li>
912914
* <li>{@link GsonBuilder#generateNonExecutableJson()}</li>
913915
* <li>{@link GsonBuilder#serializeNulls()}</li>
914-
* <li>{@link GsonBuilder#setStrictness(Strictness)}. If the strictness of this {@code Gson} instance
915-
* is set to {@code null}, the created writer will have a strictness of {@link Strictness#LEGACY_STRICT}.
916-
* If the strictness is set to a non-null value, this strictness will be used for the created writer.</li>
916+
* <li>{@link GsonBuilder#setStrictness(Strictness)}. If no
917+
* {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness has been set} the created
918+
* writer will have a strictness of {@link Strictness#LEGACY_STRICT}. Otherwise, this strictness of
919+
* the {@code Gson} instance will be used for the created writer.</li>
917920
* <li>{@link GsonBuilder#setPrettyPrinting()}</li>
918921
* <li>{@link GsonBuilder#setFormattingStyle(FormattingStyle)}</li>
919922
* </ul>
@@ -935,9 +938,10 @@ public JsonWriter newJsonWriter(Writer writer) throws IOException {
935938
*
936939
* <p>The following settings are considered:
937940
* <ul>
938-
* <li>{@link GsonBuilder#setStrictness(Strictness)}. If the strictness of this {@code Gson} instance
939-
* is set to {@code null}, the created reader will have a strictness of {@link Strictness#LEGACY_STRICT}.
940-
* If the strictness is set to a non-null value, this strictness will be used for the created reader.</li>
941+
* <li>{@link GsonBuilder#setStrictness(Strictness)}. If no
942+
* {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness has been set} the created
943+
* reader will have a strictness of {@link Strictness#LEGACY_STRICT}. Otherwise, this strictness of
944+
* the {@code Gson} instance will be used for the created reader.</li>
941945
* </ul>
942946
*/
943947
public JsonReader newJsonReader(Reader reader) {
@@ -949,18 +953,19 @@ public JsonReader newJsonReader(Reader reader) {
949953
/**
950954
* Writes the JSON for {@code jsonElement} to {@code writer}.
951955
*
952-
* <p> If the {@code Gson} instance has a not-null strictness setting, this setting will be used for writing the JSON
953-
* regardless of the {@linkplain JsonWriter#getStrictness() strictness} of the provided {@link JsonWriter}. For legacy
954-
* reasons, if the {@code Gson} instance has {@code null} as its strictness setting and the provided {@link JsonWriter}
955-
* has a strictness of {@link Strictness#LEGACY_STRICT}, the JSON will be written in {@linkplain Strictness#LENIENT}
956-
* mode. Note that in both cases the old strictness value of the writer will be restored when this method returns.
956+
* <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
957+
* this setting will be used for writing the JSON regardless of the {@linkplain JsonWriter#getStrictness() strictness}
958+
* of the provided {@link JsonWriter}. For legacy reasons, if the {@code Gson} instance has no explicit strictness setting
959+
* and the writer does not have the strictness {@link Strictness#STRICT}, the JSON will be written in {@link Strictness#LENIENT}
960+
* mode.<br>
961+
* Note that in all cases the old strictness setting of the writer will be restored when this method returns.
957962
*
958963
* <p>The 'HTML-safe' and 'serialize {@code null}' settings of this {@code Gson} instance
959964
* (configured by the {@link GsonBuilder}) are applied, and the original settings of the
960965
* writer are restored once this method returns.
961966
*
962-
* @param jsonElement the JSON element to be written.
963-
* @param writer the JSON writer to which the provided element will be written.
967+
* @param jsonElement the JSON element to be written
968+
* @param writer the JSON writer to which the provided element will be written
964969
* @throws JsonIOException if there was a problem writing to the writer
965970
*/
966971
public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOException {
@@ -973,7 +978,7 @@ public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOExce
973978

974979
if (this.strictness != null) {
975980
writer.setStrictness(this.strictness);
976-
} else if (writer.getStrictness() == Strictness.LEGACY_STRICT) {
981+
} else if (writer.getStrictness() != Strictness.STRICT) {
977982
writer.setStrictness(Strictness.LENIENT);
978983
}
979984

@@ -1203,9 +1208,12 @@ private static void assertFullConsumption(Object obj, JsonReader reader) {
12031208
* <p>Unlike the other {@code fromJson} methods, no exception is thrown if the JSON data has
12041209
* multiple top-level JSON elements, or if there is trailing data.
12051210
*
1206-
* <p>The JSON data is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode},
1207-
* regardless of the strictness setting of the provided reader. The strictness setting
1208-
* of the reader is restored once this method returns.
1211+
* <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
1212+
* this setting will be used for reading the JSON regardless of the {@linkplain JsonReader#getStrictness() strictness}
1213+
* of the provided {@link JsonReader}. For legacy reasons, if the {@code Gson} instance has no explicit strictness setting
1214+
* and the reader does not have the strictness {@link Strictness#STRICT}, the JSON will be written in {@link Strictness#LENIENT}
1215+
* mode.<br>
1216+
* Note that in all cases the old strictness setting of the reader will be restored when this method returns.
12091217
*
12101218
* @param <T> the type of the desired object
12111219
* @param reader the reader whose next JSON value should be deserialized
@@ -1232,11 +1240,12 @@ public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, J
12321240
* <p>Unlike the other {@code fromJson} methods, no exception is thrown if the JSON data has
12331241
* multiple top-level JSON elements, or if there is trailing data.
12341242
*
1235-
* <p> If the {@code Gson} instance has a not-null strictness setting, this setting will be used for reading the JSON
1236-
* regardless of the {@linkplain JsonReader#getStrictness() strictness} of the provided {@link JsonReader}. For legacy
1237-
* reasons, if the {@code Gson} instance has {@code null} as its strictness setting and the provided {@link JsonReader}
1238-
* has a strictness of {@link Strictness#LEGACY_STRICT}, the JSON will be read in {@linkplain Strictness#LENIENT}
1239-
* mode. Note that in both cases the old strictness value of the reader will be restored when this method returns.
1243+
* <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
1244+
* this setting will be used for reading the JSON regardless of the {@linkplain JsonReader#getStrictness() strictness}
1245+
* of the provided {@link JsonReader}. For legacy reasons, if the {@code Gson} instance has no explicit strictness setting
1246+
* and the reader does not have the strictness {@link Strictness#STRICT}, the JSON will be written in {@link Strictness#LENIENT}
1247+
* mode.<br>
1248+
* Note that in all cases the old strictness setting of the reader will be restored when this method returns.
12401249
*
12411250
* @param <T> the type of the desired object
12421251
* @param reader the reader whose next JSON value should be deserialized
@@ -1260,7 +1269,7 @@ public <T> T fromJson(JsonReader reader, TypeToken<T> typeOfT) throws JsonIOExce
12601269

12611270
if (this.strictness != null) {
12621271
reader.setStrictness(this.strictness);
1263-
} else if (reader.getStrictness() == Strictness.LEGACY_STRICT){
1272+
} else if (reader.getStrictness() != Strictness.STRICT) {
12641273
reader.setStrictness(Strictness.LENIENT);
12651274
}
12661275

gson/src/main/java/com/google/gson/GsonBuilder.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import static com.google.gson.Gson.DEFAULT_USE_JDK_UNSAFE;
3030

3131
import com.google.errorprone.annotations.CanIgnoreReturnValue;
32+
import com.google.errorprone.annotations.InlineMe;
3233
import com.google.gson.annotations.Since;
3334
import com.google.gson.annotations.Until;
3435
import com.google.gson.internal.$Gson$Preconditions;
@@ -51,7 +52,6 @@
5152
import java.util.Map;
5253
import java.util.Objects;
5354

54-
5555
/**
5656
* <p>Use this builder to construct a {@link Gson} instance when you need to set configuration
5757
* options other than the default. For {@link Gson} with default configuration, it is simpler to
@@ -73,12 +73,16 @@
7373
* .create();
7474
* </pre>
7575
*
76-
* <p>NOTES:
76+
* <p>Notes:
7777
* <ul>
78-
* <li> the order of invocation of configuration methods does not matter.</li>
79-
* <li> The default serialization of {@link Date} and its subclasses in Gson does
78+
* <li>The order of invocation of configuration methods does not matter.</li>
79+
* <li>The default serialization of {@link Date} and its subclasses in Gson does
8080
* not contain time-zone information. So, if you are using date/time instances,
8181
* use {@code GsonBuilder} and its {@code setDateFormat} methods.</li>
82+
* <li>By default no explicit {@link Strictness} is set; some of the {@link Gson} methods
83+
* behave as if {@link Strictness#LEGACY_STRICT} was used whereas others behave as
84+
* if {@link Strictness#LENIENT} was used. Prefer explicitly setting a strictness
85+
* with {@link #setStrictness(Strictness)} to avoid this legacy behavior.
8286
* </ul>
8387
*
8488
* @author Inderjeet Singh
@@ -525,18 +529,19 @@ public GsonBuilder setFormattingStyle(FormattingStyle formattingStyle) {
525529
/**
526530
* Sets the strictness of this builder to {@link Strictness#LENIENT}.
527531
*
528-
* <p>This method has been deprecated. Please use {@link GsonBuilder#setStrictness(Strictness)} instead.
529-
* Calling this method is equivalent to {@code setStrictness(Strictness.LENIENT)}</p>
532+
* @deprecated This method is equivalent to calling {@link #setStrictness(Strictness)} with
533+
* {@link Strictness#LENIENT}: {@code setStrictness(Strictness.LENIENT)}
530534
*
531535
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern.
532536
* @see JsonReader#setStrictness(Strictness)
533537
* @see JsonWriter#setStrictness(Strictness)
534538
* @see #setStrictness(Strictness)
535539
*/
540+
@Deprecated
541+
@InlineMe(replacement = "this.setStrictness(Strictness.LENIENT)", imports = "com.google.gson.Strictness")
536542
@CanIgnoreReturnValue
537543
public GsonBuilder setLenient() {
538-
strictness = Strictness.LENIENT;
539-
return this;
544+
return setStrictness(Strictness.LENIENT);
540545
}
541546

542547
/**
@@ -549,10 +554,11 @@ public GsonBuilder setLenient() {
549554
*
550555
* @param strictness the new strictness mode. May not be {@code null}.
551556
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern.
552-
* @see JsonWriter#setStrictness(Strictness)
553557
* @see JsonReader#setStrictness(Strictness)
558+
* @see JsonWriter#setStrictness(Strictness)
554559
* @since $next-version$
555560
*/
561+
@CanIgnoreReturnValue
556562
public GsonBuilder setStrictness(Strictness strictness) {
557563
this.strictness = Objects.requireNonNull(strictness);
558564
return this;

gson/src/main/java/com/google/gson/JsonElement.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,8 @@ public String toString() {
321321
try {
322322
StringWriter stringWriter = new StringWriter();
323323
JsonWriter jsonWriter = new JsonWriter(stringWriter);
324-
jsonWriter.setLenient(true);
324+
// Make writer lenient because toString() must not fail, even if for example JsonPrimitive contains NaN
325+
jsonWriter.setStrictness(Strictness.LENIENT);
325326
Streams.write(this, jsonWriter);
326327
return stringWriter.toString();
327328
} catch (IOException e) {

gson/src/main/java/com/google/gson/JsonParser.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public JsonParser() {}
4141
* An exception is thrown if the JSON string has multiple top-level JSON elements,
4242
* or if there is trailing data.
4343
*
44-
* <p>The JSON string is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode}.
44+
* <p>The JSON string is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode}.
4545
*
4646
* @param json JSON text
4747
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
@@ -57,7 +57,7 @@ public static JsonElement parseString(String json) throws JsonSyntaxException {
5757
* An exception is thrown if the JSON string has multiple top-level JSON elements,
5858
* or if there is trailing data.
5959
*
60-
* <p>The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode}.
60+
* <p>The JSON data is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode}.
6161
*
6262
* @param reader JSON text
6363
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
@@ -87,8 +87,8 @@ public static JsonElement parseReader(Reader reader) throws JsonIOException, Jso
8787
* Unlike the other {@code parse} methods, no exception is thrown if the JSON data has
8888
* multiple top-level JSON elements, or if there is trailing data.
8989
*
90-
* <p>The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode},
91-
* regardless of the lenient mode setting of the provided reader. The lenient mode setting
90+
* <p>The JSON data is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode},
91+
* regardless of the strictness setting of the provided reader. The strictness setting
9292
* of the reader is restored once this method returns.
9393
*
9494
* @throws JsonParseException if there is an IOException or if the specified
@@ -97,16 +97,16 @@ public static JsonElement parseReader(Reader reader) throws JsonIOException, Jso
9797
*/
9898
public static JsonElement parseReader(JsonReader reader)
9999
throws JsonIOException, JsonSyntaxException {
100-
boolean lenient = reader.isLenient();
101-
reader.setLenient(true);
100+
Strictness strictness = reader.getStrictness();
101+
reader.setStrictness(Strictness.LENIENT);
102102
try {
103103
return Streams.parse(reader);
104104
} catch (StackOverflowError e) {
105105
throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e);
106106
} catch (OutOfMemoryError e) {
107107
throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e);
108108
} finally {
109-
reader.setLenient(lenient);
109+
reader.setStrictness(strictness);
110110
}
111111
}
112112

0 commit comments

Comments
 (0)