builderFactories,
@@ -265,7 +271,7 @@ public Gson() {
this.generateNonExecutableJson = generateNonExecutableGson;
this.htmlSafe = htmlSafe;
this.formattingStyle = formattingStyle;
- this.lenient = lenient;
+ this.strictness = strictness;
this.serializeSpecialFloatingPointValues = serializeSpecialFloatingPointValues;
this.useJdkUnsafe = useJdkUnsafe;
this.longSerializationPolicy = longSerializationPolicy;
@@ -822,24 +828,37 @@ 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.
+
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.
*
*
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.
+ *
* @throws JsonIOException if there was a problem writing to the writer
*/
public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOException {
@SuppressWarnings("unchecked")
TypeAdapter adapter = (TypeAdapter) getAdapter(TypeToken.get(typeOfSrc));
- boolean oldLenient = writer.isLenient();
- writer.setLenient(true);
+
+ Strictness oldStrictness = writer.getStrictness();
+ if (this.strictness != null) {
+ writer.setStrictness(this.strictness);
+ } else if (writer.getStrictness() == Strictness.LEGACY_STRICT){
+ writer.setStrictness(Strictness.LENIENT);
+ }
+
boolean oldHtmlSafe = writer.isHtmlSafe();
- writer.setHtmlSafe(htmlSafe);
boolean oldSerializeNulls = writer.getSerializeNulls();
+
+ writer.setHtmlSafe(htmlSafe);
writer.setSerializeNulls(serializeNulls);
try {
adapter.write(writer, src);
@@ -848,7 +867,7 @@ public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOE
} catch (AssertionError e) {
throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);
} finally {
- writer.setLenient(oldLenient);
+ writer.setStrictness(oldStrictness);
writer.setHtmlSafe(oldHtmlSafe);
writer.setSerializeNulls(oldSerializeNulls);
}
@@ -892,7 +911,9 @@ public void toJson(JsonElement jsonElement, Appendable writer) throws JsonIOExce
* {@link GsonBuilder#disableHtmlEscaping()}
* {@link GsonBuilder#generateNonExecutableJson()}
* {@link GsonBuilder#serializeNulls()}
- * {@link GsonBuilder#setLenient()}
+ * {@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.
* {@link GsonBuilder#setPrettyPrinting()}
* {@link GsonBuilder#setPrettyPrinting(FormattingStyle)}
*
@@ -904,7 +925,7 @@ public JsonWriter newJsonWriter(Writer writer) throws IOException {
JsonWriter jsonWriter = new JsonWriter(writer);
jsonWriter.setFormattingStyle(formattingStyle);
jsonWriter.setHtmlSafe(htmlSafe);
- jsonWriter.setLenient(lenient);
+ jsonWriter.setStrictness(strictness == null ? Strictness.LEGACY_STRICT : strictness);
jsonWriter.setSerializeNulls(serializeNulls);
return jsonWriter;
}
@@ -914,35 +935,48 @@ public JsonWriter newJsonWriter(Writer writer) throws IOException {
*
* The following settings are considered:
*
- * {@link GsonBuilder#setLenient()}
+ * {@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.
*
*/
public JsonReader newJsonReader(Reader reader) {
JsonReader jsonReader = new JsonReader(reader);
- jsonReader.setLenient(lenient);
+ jsonReader.setStrictness(strictness == null ? Strictness.LEGACY_STRICT : strictness);
return jsonReader;
}
/**
* 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.
+ *
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.
*
*
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.
* @throws JsonIOException if there was a problem writing to the writer
*/
public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOException {
- boolean oldLenient = writer.isLenient();
- writer.setLenient(true);
+ Strictness oldStrictness = writer.getStrictness();
boolean oldHtmlSafe = writer.isHtmlSafe();
- writer.setHtmlSafe(htmlSafe);
boolean oldSerializeNulls = writer.getSerializeNulls();
+
+ writer.setHtmlSafe(htmlSafe);
writer.setSerializeNulls(serializeNulls);
+
+ if (this.strictness != null) {
+ writer.setStrictness(this.strictness);
+ } else if (writer.getStrictness() == Strictness.LEGACY_STRICT) {
+ writer.setStrictness(Strictness.LENIENT);
+ }
+
try {
Streams.write(jsonElement, writer);
} catch (IOException e) {
@@ -950,7 +984,7 @@ public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOExce
} catch (AssertionError e) {
throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);
} finally {
- writer.setLenient(oldLenient);
+ writer.setStrictness(oldStrictness);
writer.setHtmlSafe(oldHtmlSafe);
writer.setSerializeNulls(oldSerializeNulls);
}
@@ -1169,8 +1203,8 @@ private static void assertFullConsumption(Object obj, JsonReader reader) {
*
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
+ *
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.
*
* @param the type of the desired object
@@ -1198,9 +1232,11 @@ public T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, J
* 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.
+ *
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.
*
* @param the type of the desired object
* @param reader the reader whose next JSON value should be deserialized
@@ -1220,8 +1256,14 @@ public T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, J
*/
public T fromJson(JsonReader reader, TypeToken typeOfT) throws JsonIOException, JsonSyntaxException {
boolean isEmpty = true;
- boolean oldLenient = reader.isLenient();
- reader.setLenient(true);
+ Strictness oldStrictness = reader.getStrictness();
+
+ if (this.strictness != null) {
+ reader.setStrictness(this.strictness);
+ } else if (reader.getStrictness() == Strictness.LEGACY_STRICT){
+ reader.setStrictness(Strictness.LENIENT);
+ }
+
try {
reader.peek();
isEmpty = false;
@@ -1244,7 +1286,7 @@ public T fromJson(JsonReader reader, TypeToken typeOfT) throws JsonIOExce
} catch (AssertionError e) {
throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);
} finally {
- reader.setLenient(oldLenient);
+ reader.setStrictness(oldStrictness);
}
}
diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java
index 0afc2337c1..7540cda5aa 100644
--- a/gson/src/main/java/com/google/gson/GsonBuilder.java
+++ b/gson/src/main/java/com/google/gson/GsonBuilder.java
@@ -21,11 +21,11 @@
import static com.google.gson.Gson.DEFAULT_ESCAPE_HTML;
import static com.google.gson.Gson.DEFAULT_FORMATTING_STYLE;
import static com.google.gson.Gson.DEFAULT_JSON_NON_EXECUTABLE;
-import static com.google.gson.Gson.DEFAULT_LENIENT;
import static com.google.gson.Gson.DEFAULT_NUMBER_TO_NUMBER_STRATEGY;
import static com.google.gson.Gson.DEFAULT_OBJECT_TO_NUMBER_STRATEGY;
import static com.google.gson.Gson.DEFAULT_SERIALIZE_NULLS;
import static com.google.gson.Gson.DEFAULT_SPECIALIZE_FLOAT_VALUES;
+import static com.google.gson.Gson.DEFAULT_STRICTNESS;
import static com.google.gson.Gson.DEFAULT_USE_JDK_UNSAFE;
import com.google.gson.annotations.Since;
@@ -50,6 +50,7 @@
import java.util.Map;
import java.util.Objects;
+
/**
* 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
@@ -100,7 +101,7 @@ public final class GsonBuilder {
private boolean escapeHtmlChars = DEFAULT_ESCAPE_HTML;
private FormattingStyle formattingStyle = DEFAULT_FORMATTING_STYLE;
private boolean generateNonExecutableJson = DEFAULT_JSON_NON_EXECUTABLE;
- private boolean lenient = DEFAULT_LENIENT;
+ private Strictness strictness = DEFAULT_STRICTNESS;
private boolean useJdkUnsafe = DEFAULT_USE_JDK_UNSAFE;
private ToNumberStrategy objectToNumberStrategy = DEFAULT_OBJECT_TO_NUMBER_STRATEGY;
private ToNumberStrategy numberToNumberStrategy = DEFAULT_NUMBER_TO_NUMBER_STRATEGY;
@@ -130,7 +131,7 @@ public GsonBuilder() {
this.generateNonExecutableJson = gson.generateNonExecutableJson;
this.escapeHtmlChars = gson.htmlSafe;
this.formattingStyle = gson.formattingStyle;
- this.lenient = gson.lenient;
+ this.strictness = gson.strictness;
this.serializeSpecialFloatingPointValues = gson.serializeSpecialFloatingPointValues;
this.longSerializationPolicy = gson.longSerializationPolicy;
this.datePattern = gson.datePattern;
@@ -502,17 +503,38 @@ public GsonBuilder setPrettyPrinting(FormattingStyle formattingStyle) {
}
/**
- * Configures Gson to allow JSON data which does not strictly comply with the JSON specification.
+ * Sets the strictness of this builder to {@link Strictness#LENIENT}.
*
- *
Note: Due to legacy reasons most methods of Gson are always lenient, regardless of
- * whether this builder method is used.
+ *
This method has been deprecated. Please use {@link GsonBuilder#setStrictness(Strictness)} instead.
+ * Calling this method is equivalent to {@code setStrictness(Strictness.LENIENT)}
*
- * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
- * @see JsonReader#setLenient(boolean)
- * @see JsonWriter#setLenient(boolean)
+ * @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
public GsonBuilder setLenient() {
- lenient = true;
+ strictness = Strictness.LENIENT;
+ return this;
+ }
+
+ /**
+ * Sets the strictness of this builder to the provided parameter.
+ *
+ * This changes how strict the
+ * RFC 8259 JSON specification is enforced when parsing or
+ * writing JSON. For details on this, refer to {@link JsonReader#setStrictness(Strictness)} and
+ * {@link JsonWriter#setStrictness(Strictness)}.
+ *
+ * @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)
+ * @since $next-version$
+ */
+ public GsonBuilder setStrictness(Strictness strictness) {
+ this.strictness = Objects.requireNonNull(strictness);
return this;
}
@@ -684,7 +706,7 @@ public GsonBuilder registerTypeHierarchyAdapter(Class> baseType, Object typeAd
}
/**
- * Section 2.4 of JSON specification disallows
+ * Section 6 of JSON specification disallows
* special double values (NaN, Infinity, -Infinity). However,
* Javascript
* specification (see section 4.3.20, 4.3.22, 4.3.23) allows these values as valid Javascript
@@ -774,7 +796,7 @@ public Gson create() {
return new Gson(excluder, fieldNamingPolicy, new HashMap<>(instanceCreators),
serializeNulls, complexMapKeySerialization,
- generateNonExecutableJson, escapeHtmlChars, formattingStyle, lenient,
+ generateNonExecutableJson, escapeHtmlChars, formattingStyle, strictness,
serializeSpecialFloatingPointValues, useJdkUnsafe, longSerializationPolicy,
datePattern, dateStyle, timeStyle, new ArrayList<>(this.factories),
new ArrayList<>(this.hierarchyFactories), factories,
diff --git a/gson/src/main/java/com/google/gson/Strictness.java b/gson/src/main/java/com/google/gson/Strictness.java
new file mode 100644
index 0000000000..daf086a05f
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/Strictness.java
@@ -0,0 +1,29 @@
+package com.google.gson;
+
+import com.google.gson.stream.JsonWriter;
+import com.google.gson.stream.JsonReader;
+
+/**
+ * Modes that indicate how strictly a JSON {@linkplain JsonReader reader} or
+ * {@linkplain JsonWriter writer} follows the syntax laid out in the
+ * RFC 8259 JSON specification .
+ *
+ * You can look at {@link JsonWriter#setStrictness(Strictness)} to see how the strictness
+ * affects the {@link JsonWriter} and you can look at
+ * {@link JsonReader#setStrictness(Strictness)} to see how the strictness
+ * affects the {@link JsonReader}.
+ *
+ * @see JsonReader#setStrictness(Strictness)
+ * @see JsonWriter#setStrictness(Strictness)
+ * @since $next-version$
+ */
+public enum Strictness {
+ /** Allow large deviations from the JSON specification. */
+ LENIENT,
+
+ /** Allow certain small deviations from the JSON specification for legacy reasons. */
+ LEGACY_STRICT,
+
+ /** Strict compliance with the JSON specification. */
+ STRICT
+}
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 559ab2db81..df0e11934f 100644
--- a/gson/src/main/java/com/google/gson/stream/JsonReader.java
+++ b/gson/src/main/java/com/google/gson/stream/JsonReader.java
@@ -16,6 +16,7 @@
package com.google.gson.stream;
+import com.google.gson.Strictness;
import com.google.gson.internal.JsonReaderInternalAccess;
import com.google.gson.internal.bind.JsonTreeReader;
import java.io.Closeable;
@@ -26,7 +27,7 @@
import java.util.Objects;
/**
- * Reads a JSON (RFC 7159 )
+ * Reads a JSON (RFC 8259 )
* encoded value as a stream of tokens. This stream includes both literal
* values (strings, numbers, booleans, and nulls) as well as the begin and
* end delimiters of objects and arrays. The tokens are traversed in
@@ -227,7 +228,7 @@ public class JsonReader implements Closeable {
private final Reader in;
/** True to accept non-spec compliant JSON */
- private boolean lenient = false;
+ private Strictness strictness = Strictness.LEGACY_STRICT;
static final int BUFFER_SIZE = 1024;
/**
@@ -292,55 +293,102 @@ public JsonReader(Reader in) {
}
/**
- * Configure this parser to be liberal in what it accepts. By default,
- * this parser is strict and only accepts JSON as specified by RFC 4627 . Setting the
- * parser to lenient causes it to ignore the following syntax errors:
+ * Sets the strictness of this reader.
*
- *
- * Streams that start with the non-execute
- * prefix , ")]}'\n"
.
- * Streams that include multiple top-level values. With strict parsing,
- * each stream must contain exactly one top-level value.
- * Numbers may be {@link Double#isNaN() NaNs} or {@link
- * Double#isInfinite() infinities}.
- * End of line comments starting with {@code //} or {@code #} and
- * ending with a newline character.
- * C-style comments starting with {@code /*} and ending with
- * {@code *}{@code /}. Such comments may not be nested.
- * Names that are unquoted or {@code 'single quoted'}.
- * Strings that are unquoted or {@code 'single quoted'}.
- * Array elements separated by {@code ;} instead of {@code ,}.
- * Unnecessary array separators. These are interpreted as if null
- * was the omitted value.
- * Names and values separated by {@code =} or {@code =>} instead of
- * {@code :}.
- * Name/value pairs separated by {@code ;} instead of {@code ,}.
- *
+ * This method is deprecated. Please use {@link JsonReader#setStrictness(Strictness)} instead.
+ * {@code JsonReader.setLenient(true)} should be replaced by {@code JsonReader.setStrictness(Strictness.LENIENT)}
+ * and {@code JsonReader.setLenient(false)} should be replaced by {@code JsonReader.setStrictness(Strictness.LEGACY_STRICT)}.
*
- *
Note: Even in strict mode there are slight derivations from the JSON
- * specification:
- *
- * JsonReader allows the literals {@code true}, {@code false} and {@code null}
- * to have any capitalization, for example {@code fAlSe}
- * JsonReader supports the escape sequence {@code \'}, representing a {@code '}
- * JsonReader supports the escape sequence \LF
(with {@code LF}
- * being the Unicode character U+000A), resulting in a {@code LF} within the
- * read JSON string
- * JsonReader allows unescaped control characters (U+0000 through U+001F)
- *
+ * @param lenient whether this reader should be lenient. If true, the strictness is set to {@link Strictness#LENIENT}.
+ * If false, the strictness is set to {@link Strictness#LEGACY_STRICT}.
+ * @see #setStrictness(Strictness)
*/
+ @Deprecated
public final void setLenient(boolean lenient) {
- this.lenient = lenient;
+ this.strictness = lenient ? Strictness.LENIENT : Strictness.LEGACY_STRICT;
}
/**
- * Returns true if this parser is liberal in what it accepts.
+ * Returns true if the {@link Strictness} of this reader is equal to {@link Strictness#LENIENT}.
+ *
+ * @see #setStrictness(Strictness)
*/
public final boolean isLenient() {
- return lenient;
+ return strictness == Strictness.LENIENT;
}
+ /**
+ * Configures how liberal this parser is in what it accepts.
+ *
+ * In {@linkplain Strictness#STRICT strict} mode, the
+ * parser only accepts JSON in accordance with RFC 8259 .
+ * In {@linkplain Strictness#LEGACY_STRICT legacy strict} mode, only JSON in accordance with the RFC 8259 is accepted, with a few exceptions denoted below
+ * for backwards compatibility reasons. In {@linkplain Strictness#LENIENT lenient} mode,
+ * all sort of non-spec compliant JSON is accepted (see below).
+ *
+ *
+ * {@link Strictness#STRICT}
+ *
+ * In strict mode, only input compliant with RFC 8259
+ * is accepted.
+ *
+ * {@link Strictness#LEGACY_STRICT}
+ *
+ * In legacy strict mode, the following departures from RFC 8259
+ * are accepted:
+ *
+ * JsonReader allows the literals {@code true}, {@code false} and {@code null}
+ * to have any capitalization, for example {@code fAlSe} or {@code NULL}.
+ * JsonReader supports the escape sequence {@code \'}, representing a {@code '} (single-quote)
+ * JsonReader supports the escape sequence \LF
(with {@code LF}
+ * being the Unicode character {@code U+000A}), resulting in a {@code LF} within the
+ * read JSON string
+ * JsonReader allows unescaped control characters ({@code U+0000} through {@code U+001F})
+ *
+ *
+ * {@link Strictness#LENIENT}
+ *
+ * In lenient mode, all input that is accepted in legacy strict mode is accepted in addition to the following
+ * departures from RFC 8259 :
+ *
+ * Streams that start with the non-execute prefix , {@code ")]}'\n"}
+ * Streams that include multiple top-level values. With legacy strict or strict parsing,
+ * each stream must contain exactly one top-level value.
+ * Numbers may be {@link Double#isNaN() NaNs} or {@link Double#isInfinite() infinities} represented by
+ * {@code NaN} and {@code (-)Infinity} respectively.
+ * End of line comments starting with {@code //} or {@code #} and ending with a newline character.
+ * C-style comments starting with {@code /*} and ending with
+ * {@code *}{@code /}. Such comments may not be nested.
+ * Names that are unquoted or {@code 'single quoted'}.
+ * Strings that are unquoted or {@code 'single quoted'}.
+ * Array elements separated by {@code ;} instead of {@code ,}.
+ * Unnecessary array separators. These are interpreted as if null
+ * was the omitted value.
+ * Names and values separated by {@code =} or {@code =>} instead of
+ * {@code :}.
+ * Name/value pairs separated by {@code ;} instead of {@code ,}.
+ *
+ *
+ *
+ *
+ * @param strictness the new strictness value of this reader. May not be {@code null}.
+ * @since $next-version$
+ */
+ public final void setStrictness(Strictness strictness) {
+ Objects.requireNonNull(strictness);
+ this.strictness = strictness;
+ }
+
+ /**
+ * Returns the {@linkplain Strictness strictness} of this reader.
+ *
+ * @see #setStrictness(Strictness)
+ * @since $next-version$
+ */
+ public final Strictness getStrictness() {
+ return strictness;
+ }
/**
* Consumes the next token from the JSON stream and asserts that it is the
* beginning of a new array.
@@ -539,7 +587,7 @@ int doPeek() throws IOException {
throw syntaxError("Expected ':'");
}
} else if (peekStack == JsonScope.EMPTY_DOCUMENT) {
- if (lenient) {
+ if (strictness == Strictness.LENIENT) {
consumeNonExecutePrefix();
}
stack[stackSize - 1] = JsonScope.NONEMPTY_DOCUMENT;
@@ -609,6 +657,8 @@ private int peekKeyword() throws IOException {
String keyword;
String keywordUpper;
int peeking;
+
+ // Look at the first lower case letter to determine what keyword we are trying to match.
if (c == 't' || c == 'T') {
keyword = "true";
keywordUpper = "TRUE";
@@ -625,14 +675,18 @@ private int peekKeyword() throws IOException {
return PEEKED_NONE;
}
- // Confirm that chars [1..length) match the keyword.
+ // Upper cased keywords are not allowed in STRICT mode
+ boolean allowsUpperCased = strictness != Strictness.STRICT;
+
+ // Confirm that chars [0..length) match the keyword.
int length = keyword.length();
- for (int i = 1; i < length; i++) {
+ for (int i = 0; i < length; i++) {
if (pos + i >= limit && !fillBuffer(i + 1)) {
return PEEKED_NONE;
}
c = buffer[pos + i];
- if (c != keyword.charAt(i) && c != keywordUpper.charAt(i)) {
+ boolean matched = c == keyword.charAt(i) || (allowsUpperCased && c == keywordUpper.charAt(i));
+ if (!matched) {
return PEEKED_NONE;
}
}
@@ -920,7 +974,7 @@ public double nextDouble() throws IOException {
peeked = PEEKED_BUFFERED;
double result = Double.parseDouble(peekedString); // don't catch this NumberFormatException.
- if (!lenient && (Double.isNaN(result) || Double.isInfinite(result))) {
+ if (strictness != Strictness.LENIENT && (Double.isNaN(result) || Double.isInfinite(result))) {
throw new MalformedJsonException(
"JSON forbids NaN and infinities: " + result + locationString());
}
@@ -1007,7 +1061,10 @@ private String nextQuotedValue(char quote) throws IOException {
while (p < l) {
int c = buffer[p++];
- if (c == quote) {
+ // In strict mode, throw an exception when meeting unescaped control characters (U+0000 through U+001F)
+ if (strictness == Strictness.STRICT && c < 0x20) {
+ throw syntaxError("Unescaped control characters (\\u0000-\\u001F) are not allowed in strict mode.");
+ } else if (c == quote) {
pos = p;
int len = p - start - 1;
if (builder == null) {
@@ -1461,7 +1518,7 @@ private int nextNonWhitespace(boolean throwOnEof) throws IOException {
}
private void checkLenient() throws IOException {
- if (!lenient) {
+ if (strictness != Strictness.LENIENT) {
throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON");
}
}
@@ -1636,11 +1693,17 @@ private char readEscapeCharacter() throws IOException {
return '\f';
case '\n':
+ if (strictness == Strictness.STRICT) {
+ throw syntaxError("Cannot escape a newline character in strict mode!");
+ }
lineNumber++;
lineStart = pos;
// fall-through
case '\'':
+ if (strictness == Strictness.STRICT) {
+ throw syntaxError("Invalid escaped character \"'\" in strict mode");
+ }
case '"':
case '\\':
case '/':
diff --git a/gson/src/main/java/com/google/gson/stream/JsonWriter.java b/gson/src/main/java/com/google/gson/stream/JsonWriter.java
index 460dcce200..696f087eaf 100644
--- a/gson/src/main/java/com/google/gson/stream/JsonWriter.java
+++ b/gson/src/main/java/com/google/gson/stream/JsonWriter.java
@@ -24,6 +24,8 @@
import static com.google.gson.stream.JsonScope.NONEMPTY_DOCUMENT;
import static com.google.gson.stream.JsonScope.NONEMPTY_OBJECT;
+import com.google.gson.FormattingStyle;
+import com.google.gson.Strictness;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
@@ -36,10 +38,8 @@
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
-import com.google.gson.FormattingStyle;
-
/**
- * Writes a JSON (RFC 7159 )
+ * Writes a JSON (RFC 8259 )
* encoded value to a stream, one token at a time. The stream includes both
* literal values (strings, numbers, booleans and nulls) as well as the begin
* and end delimiters of objects and arrays.
@@ -141,7 +141,7 @@ public class JsonWriter implements Closeable, Flushable {
private static final Pattern VALID_JSON_NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][-+]?[0-9]+)?");
/*
- * From RFC 7159, "All Unicode characters may be placed within the
+ * From RFC 8259, "All Unicode characters may be placed within the
* quotation marks except for the characters that must be escaped:
* quotation mark, reverse solidus, and the control characters
* (U+0000 through U+001F)."
@@ -191,7 +191,7 @@ public class JsonWriter implements Closeable, Flushable {
*/
private String separator = ":";
- private boolean lenient;
+ private Strictness strictness = Strictness.LEGACY_STRICT;
private boolean htmlSafe;
@@ -228,7 +228,7 @@ public final void setIndent(String indent) {
* Sets the pretty printing style to be used in the encoded document.
* No pretty printing if null.
*
- * Sets the various attributes to be used in the encoded document.
+ *
Sets the various attributes to be used in the encoded document.
* For example the indentation string to be repeated for each level of indentation.
* Or the newline style, to accommodate various OS styles.
*
@@ -257,28 +257,66 @@ public final FormattingStyle getFormattingStyle() {
}
/**
- * Configure this writer to relax its syntax rules. By default, this writer
- * only emits well-formed JSON as specified by RFC 7159 . Setting the writer
- * to lenient permits the following:
- *
- * Numbers may be {@link Double#isNaN() NaNs} or {@link
- * Double#isInfinite() infinities}.
- *
+ * Sets the strictness of this writer.
+ *
+ * This method is deprecated. Please use {@link JsonWriter#setStrictness(Strictness)} instead.
+ * {@code JsonWriter.setLenient(true)} should be replaced by {@code JsonWriter.setStrictness(Strictness.LENIENT)}
+ * and {@code JsonWriter.setLenient(false)} should be replaced by {@code JsonWriter.setStrictness(Strictness.LEGACY_STRICT)}.
+ *
+ * @param lenient whether this writer should be lenient. If true, the strictness is set to {@link Strictness#LENIENT}.
+ * If false, the strictness is set to {@link Strictness#LEGACY_STRICT}.
+ * @see #setStrictness(Strictness)
*/
+ @Deprecated
public final void setLenient(boolean lenient) {
- this.lenient = lenient;
+ this.strictness = lenient ? Strictness.LENIENT : Strictness.LEGACY_STRICT;
}
/**
- * Returns true if this writer has relaxed syntax rules.
+ * Returns true if the {@link Strictness} of this writer is equal to {@link Strictness#LENIENT}.
+ *
+ * @see JsonWriter#setStrictness(Strictness)
*/
public boolean isLenient() {
- return lenient;
+ return strictness == Strictness.LENIENT;
+ }
+
+ /**
+ * Configures how strict this writer is with regard to the syntax rules specified in RFC 8259 . By default, {@link Strictness#LEGACY_STRICT} is used.
+ *
+ *
+ * {@link Strictness#STRICT} & {@link Strictness#LEGACY_STRICT}
+ *
+ * The behavior of these is currently identical. In these strictness modes, the writer only writes JSON
+ * in accordance with RFC 8259 .
+ *
+ * {@link Strictness#LENIENT}
+ *
+ * This mode relaxes the behavior of the writer to allow the writing of {@link Double#isNaN() NaNs}
+ * and {@link Double#isInfinite() infinities}. It also allows writing multiple top level values.
+ *
+ *
+ *
+ * @param strictness the new strictness of this writer. May not be {@code null}.
+ * @since $next-version$
+ */
+ public final void setStrictness(Strictness strictness) {
+ this.strictness = Objects.requireNonNull(strictness);
+ }
+
+ /**
+ * Returns how strict this writer is.
+ *
+ * @see #setStrictness(Strictness)
+ * @since $next-version$
+ */
+ public final Strictness getStrictness() {
+ return strictness;
}
/**
- * Configure this writer to emit JSON that's safe for direct inclusion in HTML
+ * Configures this writer to emit JSON that's safe for direct inclusion in HTML
* and XML documents. This escapes the HTML characters {@code <}, {@code >},
* {@code &} and {@code =} before writing them to the stream. Without this
* setting, your XML/HTML encoder should replace these characters with the
@@ -532,7 +570,7 @@ public JsonWriter value(Boolean value) throws IOException {
*/
public JsonWriter value(float value) throws IOException {
writeDeferredName();
- if (!lenient && (Float.isNaN(value) || Float.isInfinite(value))) {
+ if (strictness != Strictness.LENIENT && (Float.isNaN(value) || Float.isInfinite(value))) {
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
}
beforeValue();
@@ -551,7 +589,7 @@ public JsonWriter value(float value) throws IOException {
*/
public JsonWriter value(double value) throws IOException {
writeDeferredName();
- if (!lenient && (Double.isNaN(value) || Double.isInfinite(value))) {
+ if (strictness != Strictness.LENIENT && (Double.isNaN(value) || Double.isInfinite(value))) {
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
}
beforeValue();
@@ -601,7 +639,7 @@ public JsonWriter value(Number value) throws IOException {
writeDeferredName();
String string = value.toString();
if (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN")) {
- if (!lenient) {
+ if (strictness != Strictness.LENIENT) {
throw new IllegalArgumentException("Numeric values must be finite, but was " + string);
}
} else {
@@ -710,7 +748,7 @@ private void beforeName() throws IOException {
private void beforeValue() throws IOException {
switch (peek()) {
case NONEMPTY_DOCUMENT:
- if (!lenient) {
+ if (strictness != Strictness.LENIENT) {
throw new IllegalStateException(
"JSON must have only one top-level value.");
}
diff --git a/gson/src/test/java/com/google/gson/GsonBuilderTest.java b/gson/src/test/java/com/google/gson/GsonBuilderTest.java
index 4c6d5ec91e..3b5f9b027f 100644
--- a/gson/src/test/java/com/google/gson/GsonBuilderTest.java
+++ b/gson/src/test/java/com/google/gson/GsonBuilderTest.java
@@ -22,6 +22,8 @@
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
@@ -224,4 +226,31 @@ public void testSetVersionInvalid() {
assertThat(e).hasMessageThat().isEqualTo("Invalid version: -0.1");
}
}
+
+ @Test
+ public void testDefaultStrictness() throws IOException {
+ GsonBuilder builder = new GsonBuilder();
+ Gson gson = builder.create();
+ assertThat(gson.newJsonReader(new StringReader("{}}")).getStrictness()).isEqualTo(Strictness.LEGACY_STRICT);
+ assertThat(gson.newJsonWriter(new StringWriter()).getStrictness()).isEqualTo(Strictness.LEGACY_STRICT);
+ }
+
+ @Test
+ public void testSetLenient() throws IOException {
+ GsonBuilder builder = new GsonBuilder();
+ builder.setLenient();
+ Gson gson = builder.create();
+ assertThat(gson.newJsonReader(new StringReader("{}}")).getStrictness()).isEqualTo(Strictness.LENIENT);
+ assertThat(gson.newJsonWriter(new StringWriter()).getStrictness()).isEqualTo(Strictness.LENIENT);
+ }
+
+ @Test
+ public void testSetStrictness() throws IOException {
+ final Strictness STRICTNESS = Strictness.STRICT;
+ GsonBuilder builder = new GsonBuilder();
+ builder.setStrictness(STRICTNESS);
+ Gson gson = builder.create();
+ assertThat(gson.newJsonReader(new StringReader("{}}")).getStrictness()).isEqualTo(STRICTNESS);
+ assertThat(gson.newJsonWriter(new StringWriter()).getStrictness()).isEqualTo(STRICTNESS);
+ }
}
diff --git a/gson/src/test/java/com/google/gson/GsonTest.java b/gson/src/test/java/com/google/gson/GsonTest.java
index c1e9e9d785..88fabde393 100644
--- a/gson/src/test/java/com/google/gson/GsonTest.java
+++ b/gson/src/test/java/com/google/gson/GsonTest.java
@@ -59,11 +59,16 @@ public final class GsonTest {
private static final ToNumberStrategy CUSTOM_OBJECT_TO_NUMBER_STRATEGY = ToNumberPolicy.DOUBLE;
private static final ToNumberStrategy CUSTOM_NUMBER_TO_NUMBER_STRATEGY = ToNumberPolicy.LAZILY_PARSED_NUMBER;
+ @Test
+ public void testStrictnessDefault() {
+ assertThat(new Gson().strictness).isNull();
+ }
+
@Test
public void testOverridesDefaultExcluder() {
Gson gson = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
new HashMap>(), true, false, true, false,
- FormattingStyle.DEFAULT, true, false, true,
+ FormattingStyle.DEFAULT, Strictness.LENIENT, false, true,
LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
DateFormat.DEFAULT, new ArrayList(),
new ArrayList(), new ArrayList(),
@@ -80,7 +85,7 @@ public void testOverridesDefaultExcluder() {
public void testClonedTypeAdapterFactoryListsAreIndependent() {
Gson original = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
new HashMap>(), true, false, true, false,
- FormattingStyle.DEFAULT, true, false, true,
+ FormattingStyle.DEFAULT, Strictness.LENIENT, false, true,
LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
DateFormat.DEFAULT, new ArrayList(),
new ArrayList(), new ArrayList(),
diff --git a/gson/src/test/java/com/google/gson/internal/bind/JsonTreeReaderTest.java b/gson/src/test/java/com/google/gson/internal/bind/JsonTreeReaderTest.java
index 5faa718015..337d09a5e8 100644
--- a/gson/src/test/java/com/google/gson/internal/bind/JsonTreeReaderTest.java
+++ b/gson/src/test/java/com/google/gson/internal/bind/JsonTreeReaderTest.java
@@ -144,7 +144,7 @@ public JsonElement deepCopy() {
*/
@Test
public void testOverrides() {
- List ignoredMethods = Arrays.asList("setLenient(boolean)", "isLenient()");
+ List ignoredMethods = Arrays.asList("setLenient(boolean)", "isLenient()", "setStrictness(com.google.gson.Strictness)", "getStrictness()");
MoreAsserts.assertOverridesMethods(JsonReader.class, JsonTreeReader.class, ignoredMethods);
}
}
diff --git a/gson/src/test/java/com/google/gson/internal/bind/JsonTreeWriterTest.java b/gson/src/test/java/com/google/gson/internal/bind/JsonTreeWriterTest.java
index 116d275c0d..5dc9435682 100644
--- a/gson/src/test/java/com/google/gson/internal/bind/JsonTreeWriterTest.java
+++ b/gson/src/test/java/com/google/gson/internal/bind/JsonTreeWriterTest.java
@@ -280,6 +280,7 @@ public void testJsonValue() throws IOException {
public void testOverrides() {
List ignoredMethods = Arrays.asList(
"setLenient(boolean)", "isLenient()",
+ "setStrictness(com.google.gson.Strictness)", "getStrictness()",
"setIndent(java.lang.String)",
"setHtmlSafe(boolean)", "isHtmlSafe()",
"setFormattingStyle(com.google.gson.FormattingStyle)", "getFormattingStyle()",
diff --git a/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java b/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java
index 8ebe20b571..b0eb2e282a 100644
--- a/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java
+++ b/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java
@@ -27,17 +27,132 @@
import static com.google.gson.stream.JsonToken.STRING;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
import java.io.EOFException;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.Arrays;
+import com.google.gson.Strictness;
import org.junit.Ignore;
import org.junit.Test;
@SuppressWarnings("resource")
public final class JsonReaderTest {
+
+ @Test
+ public void testSetLenientTrue() {
+ JsonReader reader = new JsonReader(reader("{}"));
+ reader.setLenient(true);
+ assertThat(reader.getStrictness()).isEqualTo(Strictness.LENIENT);
+ }
+
+ @Test
+ public void testSetLenientFalse() {
+ JsonReader reader = new JsonReader(reader("{}"));
+ reader.setLenient(false);
+ assertThat(reader.getStrictness()).isEqualTo(Strictness.LEGACY_STRICT);
+ }
+
+ @Test
+ public void testSetStrictness() {
+ JsonReader reader = new JsonReader(reader("{}"));
+ reader.setStrictness(Strictness.STRICT);
+ assertThat(reader.getStrictness()).isEqualTo(Strictness.STRICT);
+ }
+
+ @Test
+ public void testSetStrictnessNull() {
+ JsonReader reader = new JsonReader(reader("{}"));
+ assertThrows(NullPointerException.class, () -> reader.setStrictness(null));
+ }
+
+ @Test
+ public void testEscapedNewlineNotAllowedInStrictMode() throws IOException {
+ String json = "\"\\\n\"";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.setStrictness(Strictness.STRICT);
+
+ IOException expected = assertThrows(IOException.class, reader::nextString);
+ assertThat(expected.getMessage()).contains("Cannot escape a newline character in strict mode!");
+ }
+
+ @Test
+ public void testEscapedNewlineAllowedInDefaultMode() throws IOException {
+ String json = "\"\\\n\"";
+ JsonReader reader = new JsonReader(reader(json));
+ assertThat(reader.nextString()).isEqualTo("\n");
+ }
+
+ @Test
+ public void testStrictModeFailsToParseUnescapedControlCharacter() {
+ String json = "\"\t\"";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.setStrictness(Strictness.STRICT);
+
+ IOException expected = assertThrows(IOException.class, reader::nextString);
+ assertThat(expected.getMessage()).contains("Unescaped control characters (\\u0000-\\u001F) are not allowed in strict mode.");
+ }
+
+ @Test
+ public void testNonStrictModeParsesUnescapedControlCharacter() throws IOException {
+ String json = "\"\t\"";
+ JsonReader reader = new JsonReader(reader(json));
+ assertThat(reader.nextString()).isEqualTo("\t");
+ }
+
+ @Test
+ public void testCapitalizedTrueFailWhenStrict() throws IOException {
+ JsonReader reader = new JsonReader(reader("TRUE"));
+ reader.setStrictness(Strictness.STRICT);
+
+ IOException expected = assertThrows(IOException.class, reader::nextString);
+ assertThat(expected).hasMessageThat().isEqualTo("Use JsonReader.setLenient(true) to accept malformed" +
+ " JSON at line 1 column 1 path $");
+
+ reader = new JsonReader(reader("True"));
+ reader.setStrictness(Strictness.STRICT);
+
+ expected = assertThrows(IOException.class, reader::nextString);
+ assertThat(expected).hasMessageThat().isEqualTo("Use JsonReader.setLenient(true) to accept malformed" +
+ " JSON at line 1 column 1 path $");
+ }
+
+ @Test
+ public void testCapitalizedNullFailWhenStrict() throws IOException {
+ JsonReader reader = new JsonReader(reader("NULL"));
+ reader.setStrictness(Strictness.STRICT);
+
+ IOException expected = assertThrows(IOException.class, reader::nextNull);
+ assertThat(expected).hasMessageThat().isEqualTo("Use JsonReader.setLenient(true) to accept malformed" +
+ " JSON at line 1 column 1 path $");
+
+ reader = new JsonReader(reader("nulL"));
+ reader.setStrictness(Strictness.STRICT);
+
+ expected = assertThrows(IOException.class, reader::nextNull);
+ assertThat(expected).hasMessageThat().isEqualTo("Use JsonReader.setLenient(true) to accept malformed" +
+ " JSON at line 1 column 1 path $");
+ }
+
+ @Test
+ public void testCapitalizedFalseFailWhenStrict() throws IOException {
+ JsonReader reader = new JsonReader(reader("FALSE"));
+ reader.setStrictness(Strictness.STRICT);
+
+ IOException expected = assertThrows(IOException.class, reader::nextBoolean);
+ assertThat(expected).hasMessageThat().isEqualTo("Use JsonReader.setLenient(true) to accept malformed" +
+ " JSON at line 1 column 1 path $");
+
+ reader = new JsonReader(reader("FaLse"));
+ reader.setStrictness(Strictness.STRICT);
+
+ expected = assertThrows(IOException.class, reader::nextBoolean);
+ assertThat(expected).hasMessageThat().isEqualTo("Use JsonReader.setLenient(true) to accept malformed" +
+ " JSON at line 1 column 1 path $");
+ }
+
@Test
public void testReadArray() throws IOException {
JsonReader reader = new JsonReader(reader("[true, true]"));
@@ -347,6 +462,43 @@ public void testCharacterUnescaping() throws IOException {
assertThat(reader.peek()).isEqualTo(JsonToken.END_DOCUMENT);
}
+ @Test
+ public void testReaderDoesNotTreatU2028U2029AsNewline() throws IOException {
+ // This test shows that the JSON String [\n"whatever'] is seen as valid
+ // And the JSON string [\u2028"whatever"] is not.
+ String jsonInvalid2028 = "[\u2028\"whatever\"]";
+ JsonReader readerInvalid2028 = new JsonReader((reader(jsonInvalid2028)));
+ readerInvalid2028.beginArray();
+ assertThrows(IOException.class, readerInvalid2028::nextString);
+
+ String jsonInvalid2029 = "[\u2029\"whatever\"]";
+ JsonReader readerInvalid2029 = new JsonReader((reader(jsonInvalid2029)));
+ readerInvalid2029.beginArray();
+ assertThrows(IOException.class, readerInvalid2029::nextString);
+
+ String jsonValid = "[\n\"whatever\"]";
+ JsonReader readerValid = new JsonReader(reader(jsonValid));
+ readerValid.beginArray();
+ assertThat(readerValid.nextString()).isEqualTo("whatever");
+ }
+
+ @Test
+ public void testEscapeCharacterQuoteInStrictMode() throws IOException {
+ String json = "\"\\'\"";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.setStrictness(Strictness.STRICT);
+
+ IOException expected = assertThrows(IOException.class, reader::nextString);
+ assertThat(expected).hasMessageThat().contains("Invalid escaped character \"'\" in strict mode");
+ }
+
+ @Test
+ public void testEscapeCharacterQuoteWithoutStrictMode() throws IOException {
+ String json = "\"\\'\"";
+ JsonReader reader = new JsonReader(reader(json));
+ assertThat(reader.nextString()).isEqualTo("'");
+ }
+
@Test
public void testUnescapingInvalidCharacters() throws IOException {
String json = "[\"\\u000g\"]";
@@ -359,6 +511,17 @@ public void testUnescapingInvalidCharacters() throws IOException {
}
}
+ @Test
+ public void testUnescapedControlCharactersInStrictMode() throws IOException {
+ String json = "[\"\u0014\"]";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.setStrictness(Strictness.STRICT);
+ reader.beginArray();
+
+ IOException expected = assertThrows(IOException.class, reader::nextString);
+ assertThat(expected).hasMessageThat().contains("Unescaped control characters");
+ }
+
@Test
public void testUnescapingTruncatedCharacters() throws IOException {
String json = "[\"\\u000";
diff --git a/gson/src/test/java/com/google/gson/stream/JsonWriterTest.java b/gson/src/test/java/com/google/gson/stream/JsonWriterTest.java
index 70470a166b..6ecdde74a7 100644
--- a/gson/src/test/java/com/google/gson/stream/JsonWriterTest.java
+++ b/gson/src/test/java/com/google/gson/stream/JsonWriterTest.java
@@ -17,9 +17,11 @@
package com.google.gson.stream;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import com.google.gson.FormattingStyle;
+import com.google.gson.Strictness;
import com.google.gson.internal.LazilyParsedNumber;
import java.io.IOException;
import java.io.StringWriter;
@@ -30,6 +32,49 @@
@SuppressWarnings("resource")
public final class JsonWriterTest {
+ @Test
+ public void testDefaultStrictness() throws IOException {
+ JsonWriter jsonWriter = new JsonWriter(new StringWriter());
+ assertThat(jsonWriter.getStrictness()).isEqualTo(Strictness.LEGACY_STRICT);
+ jsonWriter.value(false);
+ jsonWriter.close();
+ }
+
+ @Test
+ public void testSetLenientTrue() throws IOException {
+ JsonWriter jsonWriter = new JsonWriter(new StringWriter());
+ jsonWriter.setLenient(true);
+ assertThat(jsonWriter.getStrictness()).isEqualTo(Strictness.LENIENT);
+ jsonWriter.value(false);
+ jsonWriter.close();
+ }
+
+ @Test
+ public void testSetLenientFalse() throws IOException {
+ JsonWriter jsonWriter = new JsonWriter(new StringWriter());
+ jsonWriter.setLenient(false);
+ assertThat(jsonWriter.getStrictness()).isEqualTo(Strictness.LEGACY_STRICT);
+ jsonWriter.value(false);
+ jsonWriter.close();
+ }
+
+ @Test
+ public void testSetStrictness() throws IOException {
+ JsonWriter jsonWriter = new JsonWriter(new StringWriter());
+ jsonWriter.setStrictness(Strictness.STRICT);
+ assertThat(jsonWriter.getStrictness()).isEqualTo(Strictness.STRICT);
+ jsonWriter.value(false);
+ jsonWriter.close();
+ }
+
+ @Test
+ public void testSetStrictnessNull() throws IOException {
+ JsonWriter jsonWriter = new JsonWriter(new StringWriter());
+ assertThrows(NullPointerException.class, () -> jsonWriter.setStrictness(null));
+ jsonWriter.value(false);
+ jsonWriter.close();
+ }
+
@Test
public void testTopLevelValueTypes() throws IOException {
StringWriter string1 = new StringWriter();