Strict mode for JSON parsing, contributed by @marten-voorberg. (#2437)

* Strict mode for JSON parsing (#2323)

* Feat #6: Add strict flag to Gson and GsonBuilder

* Test #2: Add failing tests for capitalized keywords

* Feat #2: JsonReader does not read (partially) capitalized keywords if strict mode is used

* Feat #3: Added implementation and tests for JSONReader not accepting specific escape sequence representing in strict mode

* Test #3: Simplify test cases by removing unnecessary array

* Feat #3: Improve error by including the illegal character

* Feat #5: JsonReader does not allow unespaced control flow characters in strict mode

* Test #5: Test unespaced control flow characters in strict mode

* Feat #4: Disallow espaced newline character in strict mode

* Test #4: Add tests for (dis)allowing newline character depensding on strictness

* Test #5: Test case for unescaped control char in non-strict mode

* Test #2: Simplify test cases

* Feat #13: Change leniency API to Strictness enum in JsonReader, Gson, and GsonBuilder

* Feat #15: Change JsonWriter API to also use Strictness

* Test #15: Test Strictness in JsonWriter API

* Doc #15: Add and update documentation for Strictness in JsonWriter API

* refactor #12: Fixed typos and empty catch brackets in tests

* refactor #12: Resolved importing wildcards, made some lines adhere to Google java style

* #5 Add test case for unescaped control characters

* Feat #5: add new lines to make JsonReader able to detect unescaped control characters (U+0000 through U+001F) and throw exceptions.

* Feat #5: add new lines to make JsonReader able to detect unescaped control characters (U+0000 through U+001F) and throw exceptions.

* Test #11: Added two tests for testing implementation of control character handling in strict mode and moved the implementation to nextQuotedValue

* Test #11: Added two tests for testing implementation of control character handling in strict mode and moved the implementation to nextQuotedValue

---------

Co-authored-by: LMC117 <2295699210@qq.com>
Co-authored-by: Marten Voorberg <martenvoorberg@gmail.com>

* Doc #17: Add and change javadoc of public methods

* Doc #17: Update JavaDoc in JsonReader and Strictness

* Doc #17: Update JavaDoc in Gson and GsonBuilder

* Test #34: Add tests for setting strictness through GsonBuilder

* Fix: Add Fix broken test

* Fix: Invalid JavaDoc in Gson.java

* Doc #17: update outdated javadoc

* #37: Resolve more PR feedback 

* Fix #37: Resolve various PR comments

* Fix #37: Resolve various PR comments

* Refactor #35: Refactor JsonReader#peekKeyword to reduce the amount of strictness checks (#39)

* Doc #40: Update JavaDoc based on PR feedback

* Doc #40: Update old RFC in GsonBuilder documentation

* Doc #40: Fix formatting error in JavaDoc

* Doc #40: Add tests for setting strictness and lenient to JsonReaderTest

* Test #43: Changed tests to make use of assertThrows

* test #43: Changed tests to make use of assertThrows as per feedback

* Test #43: Update JsonWriterTest#testStrictnessNull to use assertThrows

* Test #43: Update JsonWriterTest#testStrictnessNull to use assertThrows

* test #43: Resolve PR recommendations

* Test #43: Mini change to TC

* Test #43: Mini change to TC

---------

Co-authored-by: Marten Voorberg <martenvoorberg@gmail.com>

* doc #46: Resolved comments in main PR

* Feat #45: Change Gson.fromJson and Gson.toJson to be strict when the provided writer/reader is strict

* Fix #45: Small type

* Update gson/src/test/java/com/google/gson/stream/JsonReaderTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Fix #45: Resolve various comments by Marcono1234

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

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Fix #45: Resolve various comments by Marcono1234

* Fix #45: Resolve various comments by eamonmcmanus

* Strictness mode follow-up

* Update Troubleshooting.md and Gson default lenient mode documentation

* Always use GSON strictness when set.

* Rename Strictness.DEFAULT to Strictness.LEGACY_STRICT

* Update JavaDoc with new strictness functionality

* Replace default with legacy strict for JsonReader javadoc

* Add JSONReader test cases for U2028 and U2029

* Refactor JSONReader#peekKeyWord() based on @eamonmcmanus's suggestion

* Deprecate setLenient in favor of setStrictness

---------

Co-authored-by: Carl Peterson <unknown>
Co-authored-by: Gustaf Johansson <gustajoh@kth.se>
Co-authored-by: gustajoh <58432871+gustajoh@users.noreply.github.com>
Co-authored-by: LMC117 <2295699210@qq.com>
Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* 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

* `strictness2` fixes & improvements (#2456)

* Adjust ProGuard default rules and shrinking tests (#2420)

* Adjust ProGuard default rules and shrinking tests

* Adjust comment

* Add shrinking test for class without no-args constructor; improve docs

* Improve Unsafe mention in Troubleshooting Guide

* Improve comment for `-if class *`

* Bump com.google.guava:guava from 32.0.1-jre to 32.1.1-jre (#2444)

Bumps [com.google.guava:guava](https://github.com/google/guava) from 32.0.1-jre to 32.1.1-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump com.google.guava:guava-testlib from 32.0.1-jre to 32.1.1-jre (#2443)

Bumps [com.google.guava:guava-testlib](https://github.com/google/guava) from 32.0.1-jre to 32.1.1-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava-testlib
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Support non-generic type for `TypeToken.getParameterized` for legacy reasons (#2447)

This partially restores the behavior before a589ef2008,
except that back then for a non-generic type a bogus `TypeToken(ParameterizedType)`
was created, whereas now a `TypeToken(Class)` is created instead.

* Fixed Typo in GsonBuilder.java (#2449)

* Make date-formatting tests less fragile with regular expressions. (#2450)

* Make date-formatting tests less fragile with regular expressions.

This is not great. We should really ensure that formatted dates are the same
regardless of JDK version. There is code that attempts to do that but it is not
really effective. So for now we fudge around the differences by using regular
expressions to paper over the differences.

* Temporarily add test-debugging code.

* Another attempt at debugging a test failure.

* Fix pattern in assertion.

* Modification in test cases (#2454)

* Fixed Typo in GsonBuilder.java

* Suggestions on Test cases

* Modified test cases using assertThrows method (JUnit)

* Update gson/src/test/java/com/google/gson/JsonArrayAsListTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/GsonTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/JsonArrayAsListTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/JsonStreamParserTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/JsonStreamParserTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/JsonStreamParserTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/ToNumberPolicyTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/TypeAdapterTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/TypeAdapterTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/ToNumberPolicyTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/ToNumberPolicyTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

---------

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Minor follow-up changes

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: elevne <97422844+elevne@users.noreply.github.com>
Co-authored-by: Éamonn McManus <emcmanus@google.com>
Co-authored-by: Wonil <cwi5525@naver.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Marten <martenvoorberg@gmail.com>
Co-authored-by: Gustaf Johansson <gustajoh@kth.se>
Co-authored-by: gustajoh <58432871+gustajoh@users.noreply.github.com>
Co-authored-by: LMC117 <2295699210@qq.com>
Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: elevne <97422844+elevne@users.noreply.github.com>
Co-authored-by: Wonil <cwi5525@naver.com>
This commit is contained in:
Éamonn McManus 2023-07-30 09:20:32 -07:00 committed by GitHub
parent 3d241ca0a6
commit b3dce2dc17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 864 additions and 333 deletions

View File

@ -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 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. The proper solution here is to fix the malformed JSON data.
@ -147,9 +147,12 @@ To spot syntax errors in the JSON data easily you can open it in an editor with
**Reason:** Due to legacy reasons Gson performs parsing by default in lenient mode **Reason:** Due to legacy reasons Gson performs parsing by default in lenient mode
**Solution:** See [`Gson` class documentation](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/Gson.html#default-lenient) section "Lenient JSON handling" **Solution:** If you are using Gson 2.11.0 or newer, call [`GsonBuilder.setStrictness`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/GsonBuilder.html#setStrictness(com.google.gson.Strictness)),
[`JsonReader.setStrictness`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/stream/JsonReader.html#setStrictness(com.google.gson.Strictness))
Note: Even in non-lenient mode Gson deviates slightly from the JSON specification, see [`JsonReader.setLenient`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/stream/JsonReader.html#setLenient(boolean)) for more details. and [`JsonWriter.setStrictness`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/stream/JsonWriter.html#setStrictness(com.google.gson.Strictness))
with `Strictness.STRICT` to overwrite the default lenient behavior of `Gson` and make these classes strictly adhere to the JSON specification.
Otherwise if you are using an older Gson version, see the [`Gson` class documentation](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/Gson.html#default-lenient)
section "JSON Strictness handling" for alternative solutions.
## <a id="unexpected-json-structure"></a> `IllegalStateException`: "Expected ... but was ..." ## <a id="unexpected-json-structure"></a> `IllegalStateException`: "Expected ... but was ..."

View File

@ -105,10 +105,14 @@ import java.util.concurrent.atomic.AtomicLongArray;
* <p>See the <a href="https://github.com/google/gson/blob/main/UserGuide.md">Gson User Guide</a> * <p>See the <a href="https://github.com/google/gson/blob/main/UserGuide.md">Gson User Guide</a>
* for a more complete set of examples.</p> * for a more complete set of examples.</p>
* *
* <h2 id="default-lenient">Lenient JSON handling</h2> * <h2 id="default-lenient">JSON Strictness handling</h2>
* For legacy reasons most of the {@code Gson} methods allow JSON data which does not * For legacy reasons most of the {@code Gson} methods allow JSON data which does not
* comply with the JSON specification, regardless of whether {@link GsonBuilder#setLenient()} * comply with the JSON specification when no explicit {@linkplain Strictness strictness} is set (the default).
* is used or not. If this behavior is not desired, the following workarounds can be used: * 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 strictness mode API, the following
* workarounds can be used:
* *
* <h3>Serialization</h3> * <h3>Serialization</h3>
* <ol> * <ol>
@ -132,6 +136,10 @@ import java.util.concurrent.atomic.AtomicLongArray;
* to make sure there is no trailing data * to make sure there is no trailing data
* </ol> * </ol>
* *
* Note that the {@code JsonReader} created this way is only 'legacy strict', it mostly adheres
* to the JSON specification but allows small deviations. See {@link JsonReader#setStrictness(Strictness)}
* for details.
*
* @see TypeToken * @see TypeToken
* *
* @author Inderjeet Singh * @author Inderjeet Singh
@ -140,7 +148,8 @@ import java.util.concurrent.atomic.AtomicLongArray;
*/ */
public final class Gson { public final class Gson {
static final boolean DEFAULT_JSON_NON_EXECUTABLE = false; static final boolean DEFAULT_JSON_NON_EXECUTABLE = false;
static final boolean DEFAULT_LENIENT = 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 FormattingStyle DEFAULT_FORMATTING_STYLE = FormattingStyle.COMPACT;
static final boolean DEFAULT_ESCAPE_HTML = true; static final boolean DEFAULT_ESCAPE_HTML = true;
static final boolean DEFAULT_SERIALIZE_NULLS = false; static final boolean DEFAULT_SERIALIZE_NULLS = false;
@ -184,7 +193,7 @@ public final class Gson {
final boolean generateNonExecutableJson; final boolean generateNonExecutableJson;
final boolean htmlSafe; final boolean htmlSafe;
final FormattingStyle formattingStyle; final FormattingStyle formattingStyle;
final boolean lenient; final Strictness strictness;
final boolean serializeSpecialFloatingPointValues; final boolean serializeSpecialFloatingPointValues;
final boolean useJdkUnsafe; final boolean useJdkUnsafe;
final String datePattern; final String datePattern;
@ -231,13 +240,15 @@ public final class Gson {
* <li>By default, Gson excludes <code>transient</code> or <code>static</code> fields from * <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 * consideration for serialization and deserialization. You can change this behavior through
* {@link GsonBuilder#excludeFieldsWithModifiers(int...)}.</li> * {@link GsonBuilder#excludeFieldsWithModifiers(int...)}.</li>
* <li>No explicit strictness is set. You can change this by calling
* {@link GsonBuilder#setStrictness(Strictness)}.</li>
* </ul> * </ul>
*/ */
public Gson() { public Gson() {
this(Excluder.DEFAULT, DEFAULT_FIELD_NAMING_STRATEGY, this(Excluder.DEFAULT, DEFAULT_FIELD_NAMING_STRATEGY,
Collections.<Type, InstanceCreator<?>>emptyMap(), DEFAULT_SERIALIZE_NULLS, Collections.<Type, InstanceCreator<?>>emptyMap(), DEFAULT_SERIALIZE_NULLS,
DEFAULT_COMPLEX_MAP_KEYS, DEFAULT_JSON_NON_EXECUTABLE, DEFAULT_ESCAPE_HTML, DEFAULT_COMPLEX_MAP_KEYS, DEFAULT_JSON_NON_EXECUTABLE, DEFAULT_ESCAPE_HTML,
DEFAULT_FORMATTING_STYLE, DEFAULT_LENIENT, DEFAULT_SPECIALIZE_FLOAT_VALUES, DEFAULT_FORMATTING_STYLE, DEFAULT_STRICTNESS, DEFAULT_SPECIALIZE_FLOAT_VALUES,
DEFAULT_USE_JDK_UNSAFE, DEFAULT_USE_JDK_UNSAFE,
LongSerializationPolicy.DEFAULT, DEFAULT_DATE_PATTERN, DateFormat.DEFAULT, DateFormat.DEFAULT, LongSerializationPolicy.DEFAULT, DEFAULT_DATE_PATTERN, DateFormat.DEFAULT, DateFormat.DEFAULT,
Collections.<TypeAdapterFactory>emptyList(), Collections.<TypeAdapterFactory>emptyList(), Collections.<TypeAdapterFactory>emptyList(), Collections.<TypeAdapterFactory>emptyList(),
@ -248,7 +259,7 @@ public final class Gson {
Gson(Excluder excluder, FieldNamingStrategy fieldNamingStrategy, Gson(Excluder excluder, FieldNamingStrategy fieldNamingStrategy,
Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls, Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe, boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
FormattingStyle formattingStyle, boolean lenient, boolean serializeSpecialFloatingPointValues, FormattingStyle formattingStyle, Strictness strictness, boolean serializeSpecialFloatingPointValues,
boolean useJdkUnsafe, boolean useJdkUnsafe,
LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle, LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,
int timeStyle, List<TypeAdapterFactory> builderFactories, int timeStyle, List<TypeAdapterFactory> builderFactories,
@ -265,7 +276,7 @@ public final class Gson {
this.generateNonExecutableJson = generateNonExecutableGson; this.generateNonExecutableJson = generateNonExecutableGson;
this.htmlSafe = htmlSafe; this.htmlSafe = htmlSafe;
this.formattingStyle = formattingStyle; this.formattingStyle = formattingStyle;
this.lenient = lenient; this.strictness = strictness;
this.serializeSpecialFloatingPointValues = serializeSpecialFloatingPointValues; this.serializeSpecialFloatingPointValues = serializeSpecialFloatingPointValues;
this.useJdkUnsafe = useJdkUnsafe; this.useJdkUnsafe = useJdkUnsafe;
this.longSerializationPolicy = longSerializationPolicy; this.longSerializationPolicy = longSerializationPolicy;
@ -802,7 +813,7 @@ public final class Gson {
* <pre> * <pre>
* Type typeOfSrc = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType(); * Type typeOfSrc = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType();
* </pre> * </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 * @throws JsonIOException if there was a problem writing to the writer
* @since 1.2 * @since 1.2
* *
@ -822,24 +833,38 @@ public final class Gson {
* Writes the JSON representation of {@code src} of type {@code typeOfSrc} to * Writes the JSON representation of {@code src} of type {@code typeOfSrc} to
* {@code writer}. * {@code writer}.
* *
* <p>The JSON data is written in {@linkplain JsonWriter#setLenient(boolean) lenient mode}, * <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
* regardless of the lenient mode setting of the provided writer. The lenient mode setting * this setting will be used for writing the JSON regardless of the {@linkplain JsonWriter#getStrictness() strictness}
* of the writer is restored once this method returns. * 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 * <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 * (configured by the {@link GsonBuilder}) are applied, and the original settings of the
* writer are restored once this method returns. * writer are restored once this method returns.
* *
* @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 * @throws JsonIOException if there was a problem writing to the writer
*/ */
public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOException { public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOException {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
TypeAdapter<Object> adapter = (TypeAdapter<Object>) getAdapter(TypeToken.get(typeOfSrc)); TypeAdapter<Object> adapter = (TypeAdapter<Object>) 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.STRICT) {
writer.setStrictness(Strictness.LENIENT);
}
boolean oldHtmlSafe = writer.isHtmlSafe(); boolean oldHtmlSafe = writer.isHtmlSafe();
writer.setHtmlSafe(htmlSafe);
boolean oldSerializeNulls = writer.getSerializeNulls(); boolean oldSerializeNulls = writer.getSerializeNulls();
writer.setHtmlSafe(htmlSafe);
writer.setSerializeNulls(serializeNulls); writer.setSerializeNulls(serializeNulls);
try { try {
adapter.write(writer, src); adapter.write(writer, src);
@ -848,7 +873,7 @@ public final class Gson {
} catch (AssertionError e) { } catch (AssertionError e) {
throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e); throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);
} finally { } finally {
writer.setLenient(oldLenient); writer.setStrictness(oldStrictness);
writer.setHtmlSafe(oldHtmlSafe); writer.setHtmlSafe(oldHtmlSafe);
writer.setSerializeNulls(oldSerializeNulls); writer.setSerializeNulls(oldSerializeNulls);
} }
@ -892,7 +917,10 @@ public final class Gson {
* <li>{@link GsonBuilder#disableHtmlEscaping()}</li> * <li>{@link GsonBuilder#disableHtmlEscaping()}</li>
* <li>{@link GsonBuilder#generateNonExecutableJson()}</li> * <li>{@link GsonBuilder#generateNonExecutableJson()}</li>
* <li>{@link GsonBuilder#serializeNulls()}</li> * <li>{@link GsonBuilder#serializeNulls()}</li>
* <li>{@link GsonBuilder#setLenient()}</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, the strictness of
* the {@code Gson} instance will be used for the created writer.</li>
* <li>{@link GsonBuilder#setPrettyPrinting()}</li> * <li>{@link GsonBuilder#setPrettyPrinting()}</li>
* <li>{@link GsonBuilder#setFormattingStyle(FormattingStyle)}</li> * <li>{@link GsonBuilder#setFormattingStyle(FormattingStyle)}</li>
* </ul> * </ul>
@ -904,7 +932,7 @@ public final class Gson {
JsonWriter jsonWriter = new JsonWriter(writer); JsonWriter jsonWriter = new JsonWriter(writer);
jsonWriter.setFormattingStyle(formattingStyle); jsonWriter.setFormattingStyle(formattingStyle);
jsonWriter.setHtmlSafe(htmlSafe); jsonWriter.setHtmlSafe(htmlSafe);
jsonWriter.setLenient(lenient); jsonWriter.setStrictness(strictness == null ? Strictness.LEGACY_STRICT : strictness);
jsonWriter.setSerializeNulls(serializeNulls); jsonWriter.setSerializeNulls(serializeNulls);
return jsonWriter; return jsonWriter;
} }
@ -914,35 +942,50 @@ public final class Gson {
* *
* <p>The following settings are considered: * <p>The following settings are considered:
* <ul> * <ul>
* <li>{@link GsonBuilder#setLenient()}</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, the strictness of
* the {@code Gson} instance will be used for the created reader.</li>
* </ul> * </ul>
*/ */
public JsonReader newJsonReader(Reader reader) { public JsonReader newJsonReader(Reader reader) {
JsonReader jsonReader = new JsonReader(reader); JsonReader jsonReader = new JsonReader(reader);
jsonReader.setLenient(lenient); jsonReader.setStrictness(strictness == null ? Strictness.LEGACY_STRICT : strictness);
return jsonReader; return jsonReader;
} }
/** /**
* Writes the JSON for {@code jsonElement} to {@code writer}. * Writes the JSON for {@code jsonElement} to {@code writer}.
* *
* <p>The JSON data is written in {@linkplain JsonWriter#setLenient(boolean) lenient mode}, * <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
* regardless of the lenient mode setting of the provided writer. The lenient mode setting * this setting will be used for writing the JSON regardless of the {@linkplain JsonWriter#getStrictness() strictness}
* of the writer is restored once this method returns. * 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 * <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 * (configured by the {@link GsonBuilder}) are applied, and the original settings of the
* writer are restored once this method returns. * 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 * @throws JsonIOException if there was a problem writing to the writer
*/ */
public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOException { public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOException {
boolean oldLenient = writer.isLenient(); Strictness oldStrictness = writer.getStrictness();
writer.setLenient(true);
boolean oldHtmlSafe = writer.isHtmlSafe(); boolean oldHtmlSafe = writer.isHtmlSafe();
writer.setHtmlSafe(htmlSafe);
boolean oldSerializeNulls = writer.getSerializeNulls(); boolean oldSerializeNulls = writer.getSerializeNulls();
writer.setHtmlSafe(htmlSafe);
writer.setSerializeNulls(serializeNulls); writer.setSerializeNulls(serializeNulls);
if (this.strictness != null) {
writer.setStrictness(this.strictness);
} else if (writer.getStrictness() != Strictness.STRICT) {
writer.setStrictness(Strictness.LENIENT);
}
try { try {
Streams.write(jsonElement, writer); Streams.write(jsonElement, writer);
} catch (IOException e) { } catch (IOException e) {
@ -950,7 +993,7 @@ public final class Gson {
} catch (AssertionError e) { } catch (AssertionError e) {
throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e); throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);
} finally { } finally {
writer.setLenient(oldLenient); writer.setStrictness(oldStrictness);
writer.setHtmlSafe(oldHtmlSafe); writer.setHtmlSafe(oldHtmlSafe);
writer.setSerializeNulls(oldSerializeNulls); writer.setSerializeNulls(oldSerializeNulls);
} }
@ -1169,9 +1212,12 @@ public final class Gson {
* <p>Unlike the other {@code fromJson} methods, no exception is thrown if the JSON data has * <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. * 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>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
* regardless of the lenient mode setting of the provided reader. The lenient mode setting * this setting will be used for reading the JSON regardless of the {@linkplain JsonReader#getStrictness() strictness}
* of the reader is restored once this method returns. * 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 <T> the type of the desired object
* @param reader the reader whose next JSON value should be deserialized * @param reader the reader whose next JSON value should be deserialized
@ -1198,9 +1244,12 @@ public final class Gson {
* <p>Unlike the other {@code fromJson} methods, no exception is thrown if the JSON data has * <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. * 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>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
* regardless of the lenient mode setting of the provided reader. The lenient mode setting * this setting will be used for reading the JSON regardless of the {@linkplain JsonReader#getStrictness() strictness}
* of the reader is restored once this method returns. * 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 <T> the type of the desired object
* @param reader the reader whose next JSON value should be deserialized * @param reader the reader whose next JSON value should be deserialized
@ -1220,8 +1269,14 @@ public final class Gson {
*/ */
public <T> T fromJson(JsonReader reader, TypeToken<T> typeOfT) throws JsonIOException, JsonSyntaxException { public <T> T fromJson(JsonReader reader, TypeToken<T> typeOfT) throws JsonIOException, JsonSyntaxException {
boolean isEmpty = true; boolean isEmpty = true;
boolean oldLenient = reader.isLenient(); Strictness oldStrictness = reader.getStrictness();
reader.setLenient(true);
if (this.strictness != null) {
reader.setStrictness(this.strictness);
} else if (reader.getStrictness() != Strictness.STRICT) {
reader.setStrictness(Strictness.LENIENT);
}
try { try {
JsonToken unused = reader.peek(); JsonToken unused = reader.peek();
isEmpty = false; isEmpty = false;
@ -1244,7 +1299,7 @@ public final class Gson {
} catch (AssertionError e) { } catch (AssertionError e) {
throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e); throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);
} finally { } finally {
reader.setLenient(oldLenient); reader.setStrictness(oldStrictness);
} }
} }

View File

@ -21,14 +21,15 @@ import static com.google.gson.Gson.DEFAULT_DATE_PATTERN;
import static com.google.gson.Gson.DEFAULT_ESCAPE_HTML; 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_FORMATTING_STYLE;
import static com.google.gson.Gson.DEFAULT_JSON_NON_EXECUTABLE; 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_NUMBER_TO_NUMBER_STRATEGY;
import static com.google.gson.Gson.DEFAULT_OBJECT_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_SERIALIZE_NULLS;
import static com.google.gson.Gson.DEFAULT_SPECIALIZE_FLOAT_VALUES; 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 static com.google.gson.Gson.DEFAULT_USE_JDK_UNSAFE;
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.InlineMe;
import com.google.gson.annotations.Since; import com.google.gson.annotations.Since;
import com.google.gson.annotations.Until; import com.google.gson.annotations.Until;
import com.google.gson.internal.$Gson$Preconditions; import com.google.gson.internal.$Gson$Preconditions;
@ -71,12 +72,16 @@ import java.util.Objects;
* .create(); * .create();
* </pre> * </pre>
* *
* <p>NOTES: * <p>Notes:
* <ul> * <ul>
* <li> the order of invocation of configuration methods does not matter.</li> * <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 default serialization of {@link Date} and its subclasses in Gson does
* not contain time-zone information. So, if you are using date/time instances, * not contain time-zone information. So, if you are using date/time instances,
* use {@code GsonBuilder} and its {@code setDateFormat} methods.</li> * 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> * </ul>
* *
* @author Inderjeet Singh * @author Inderjeet Singh
@ -100,7 +105,7 @@ public final class GsonBuilder {
private boolean escapeHtmlChars = DEFAULT_ESCAPE_HTML; private boolean escapeHtmlChars = DEFAULT_ESCAPE_HTML;
private FormattingStyle formattingStyle = DEFAULT_FORMATTING_STYLE; private FormattingStyle formattingStyle = DEFAULT_FORMATTING_STYLE;
private boolean generateNonExecutableJson = DEFAULT_JSON_NON_EXECUTABLE; 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 boolean useJdkUnsafe = DEFAULT_USE_JDK_UNSAFE;
private ToNumberStrategy objectToNumberStrategy = DEFAULT_OBJECT_TO_NUMBER_STRATEGY; private ToNumberStrategy objectToNumberStrategy = DEFAULT_OBJECT_TO_NUMBER_STRATEGY;
private ToNumberStrategy numberToNumberStrategy = DEFAULT_NUMBER_TO_NUMBER_STRATEGY; private ToNumberStrategy numberToNumberStrategy = DEFAULT_NUMBER_TO_NUMBER_STRATEGY;
@ -130,7 +135,7 @@ public final class GsonBuilder {
this.generateNonExecutableJson = gson.generateNonExecutableJson; this.generateNonExecutableJson = gson.generateNonExecutableJson;
this.escapeHtmlChars = gson.htmlSafe; this.escapeHtmlChars = gson.htmlSafe;
this.formattingStyle = gson.formattingStyle; this.formattingStyle = gson.formattingStyle;
this.lenient = gson.lenient; this.strictness = gson.strictness;
this.serializeSpecialFloatingPointValues = gson.serializeSpecialFloatingPointValues; this.serializeSpecialFloatingPointValues = gson.serializeSpecialFloatingPointValues;
this.longSerializationPolicy = gson.longSerializationPolicy; this.longSerializationPolicy = gson.longSerializationPolicy;
this.datePattern = gson.datePattern; this.datePattern = gson.datePattern;
@ -521,18 +526,40 @@ public final class GsonBuilder {
} }
/** /**
* 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}.
* *
* <p>Note: Due to legacy reasons most methods of Gson are always lenient, regardless of * @deprecated This method is equivalent to calling {@link #setStrictness(Strictness)} with
* whether this builder method is used. * {@link Strictness#LENIENT}: {@code setStrictness(Strictness.LENIENT)}
* *
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern.
* @see JsonReader#setLenient(boolean) * @see JsonReader#setStrictness(Strictness)
* @see JsonWriter#setLenient(boolean) * @see JsonWriter#setStrictness(Strictness)
* @see #setStrictness(Strictness)
*/ */
@Deprecated
@InlineMe(replacement = "this.setStrictness(Strictness.LENIENT)", imports = "com.google.gson.Strictness")
@CanIgnoreReturnValue @CanIgnoreReturnValue
public GsonBuilder setLenient() { public GsonBuilder setLenient() {
lenient = true; return setStrictness(Strictness.LENIENT);
}
/**
* Sets the strictness of this builder to the provided parameter.
*
* <p>This changes how strict the
* <a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259 JSON specification</a> is enforced when parsing or
* writing JSON. For details on this, refer to {@link JsonReader#setStrictness(Strictness)} and
* {@link JsonWriter#setStrictness(Strictness)}.</p>
*
* @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 JsonReader#setStrictness(Strictness)
* @see JsonWriter#setStrictness(Strictness)
* @since $next-version$
*/
@CanIgnoreReturnValue
public GsonBuilder setStrictness(Strictness strictness) {
this.strictness = Objects.requireNonNull(strictness);
return this; return this;
} }
@ -711,7 +738,7 @@ public final class GsonBuilder {
} }
/** /**
* Section 2.4 of <a href="http://www.ietf.org/rfc/rfc4627.txt">JSON specification</a> disallows * Section 6 of <a href="https://www.ietf.org/rfc/rfc8259.txt">JSON specification</a> disallows
* special double values (NaN, Infinity, -Infinity). However, * special double values (NaN, Infinity, -Infinity). However,
* <a href="http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf">Javascript * <a href="http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf">Javascript
* specification</a> (see section 4.3.20, 4.3.22, 4.3.23) allows these values as valid Javascript * specification</a> (see section 4.3.20, 4.3.22, 4.3.23) allows these values as valid Javascript
@ -804,7 +831,7 @@ public final class GsonBuilder {
return new Gson(excluder, fieldNamingPolicy, new HashMap<>(instanceCreators), return new Gson(excluder, fieldNamingPolicy, new HashMap<>(instanceCreators),
serializeNulls, complexMapKeySerialization, serializeNulls, complexMapKeySerialization,
generateNonExecutableJson, escapeHtmlChars, formattingStyle, lenient, generateNonExecutableJson, escapeHtmlChars, formattingStyle, strictness,
serializeSpecialFloatingPointValues, useJdkUnsafe, longSerializationPolicy, serializeSpecialFloatingPointValues, useJdkUnsafe, longSerializationPolicy,
datePattern, dateStyle, timeStyle, new ArrayList<>(this.factories), datePattern, dateStyle, timeStyle, new ArrayList<>(this.factories),
new ArrayList<>(this.hierarchyFactories), factories, new ArrayList<>(this.hierarchyFactories), factories,

View File

@ -321,7 +321,8 @@ public abstract class JsonElement {
try { try {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(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); Streams.write(this, jsonWriter);
return stringWriter.toString(); return stringWriter.toString();
} catch (IOException e) { } catch (IOException e) {

View File

@ -41,7 +41,7 @@ public final class JsonParser {
* An exception is thrown if the JSON string has multiple top-level JSON elements, * An exception is thrown if the JSON string has multiple top-level JSON elements,
* or if there is trailing data. * 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 * @param json JSON text
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON * @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
@ -57,7 +57,7 @@ public final class JsonParser {
* An exception is thrown if the JSON string has multiple top-level JSON elements, * An exception is thrown if the JSON string has multiple top-level JSON elements,
* or if there is trailing data. * 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 * @param reader JSON text
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON * @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
@ -87,8 +87,8 @@ public final class JsonParser {
* Unlike the other {@code parse} methods, no exception is thrown if the JSON data has * 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. * 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},
* regardless of the lenient mode setting of the provided reader. The lenient mode setting * regardless of the strictness setting of the provided reader. The strictness setting
* of the reader is restored once this method returns. * of the reader is restored once this method returns.
* *
* @throws JsonParseException if there is an IOException or if the specified * @throws JsonParseException if there is an IOException or if the specified
@ -97,8 +97,8 @@ public final class JsonParser {
*/ */
public static JsonElement parseReader(JsonReader reader) public static JsonElement parseReader(JsonReader reader)
throws JsonIOException, JsonSyntaxException { throws JsonIOException, JsonSyntaxException {
boolean lenient = reader.isLenient(); Strictness strictness = reader.getStrictness();
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
try { try {
return Streams.parse(reader); return Streams.parse(reader);
} catch (StackOverflowError e) { } catch (StackOverflowError e) {
@ -106,7 +106,7 @@ public final class JsonParser {
} catch (OutOfMemoryError e) { } catch (OutOfMemoryError e) {
throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e); throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e);
} finally { } finally {
reader.setLenient(lenient); reader.setStrictness(strictness);
} }
} }

View File

@ -28,7 +28,7 @@ import java.util.NoSuchElementException;
/** /**
* A streaming parser that allows reading of multiple {@link JsonElement}s from the specified reader * A streaming parser that allows reading of multiple {@link JsonElement}s from the specified reader
* asynchronously. The JSON data is parsed in lenient mode, see also * asynchronously. The JSON data is parsed in lenient mode, see also
* {@link JsonReader#setLenient(boolean)}. * {@link JsonReader#setStrictness(Strictness)}.
* *
* <p>This class is conditionally thread-safe (see Item 70, Effective Java second edition). To * <p>This class is conditionally thread-safe (see Item 70, Effective Java second edition). To
* properly use this class across multiple threads, you will need to add some external * properly use this class across multiple threads, you will need to add some external
@ -66,7 +66,7 @@ public final class JsonStreamParser implements Iterator<JsonElement> {
*/ */
public JsonStreamParser(Reader reader) { public JsonStreamParser(Reader reader) {
parser = new JsonReader(reader); parser = new JsonReader(reader);
parser.setLenient(true); parser.setStrictness(Strictness.LENIENT);
lock = new Object(); lock = new Object();
} }

View File

@ -0,0 +1,29 @@
package com.google.gson;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
/**
* Modes that indicate how strictly a JSON {@linkplain JsonReader reader} or
* {@linkplain JsonWriter writer} follows the syntax laid out in the
* <a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259 JSON specification</a>.
*
* <p>You can look at {@link JsonReader#setStrictness(Strictness)} to see how the strictness
* affects the {@link JsonReader} and you can look at
* {@link JsonWriter#setStrictness(Strictness)} to see how the strictness
* affects the {@link JsonWriter}.</p>
*
* @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
}

View File

@ -134,10 +134,10 @@ public abstract class TypeAdapter<T> {
/** /**
* Converts {@code value} to a JSON document and writes it to {@code out}. * Converts {@code value} to a JSON document and writes it to {@code out}.
* Unlike Gson's similar {@link Gson#toJson(JsonElement, Appendable) toJson} * The strictness {@link Strictness#LEGACY_STRICT} is used for writing the JSON data.
* method, this write is strict. Create a {@link * To use a different strictness setting create a {@link JsonWriter}, call its
* JsonWriter#setLenient(boolean) lenient} {@code JsonWriter} and call * {@link JsonWriter#setStrictness(Strictness)} method and then use
* {@link #write(JsonWriter, Object)} for lenient writing. * {@link #write(JsonWriter, Object)} for writing.
* *
* @param value the Java object to convert. May be null. * @param value the Java object to convert. May be null.
* @since 2.2 * @since 2.2
@ -207,10 +207,11 @@ public abstract class TypeAdapter<T> {
} }
/** /**
* Converts {@code value} to a JSON document. Unlike Gson's similar {@link * Converts {@code value} to a JSON document.
* Gson#toJson(Object) toJson} method, this write is strict. Create a {@link * The strictness {@link Strictness#LEGACY_STRICT} is used for writing the JSON data.
* JsonWriter#setLenient(boolean) lenient} {@code JsonWriter} and call * To use a different strictness setting create a {@link JsonWriter}, call its
* {@link #write(JsonWriter, Object)} for lenient writing. * {@link JsonWriter#setStrictness(Strictness)} method and then use
* {@link #write(JsonWriter, Object)} for writing.
* *
* @throws JsonIOException wrapping {@code IOException}s thrown by {@link #write(JsonWriter, Object)} * @throws JsonIOException wrapping {@code IOException}s thrown by {@link #write(JsonWriter, Object)}
* @param value the Java object to convert. May be null. * @param value the Java object to convert. May be null.
@ -253,10 +254,10 @@ public abstract class TypeAdapter<T> {
public abstract T read(JsonReader in) throws IOException; public abstract T read(JsonReader in) throws IOException;
/** /**
* Converts the JSON document in {@code in} to a Java object. Unlike Gson's * Converts the JSON document in {@code in} to a Java object. The strictness
* similar {@link Gson#fromJson(Reader, Class) fromJson} method, this * {@link Strictness#LEGACY_STRICT} is used for reading the JSON data. To use a different
* read is strict. Create a {@link JsonReader#setLenient(boolean) lenient} * strictness setting create a {@link JsonReader}, call its {@link JsonReader#setStrictness(Strictness)}
* {@code JsonReader} and call {@link #read(JsonReader)} for lenient reading. * method and then use {@link #read(JsonReader)} for reading.
* *
* <p>No exception is thrown if the JSON data has multiple top-level JSON elements, * <p>No exception is thrown if the JSON data has multiple top-level JSON elements,
* or if there is trailing data. * or if there is trailing data.
@ -270,10 +271,10 @@ public abstract class TypeAdapter<T> {
} }
/** /**
* Converts the JSON document in {@code json} to a Java object. Unlike Gson's * Converts the JSON document in {@code json} to a Java object. The strictness
* similar {@link Gson#fromJson(String, Class) fromJson} method, this read is * {@link Strictness#LEGACY_STRICT} is used for reading the JSON data. To use a different
* strict. Create a {@link JsonReader#setLenient(boolean) lenient} {@code * strictness setting create a {@link JsonReader}, call its {@link JsonReader#setStrictness(Strictness)}
* JsonReader} and call {@link #read(JsonReader)} for lenient reading. * method and then use {@link #read(JsonReader)} for reading.
* *
* <p>No exception is thrown if the JSON data has multiple top-level JSON elements, * <p>No exception is thrown if the JSON data has multiple top-level JSON elements,
* or if there is trailing data. * or if there is trailing data.

View File

@ -16,6 +16,7 @@
package com.google.gson.stream; package com.google.gson.stream;
import com.google.gson.Strictness;
import com.google.gson.internal.JsonReaderInternalAccess; import com.google.gson.internal.JsonReaderInternalAccess;
import com.google.gson.internal.TroubleshootingGuide; import com.google.gson.internal.TroubleshootingGuide;
import com.google.gson.internal.bind.JsonTreeReader; import com.google.gson.internal.bind.JsonTreeReader;
@ -27,7 +28,7 @@ import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
/** /**
* Reads a JSON (<a href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>) * Reads a JSON (<a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>)
* encoded value as a stream of tokens. This stream includes both literal * encoded value as a stream of tokens. This stream includes both literal
* values (strings, numbers, booleans, and nulls) as well as the begin and * values (strings, numbers, booleans, and nulls) as well as the begin and
* end delimiters of objects and arrays. The tokens are traversed in * end delimiters of objects and arrays. The tokens are traversed in
@ -181,7 +182,7 @@ import java.util.Objects;
* <p>Prefixing JSON files with <code>")]}'\n"</code> makes them non-executable * <p>Prefixing JSON files with <code>")]}'\n"</code> makes them non-executable
* by {@code <script>} tags, disarming the attack. Since the prefix is malformed * by {@code <script>} tags, disarming the attack. Since the prefix is malformed
* JSON, strict parsing fails when it is encountered. This class permits the * JSON, strict parsing fails when it is encountered. This class permits the
* non-execute prefix when {@link #setLenient(boolean) lenient parsing} is * non-execute prefix when {@linkplain #setStrictness(Strictness) lenient parsing} is
* enabled. * enabled.
* *
* <p>Each {@code JsonReader} may be used to read a single JSON stream. Instances * <p>Each {@code JsonReader} may be used to read a single JSON stream. Instances
@ -227,8 +228,7 @@ public class JsonReader implements Closeable {
/** The input JSON. */ /** The input JSON. */
private final Reader in; private final Reader in;
/** True to accept non-spec compliant JSON */ private Strictness strictness = Strictness.LEGACY_STRICT;
private boolean lenient = false;
static final int BUFFER_SIZE = 1024; static final int BUFFER_SIZE = 1024;
/** /**
@ -293,55 +293,101 @@ public class JsonReader implements Closeable {
} }
/** /**
* Configure this parser to be liberal in what it accepts. By default, * Sets the strictness of this reader.
* this parser is strict and only accepts JSON as specified by <a
* href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the
* parser to lenient causes it to ignore the following syntax errors:
* *
* <ul> * @deprecated Please use {@link #setStrictness(Strictness)} instead.
* <li>Streams that start with the <a href="#nonexecuteprefix">non-execute * {@code JsonReader.setLenient(true)} should be replaced by {@code JsonReader.setStrictness(Strictness.LENIENT)}
* prefix</a>, <code>")]}'\n"</code>. * and {@code JsonReader.setLenient(false)} should be replaced by {@code JsonReader.setStrictness(Strictness.LEGACY_STRICT)}.<br>
* <li>Streams that include multiple top-level values. With strict parsing, * However, if you used {@code setLenient(false)} before, you might prefer {@link Strictness#STRICT} now instead.
* each stream must contain exactly one top-level value.
* <li>Numbers may be {@link Double#isNaN() NaNs} or {@link
* Double#isInfinite() infinities}.
* <li>End of line comments starting with {@code //} or {@code #} and
* ending with a newline character.
* <li>C-style comments starting with {@code /*} and ending with
* {@code *}{@code /}. Such comments may not be nested.
* <li>Names that are unquoted or {@code 'single quoted'}.
* <li>Strings that are unquoted or {@code 'single quoted'}.
* <li>Array elements separated by {@code ;} instead of {@code ,}.
* <li>Unnecessary array separators. These are interpreted as if null
* was the omitted value.
* <li>Names and values separated by {@code =} or {@code =>} instead of
* {@code :}.
* <li>Name/value pairs separated by {@code ;} instead of {@code ,}.
* </ul>
* *
* <p>Note: Even in strict mode there are slight derivations from the JSON * @param lenient whether this reader should be lenient. If true, the strictness is set to {@link Strictness#LENIENT}.
* specification: * If false, the strictness is set to {@link Strictness#LEGACY_STRICT}.
* <ul> * @see #setStrictness(Strictness)
* <li>JsonReader allows the literals {@code true}, {@code false} and {@code null}
* to have any capitalization, for example {@code fAlSe}
* <li>JsonReader supports the escape sequence {@code \'}, representing a {@code '}
* <li>JsonReader supports the escape sequence <code>\<i>LF</i></code> (with {@code LF}
* being the Unicode character U+000A), resulting in a {@code LF} within the
* read JSON string
* <li>JsonReader allows unescaped control characters (U+0000 through U+001F)
* </ul>
*/ */
@Deprecated
@SuppressWarnings("InlineMeSuggester") // Don't specify @InlineMe, so caller with `setLenient(false)` becomes aware of new Strictness.STRICT
public final void setLenient(boolean lenient) { public final void setLenient(boolean lenient) {
this.lenient = lenient; setStrictness(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() { public final boolean isLenient() {
return lenient; return strictness == Strictness.LENIENT;
} }
/**
* Configures how liberal this parser is in what it accepts.
*
* <p>In {@linkplain Strictness#STRICT strict} mode, the
* parser only accepts JSON in accordance with <a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>.
* In {@linkplain Strictness#LEGACY_STRICT legacy strict} mode (the default), 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).</p>
*
* <dl>
* <dt>{@link Strictness#STRICT}</dt>
* <dd>
* In strict mode, only input compliant with RFC 8259 is accepted.
* </dd>
* <dt>{@link Strictness#LEGACY_STRICT}</dt>
* <dd>
* In legacy strict mode, the following departures from RFC 8259 are accepted:
* <ul>
* <li>JsonReader allows the literals {@code true}, {@code false} and {@code null}
* to have any capitalization, for example {@code fAlSe} or {@code NULL}
* <li>JsonReader supports the escape sequence {@code \'}, representing a {@code '} (single-quote)
* <li>JsonReader supports the escape sequence <code>\<i>LF</i></code> (with {@code LF}
* being the Unicode character {@code U+000A}), resulting in a {@code LF} within the
* read JSON string
* <li>JsonReader allows unescaped control characters ({@code U+0000} through {@code U+001F})
* </ul>
* </dd>
* <dt>{@link Strictness#LENIENT}</dt>
* <dd>
* In lenient mode, all input that is accepted in legacy strict mode is accepted in addition to the following
* departures from RFC 8259:
* <ul>
* <li>Streams that start with the <a href="#nonexecuteprefix">non-execute prefix</a>, {@code ")]}'\n"}
* <li>Streams that include multiple top-level values. With legacy strict or strict parsing,
* each stream must contain exactly one top-level value.
* <li>Numbers may be {@link Double#isNaN() NaNs} or {@link Double#isInfinite() infinities} represented by
* {@code NaN} and {@code (-)Infinity} respectively.
* <li>End of line comments starting with {@code //} or {@code #} and ending with a newline character.
* <li>C-style comments starting with {@code /*} and ending with
* {@code *}{@code /}. Such comments may not be nested.
* <li>Names that are unquoted or {@code 'single quoted'}.
* <li>Strings that are unquoted or {@code 'single quoted'}.
* <li>Array elements separated by {@code ;} instead of {@code ,}.
* <li>Unnecessary array separators. These are interpreted as if null
* was the omitted value.
* <li>Names and values separated by {@code =} or {@code =>} instead of
* {@code :}.
* <li>Name/value pairs separated by {@code ;} instead of {@code ,}.
* </ul>
* </dd>
* </dl>
*
* @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 * Consumes the next token from the JSON stream and asserts that it is the
* beginning of a new array. * beginning of a new array.
@ -540,7 +586,7 @@ public class JsonReader implements Closeable {
throw syntaxError("Expected ':'"); throw syntaxError("Expected ':'");
} }
} else if (peekStack == JsonScope.EMPTY_DOCUMENT) { } else if (peekStack == JsonScope.EMPTY_DOCUMENT) {
if (lenient) { if (strictness == Strictness.LENIENT) {
consumeNonExecutePrefix(); consumeNonExecutePrefix();
} }
stack[stackSize - 1] = JsonScope.NONEMPTY_DOCUMENT; stack[stackSize - 1] = JsonScope.NONEMPTY_DOCUMENT;
@ -610,6 +656,8 @@ public class JsonReader implements Closeable {
String keyword; String keyword;
String keywordUpper; String keywordUpper;
int peeking; int peeking;
// Look at the first letter to determine what keyword we are trying to match.
if (c == 't' || c == 'T') { if (c == 't' || c == 'T') {
keyword = "true"; keyword = "true";
keywordUpper = "TRUE"; keywordUpper = "TRUE";
@ -626,14 +674,18 @@ public class JsonReader implements Closeable {
return PEEKED_NONE; 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(); 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)) { if (pos + i >= limit && !fillBuffer(i + 1)) {
return PEEKED_NONE; return PEEKED_NONE;
} }
c = buffer[pos + i]; 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; return PEEKED_NONE;
} }
} }
@ -894,7 +946,7 @@ public class JsonReader implements Closeable {
* @throws NumberFormatException if the next literal value cannot be parsed * @throws NumberFormatException if the next literal value cannot be parsed
* as a double. * as a double.
* @throws MalformedJsonException if the next literal value is NaN or Infinity * @throws MalformedJsonException if the next literal value is NaN or Infinity
* and this reader is not {@link #setLenient(boolean) lenient}. * and this reader is not {@link #setStrictness(Strictness) lenient}.
*/ */
public double nextDouble() throws IOException { public double nextDouble() throws IOException {
int p = peeked; int p = peeked;
@ -921,7 +973,7 @@ public class JsonReader implements Closeable {
peeked = PEEKED_BUFFERED; peeked = PEEKED_BUFFERED;
double result = Double.parseDouble(peekedString); // don't catch this NumberFormatException. 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 syntaxError("JSON forbids NaN and infinities: " + result); throw syntaxError("JSON forbids NaN and infinities: " + result);
} }
peekedString = null; peekedString = null;
@ -1007,7 +1059,10 @@ public class JsonReader implements Closeable {
while (p < l) { while (p < l) {
int c = buffer[p++]; 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; pos = p;
int len = p - start - 1; int len = p - start - 1;
if (builder == null) { if (builder == null) {
@ -1461,8 +1516,8 @@ public class JsonReader implements Closeable {
} }
private void checkLenient() throws IOException { private void checkLenient() throws IOException {
if (!lenient) { if (strictness != Strictness.LENIENT) {
throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON"); throw syntaxError("Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON");
} }
} }
@ -1636,11 +1691,17 @@ public class JsonReader implements Closeable {
return '\f'; return '\f';
case '\n': case '\n':
if (strictness == Strictness.STRICT) {
throw syntaxError("Cannot escape a newline character in strict mode");
}
lineNumber++; lineNumber++;
lineStart = pos; lineStart = pos;
// fall-through // fall-through
case '\'': case '\'':
if (strictness == Strictness.STRICT) {
throw syntaxError("Invalid escaped character \"'\" in strict mode");
}
case '"': case '"':
case '\\': case '\\':
case '/': case '/':

View File

@ -26,6 +26,7 @@ import static com.google.gson.stream.JsonScope.NONEMPTY_OBJECT;
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gson.FormattingStyle; import com.google.gson.FormattingStyle;
import com.google.gson.Strictness;
import java.io.Closeable; import java.io.Closeable;
import java.io.Flushable; import java.io.Flushable;
import java.io.IOException; import java.io.IOException;
@ -39,7 +40,7 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
* Writes a JSON (<a href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>) * Writes a JSON (<a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>)
* encoded value to a stream, one token at a time. The stream includes both * 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 * literal values (strings, numbers, booleans and nulls) as well as the begin
* and end delimiters of objects and arrays. * and end delimiters of objects and arrays.
@ -141,7 +142,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]+)?"); 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 marks except for the characters that must be escaped:
* quotation mark, reverse solidus, and the control characters * quotation mark, reverse solidus, and the control characters
* (U+0000 through U+001F)." * (U+0000 through U+001F)."
@ -188,7 +189,7 @@ public class JsonWriter implements Closeable, Flushable {
private String formattedComma; private String formattedComma;
private boolean usesEmptyNewlineAndIndent; private boolean usesEmptyNewlineAndIndent;
private boolean lenient; private Strictness strictness = Strictness.LEGACY_STRICT;
private boolean htmlSafe; private boolean htmlSafe;
@ -268,28 +269,68 @@ public class JsonWriter implements Closeable, Flushable {
} }
/** /**
* Configure this writer to relax its syntax rules. By default, this writer * Sets the strictness of this writer.
* only emits well-formed JSON as specified by <a *
* href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>. Setting the writer * @deprecated Please use {@link #setStrictness(Strictness)} instead.
* to lenient permits the following: * {@code JsonWriter.setLenient(true)} should be replaced by {@code JsonWriter.setStrictness(Strictness.LENIENT)}
* <ul> * and {@code JsonWriter.setLenient(false)} should be replaced by {@code JsonWriter.setStrictness(Strictness.LEGACY_STRICT)}.<br>
* <li>Numbers may be {@link Double#isNaN() NaNs} or {@link * However, if you used {@code setLenient(false)} before, you might prefer {@link Strictness#STRICT} now instead.
* Double#isInfinite() infinities}. *
* </ul> * @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
@SuppressWarnings("InlineMeSuggester") // Don't specify @InlineMe, so caller with `setLenient(false)` becomes aware of new Strictness.STRICT
public final void setLenient(boolean lenient) { public final void setLenient(boolean lenient) {
this.lenient = lenient; setStrictness(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() { public boolean isLenient() {
return lenient; return strictness == Strictness.LENIENT;
} }
/** /**
* Configure this writer to emit JSON that's safe for direct inclusion in HTML * Configures how strict this writer is with regard to the syntax rules specified in <a
* href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>. By default, {@link Strictness#LEGACY_STRICT} is used.
*
* <dl>
* <dt>{@link Strictness#STRICT} &amp; {@link Strictness#LEGACY_STRICT}</dt>
* <dd>
* The behavior of these is currently identical. In these strictness modes, the writer only writes JSON
* in accordance with RFC 8259.
* </dd>
* <dt>{@link Strictness#LENIENT}</dt>
* <dd>
* 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.
* </dd>
* </dl>
*
* @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 the {@linkplain Strictness strictness} of this writer.
*
* @see #setStrictness(Strictness)
* @since $next-version$
*/
public final Strictness getStrictness() {
return strictness;
}
/**
* Configures this writer to emit JSON that's safe for direct inclusion in HTML
* and XML documents. This escapes the HTML characters {@code <}, {@code >}, * and XML documents. This escapes the HTML characters {@code <}, {@code >},
* {@code &} and {@code =} before writing them to the stream. Without this * {@code &} and {@code =} before writing them to the stream. Without this
* setting, your XML/HTML encoder should replace these characters with the * setting, your XML/HTML encoder should replace these characters with the
@ -436,7 +477,7 @@ public class JsonWriter implements Closeable, Flushable {
public JsonWriter name(String name) throws IOException { public JsonWriter name(String name) throws IOException {
Objects.requireNonNull(name, "name == null"); Objects.requireNonNull(name, "name == null");
if (deferredName != null) { if (deferredName != null) {
throw new IllegalStateException(); throw new IllegalStateException("Already wrote a name, expecting a value.");
} }
if (stackSize == 0) { if (stackSize == 0) {
throw new IllegalStateException("JsonWriter is closed."); throw new IllegalStateException("JsonWriter is closed.");
@ -545,18 +586,18 @@ public class JsonWriter implements Closeable, Flushable {
/** /**
* Encodes {@code value}. * Encodes {@code value}.
* *
* @param value a finite value, or if {@link #setLenient(boolean) lenient}, * @param value a finite value, or if {@link #setStrictness(Strictness) lenient},
* also {@link Float#isNaN() NaN} or {@link Float#isInfinite() * also {@link Float#isNaN() NaN} or {@link Float#isInfinite()
* infinity}. * infinity}.
* @return this writer. * @return this writer.
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is not {@link * @throws IllegalArgumentException if the value is NaN or Infinity and this writer is not {@link
* #setLenient(boolean) lenient}. * #setStrictness(Strictness) lenient}.
* @since 2.9.1 * @since 2.9.1
*/ */
@CanIgnoreReturnValue @CanIgnoreReturnValue
public JsonWriter value(float value) throws IOException { public JsonWriter value(float value) throws IOException {
writeDeferredName(); 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); throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
} }
beforeValue(); beforeValue();
@ -567,16 +608,16 @@ public class JsonWriter implements Closeable, Flushable {
/** /**
* Encodes {@code value}. * Encodes {@code value}.
* *
* @param value a finite value, or if {@link #setLenient(boolean) lenient}, * @param value a finite value, or if {@link #setStrictness(Strictness) lenient},
* also {@link Double#isNaN() NaN} or {@link Double#isInfinite() infinity}. * also {@link Double#isNaN() NaN} or {@link Double#isInfinite() infinity}.
* @return this writer. * @return this writer.
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is * @throws IllegalArgumentException if the value is NaN or Infinity and this writer is
* not {@link #setLenient(boolean) lenient}. * not {@link #setStrictness(Strictness) lenient}.
*/ */
@CanIgnoreReturnValue @CanIgnoreReturnValue
public JsonWriter value(double value) throws IOException { public JsonWriter value(double value) throws IOException {
writeDeferredName(); 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); throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
} }
beforeValue(); beforeValue();
@ -612,11 +653,11 @@ public class JsonWriter implements Closeable, Flushable {
* Encodes {@code value}. The value is written by directly writing the {@link Number#toString()} * Encodes {@code value}. The value is written by directly writing the {@link Number#toString()}
* result to JSON. Implementations must make sure that the result represents a valid JSON number. * result to JSON. Implementations must make sure that the result represents a valid JSON number.
* *
* @param value a finite value, or if {@link #setLenient(boolean) lenient}, * @param value a finite value, or if {@link #setStrictness(Strictness) lenient},
* also {@link Double#isNaN() NaN} or {@link Double#isInfinite() infinity}. * also {@link Double#isNaN() NaN} or {@link Double#isInfinite() infinity}.
* @return this writer. * @return this writer.
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is * @throws IllegalArgumentException if the value is NaN or Infinity and this writer is
* not {@link #setLenient(boolean) lenient}; or if the {@code toString()} result is not a * not {@link #setStrictness(Strictness) lenient}; or if the {@code toString()} result is not a
* valid JSON number. * valid JSON number.
*/ */
@CanIgnoreReturnValue @CanIgnoreReturnValue
@ -628,7 +669,7 @@ public class JsonWriter implements Closeable, Flushable {
writeDeferredName(); writeDeferredName();
String string = value.toString(); String string = value.toString();
if (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN")) { 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); throw new IllegalArgumentException("Numeric values must be finite, but was " + string);
} }
} else { } else {
@ -737,7 +778,7 @@ public class JsonWriter implements Closeable, Flushable {
private void beforeValue() throws IOException { private void beforeValue() throws IOException {
switch (peek()) { switch (peek()) {
case NONEMPTY_DOCUMENT: case NONEMPTY_DOCUMENT:
if (!lenient) { if (strictness != Strictness.LENIENT) {
throw new IllegalStateException( throw new IllegalStateException(
"JSON must have only one top-level value."); "JSON must have only one top-level value.");
} }

View File

@ -16,11 +16,12 @@
package com.google.gson.stream; package com.google.gson.stream;
import com.google.gson.Strictness;
import java.io.IOException; import java.io.IOException;
/** /**
* Thrown when a reader encounters malformed JSON. Some syntax errors can be * Thrown when a reader encounters malformed JSON. Some syntax errors can be
* ignored by calling {@link JsonReader#setLenient(boolean)}. * ignored by using {@link Strictness#LENIENT} for {@link JsonReader#setStrictness(Strictness)}.
*/ */
public final class MalformedJsonException extends IOException { public final class MalformedJsonException extends IOException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@ -22,6 +22,8 @@ import static org.junit.Assert.fail;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.lang.reflect.Type; import java.lang.reflect.Type;
@ -224,4 +226,32 @@ public class GsonBuilderTest {
assertThat(e).hasMessageThat().isEqualTo("Invalid version: -0.1"); 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);
}
@SuppressWarnings({"deprecation", "InlineMeInliner"}) // for GsonBuilder.setLenient
@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);
}
} }

View File

@ -59,11 +59,16 @@ public final class GsonTest {
private static final ToNumberStrategy CUSTOM_OBJECT_TO_NUMBER_STRATEGY = ToNumberPolicy.DOUBLE; private static final ToNumberStrategy CUSTOM_OBJECT_TO_NUMBER_STRATEGY = ToNumberPolicy.DOUBLE;
private static final ToNumberStrategy CUSTOM_NUMBER_TO_NUMBER_STRATEGY = ToNumberPolicy.LAZILY_PARSED_NUMBER; private static final ToNumberStrategy CUSTOM_NUMBER_TO_NUMBER_STRATEGY = ToNumberPolicy.LAZILY_PARSED_NUMBER;
@Test
public void testStrictnessDefault() {
assertThat(new Gson().strictness).isNull();
}
@Test @Test
public void testOverridesDefaultExcluder() { public void testOverridesDefaultExcluder() {
Gson gson = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY, Gson gson = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false, new HashMap<Type, InstanceCreator<?>>(), true, false, true, false,
FormattingStyle.PRETTY, true, false, true, FormattingStyle.PRETTY, Strictness.LENIENT, false, true,
LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(), DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
@ -80,7 +85,7 @@ public final class GsonTest {
public void testClonedTypeAdapterFactoryListsAreIndependent() { public void testClonedTypeAdapterFactoryListsAreIndependent() {
Gson original = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY, Gson original = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false, new HashMap<Type, InstanceCreator<?>>(), true, false, true, false,
FormattingStyle.PRETTY, true, false, true, FormattingStyle.PRETTY, Strictness.LENIENT, false, true,
LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(), DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
@ -286,6 +291,7 @@ public final class GsonTest {
assertThat(writer.toString()).isEqualTo("{\"\\u003ctest2\":true}"); assertThat(writer.toString()).isEqualTo("{\"\\u003ctest2\":true}");
} }
@SuppressWarnings({"deprecation", "InlineMeInliner"}) // for GsonBuilder.setLenient
@Test @Test
public void testNewJsonWriter_Custom() throws IOException { public void testNewJsonWriter_Custom() throws IOException {
StringWriter writer = new StringWriter(); StringWriter writer = new StringWriter();
@ -319,6 +325,7 @@ public final class GsonTest {
jsonReader.close(); jsonReader.close();
} }
@SuppressWarnings({"deprecation", "InlineMeInliner"}) // for GsonBuilder.setLenient
@Test @Test
public void testNewJsonReader_Custom() throws IOException { public void testNewJsonReader_Custom() throws IOException {
String json = "test"; // String without quotes String json = "test"; // String without quotes

View File

@ -163,7 +163,7 @@ public class JsonParserTest {
CharArrayReader reader = new CharArrayReader(writer.toCharArray()); CharArrayReader reader = new CharArrayReader(writer.toCharArray());
JsonReader parser = new JsonReader(reader); JsonReader parser = new JsonReader(reader);
parser.setLenient(true); parser.setStrictness(Strictness.LENIENT);
JsonElement element1 = Streams.parse(parser); JsonElement element1 = Streams.parse(parser);
JsonElement element2 = Streams.parse(parser); JsonElement element2 = Streams.parse(parser);
BagOfPrimitives actualOne = gson.fromJson(element1, BagOfPrimitives.class); BagOfPrimitives actualOne = gson.fromJson(element1, BagOfPrimitives.class);
@ -171,4 +171,16 @@ public class JsonParserTest {
BagOfPrimitives actualTwo = gson.fromJson(element2, BagOfPrimitives.class); BagOfPrimitives actualTwo = gson.fromJson(element2, BagOfPrimitives.class);
assertThat(actualTwo.stringValue).isEqualTo("two"); assertThat(actualTwo.stringValue).isEqualTo("two");
} }
@Test
public void testStrict() {
JsonReader reader = new JsonReader(new StringReader("faLsE"));
Strictness strictness = Strictness.STRICT;
// Strictness is ignored by JsonParser later; always parses in lenient mode
reader.setStrictness(strictness);
assertThat(JsonParser.parseReader(reader)).isEqualTo(new JsonPrimitive(false));
// Original strictness was restored
assertThat(reader.getStrictness()).isEqualTo(strictness);
}
} }

View File

@ -81,6 +81,7 @@ public final class MixedStreamTest {
jsonReader.endArray(); jsonReader.endArray();
} }
@SuppressWarnings("deprecation") // for JsonReader.setLenient
@Test @Test
public void testReaderDoesNotMutateState() throws IOException { public void testReaderDoesNotMutateState() throws IOException {
Gson gson = new Gson(); Gson gson = new Gson();
@ -96,6 +97,7 @@ public final class MixedStreamTest {
assertThat(jsonReader.isLenient()).isTrue(); assertThat(jsonReader.isLenient()).isTrue();
} }
@SuppressWarnings("deprecation") // for JsonWriter.setLenient
@Test @Test
public void testWriteDoesNotMutateState() throws IOException { public void testWriteDoesNotMutateState() throws IOException {
Gson gson = new Gson(); Gson gson = new Gson();

View File

@ -70,17 +70,17 @@ public class ToNumberPolicyTest {
e = assertThrows(MalformedJsonException.class, () -> strategy.readNumber(fromString("NaN"))); e = assertThrows(MalformedJsonException.class, () -> strategy.readNumber(fromString("NaN")));
assertThat(e).hasMessageThat().isEqualTo( assertThat(e).hasMessageThat().isEqualTo(
"Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $" "Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON at line 1 column 1 path $"
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json"); + "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json");
e = assertThrows(MalformedJsonException.class, () -> strategy.readNumber(fromString("Infinity"))); e = assertThrows(MalformedJsonException.class, () -> strategy.readNumber(fromString("Infinity")));
assertThat(e).hasMessageThat().isEqualTo( assertThat(e).hasMessageThat().isEqualTo(
"Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $" "Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON at line 1 column 1 path $"
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json"); + "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json");
e = assertThrows(MalformedJsonException.class, () -> strategy.readNumber(fromString("-Infinity"))); e = assertThrows(MalformedJsonException.class, () -> strategy.readNumber(fromString("-Infinity")));
assertThat(e).hasMessageThat().isEqualTo( assertThat(e).hasMessageThat().isEqualTo(
"Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $" "Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON at line 1 column 1 path $"
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json"); + "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json");
} }
@ -120,7 +120,7 @@ public class ToNumberPolicyTest {
private static JsonReader fromStringLenient(String json) { private static JsonReader fromStringLenient(String json) {
JsonReader jsonReader = fromString(json); JsonReader jsonReader = fromString(json);
jsonReader.setLenient(true); jsonReader.setStrictness(Strictness.LENIENT);
return jsonReader; return jsonReader;
} }
} }

View File

@ -28,7 +28,6 @@ import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

View File

@ -32,6 +32,7 @@ public class LeniencyTest {
private Gson gson; private Gson gson;
@SuppressWarnings({"deprecation", "InlineMeInliner"}) // for GsonBuilder.setLenient
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
gson = new GsonBuilder().setLenient().create(); gson = new GsonBuilder().setLenient().create();

View File

@ -26,7 +26,6 @@ import java.lang.reflect.Type;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.junit.Before; import org.junit.Before;

View File

@ -28,7 +28,6 @@ import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.common.TestTypes.ClassOverridingEquals; import com.google.gson.common.TestTypes.ClassOverridingEquals;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

View File

@ -22,6 +22,7 @@ import static org.junit.Assert.fail;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive; import com.google.gson.JsonPrimitive;
import com.google.gson.Strictness;
import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonToken;
import com.google.gson.stream.MalformedJsonException; import com.google.gson.stream.MalformedJsonException;
import java.io.IOException; import java.io.IOException;
@ -45,7 +46,7 @@ public final class JsonElementReaderTest {
public void testLenientNansAndInfinities() throws IOException { public void testLenientNansAndInfinities() throws IOException {
JsonElement element = JsonParser.parseString("[NaN, -Infinity, Infinity]"); JsonElement element = JsonParser.parseString("[NaN, -Infinity, Infinity]");
JsonTreeReader reader = new JsonTreeReader(element); JsonTreeReader reader = new JsonTreeReader(element);
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(Double.isNaN(reader.nextDouble())).isTrue(); assertThat(Double.isNaN(reader.nextDouble())).isTrue();
assertThat(reader.nextDouble()).isEqualTo(Double.NEGATIVE_INFINITY); assertThat(reader.nextDouble()).isEqualTo(Double.NEGATIVE_INFINITY);
@ -57,7 +58,7 @@ public final class JsonElementReaderTest {
public void testStrictNansAndInfinities() throws IOException { public void testStrictNansAndInfinities() throws IOException {
JsonElement element = JsonParser.parseString("[NaN, -Infinity, Infinity]"); JsonElement element = JsonParser.parseString("[NaN, -Infinity, Infinity]");
JsonTreeReader reader = new JsonTreeReader(element); JsonTreeReader reader = new JsonTreeReader(element);
reader.setLenient(false); reader.setStrictness(Strictness.LEGACY_STRICT);
reader.beginArray(); reader.beginArray();
try { try {
reader.nextDouble(); reader.nextDouble();

View File

@ -144,7 +144,7 @@ public class JsonTreeReaderTest {
*/ */
@Test @Test
public void testOverrides() { public void testOverrides() {
List<String> ignoredMethods = Arrays.asList("setLenient(boolean)", "isLenient()"); List<String> ignoredMethods = Arrays.asList("setLenient(boolean)", "isLenient()", "setStrictness(com.google.gson.Strictness)", "getStrictness()");
MoreAsserts.assertOverridesMethods(JsonReader.class, JsonTreeReader.class, ignoredMethods); MoreAsserts.assertOverridesMethods(JsonReader.class, JsonTreeReader.class, ignoredMethods);
} }
} }

View File

@ -21,6 +21,7 @@ import static org.junit.Assert.fail;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonNull; import com.google.gson.JsonNull;
import com.google.gson.Strictness;
import com.google.gson.common.MoreAsserts; import com.google.gson.common.MoreAsserts;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import java.io.IOException; import java.io.IOException;
@ -86,7 +87,7 @@ public final class JsonTreeWriterTest {
@Test @Test
public void testWriteAfterClose() throws Exception { public void testWriteAfterClose() throws Exception {
JsonTreeWriter writer = new JsonTreeWriter(); JsonTreeWriter writer = new JsonTreeWriter();
writer.setLenient(true); writer.setStrictness(Strictness.LENIENT);
writer.beginArray(); writer.beginArray();
writer.value("A"); writer.value("A");
writer.endArray(); writer.endArray();
@ -101,12 +102,13 @@ public final class JsonTreeWriterTest {
@Test @Test
public void testPrematureClose() throws Exception { public void testPrematureClose() throws Exception {
JsonTreeWriter writer = new JsonTreeWriter(); JsonTreeWriter writer = new JsonTreeWriter();
writer.setLenient(true); writer.setStrictness(Strictness.LENIENT);
writer.beginArray(); writer.beginArray();
try { try {
writer.close(); writer.close();
fail(); fail();
} catch (IOException expected) { } catch (IOException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Incomplete document");
} }
} }
@ -174,7 +176,7 @@ public final class JsonTreeWriterTest {
@Test @Test
public void testLenientNansAndInfinities() throws IOException { public void testLenientNansAndInfinities() throws IOException {
JsonTreeWriter writer = new JsonTreeWriter(); JsonTreeWriter writer = new JsonTreeWriter();
writer.setLenient(true); writer.setStrictness(Strictness.LENIENT);
writer.beginArray(); writer.beginArray();
writer.value(Float.NaN); writer.value(Float.NaN);
writer.value(Float.NEGATIVE_INFINITY); writer.value(Float.NEGATIVE_INFINITY);
@ -189,7 +191,7 @@ public final class JsonTreeWriterTest {
@Test @Test
public void testStrictNansAndInfinities() throws IOException { public void testStrictNansAndInfinities() throws IOException {
JsonTreeWriter writer = new JsonTreeWriter(); JsonTreeWriter writer = new JsonTreeWriter();
writer.setLenient(false); writer.setStrictness(Strictness.LEGACY_STRICT);
writer.beginArray(); writer.beginArray();
try { try {
writer.value(Float.NaN); writer.value(Float.NaN);
@ -226,7 +228,7 @@ public final class JsonTreeWriterTest {
@Test @Test
public void testStrictBoxedNansAndInfinities() throws IOException { public void testStrictBoxedNansAndInfinities() throws IOException {
JsonTreeWriter writer = new JsonTreeWriter(); JsonTreeWriter writer = new JsonTreeWriter();
writer.setLenient(false); writer.setStrictness(Strictness.LEGACY_STRICT);
writer.beginArray(); writer.beginArray();
try { try {
writer.value(Float.valueOf(Float.NaN)); writer.value(Float.valueOf(Float.NaN));
@ -280,6 +282,7 @@ public final class JsonTreeWriterTest {
public void testOverrides() { public void testOverrides() {
List<String> ignoredMethods = Arrays.asList( List<String> ignoredMethods = Arrays.asList(
"setLenient(boolean)", "isLenient()", "setLenient(boolean)", "isLenient()",
"setStrictness(com.google.gson.Strictness)", "getStrictness()",
"setIndent(java.lang.String)", "setIndent(java.lang.String)",
"setHtmlSafe(boolean)", "isHtmlSafe()", "setHtmlSafe(boolean)", "isHtmlSafe()",
"setFormattingStyle(com.google.gson.FormattingStyle)", "getFormattingStyle()", "setFormattingStyle(com.google.gson.FormattingStyle)", "getFormattingStyle()",

View File

@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue; import static org.junit.Assume.assumeTrue;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.Strictness;
import com.google.gson.internal.Streams; import com.google.gson.internal.Streams;
import com.google.gson.internal.bind.JsonTreeReader; import com.google.gson.internal.bind.JsonTreeReader;
import java.io.IOException; import java.io.IOException;
@ -201,7 +202,7 @@ public class JsonReaderPathTest {
assumeTrue(factory == Factory.STRING_READER); assumeTrue(factory == Factory.STRING_READER);
JsonReader reader = factory.create("[][]"); JsonReader reader = factory.create("[][]");
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
reader.endArray(); reader.endArray();
assertThat(reader.getPreviousPath()).isEqualTo("$"); assertThat(reader.getPreviousPath()).isEqualTo("$");

View File

@ -26,19 +26,158 @@ import static com.google.gson.stream.JsonToken.NAME;
import static com.google.gson.stream.JsonToken.NULL; import static com.google.gson.stream.JsonToken.NULL;
import static com.google.gson.stream.JsonToken.NUMBER; import static com.google.gson.stream.JsonToken.NUMBER;
import static com.google.gson.stream.JsonToken.STRING; import static com.google.gson.stream.JsonToken.STRING;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import com.google.gson.Strictness;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.io.StringReader; import java.io.StringReader;
import java.util.Arrays; import java.util.Arrays;
import com.google.gson.stream.JsonToken;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
@SuppressWarnings("resource") @SuppressWarnings("resource")
public final class JsonReaderTest { public final class JsonReaderTest {
@SuppressWarnings("deprecation") // for JsonReader.setLenient
@Test
public void testSetLenientTrue() {
JsonReader reader = new JsonReader(reader("{}"));
reader.setLenient(true);
assertThat(reader.getStrictness()).isEqualTo(Strictness.LENIENT);
}
@SuppressWarnings("deprecation") // for JsonReader.setLenient
@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).hasMessageThat().startsWith("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 = "\"\0\"";
JsonReader reader = new JsonReader(reader(json));
reader.setStrictness(Strictness.STRICT);
IOException expected = assertThrows(IOException.class, reader::nextString);
assertThat(expected).hasMessageThat().startsWith("Unescaped control characters (\\u0000-\\u001F) are not allowed in strict mode");
json = "\"\t\"";
reader = new JsonReader(reader(json));
reader.setStrictness(Strictness.STRICT);
expected = assertThrows(IOException.class, reader::nextString);
assertThat(expected).hasMessageThat().startsWith("Unescaped control characters (\\u0000-\\u001F) are not allowed in strict mode");
json = "\"\u001F\"";
reader = new JsonReader(reader(json));
reader.setStrictness(Strictness.STRICT);
expected = assertThrows(IOException.class, reader::nextString);
assertThat(expected).hasMessageThat().startsWith("Unescaped control characters (\\u0000-\\u001F) are not allowed in strict mode");
}
@Test
public void testStrictModeAllowsOtherControlCharacters() throws IOException {
// JSON specification only forbids control characters U+0000 - U+001F, other control characters should be allowed
String json = "\"\u007F\u009F\"";
JsonReader reader = new JsonReader(reader(json));
reader.setStrictness(Strictness.STRICT);
assertThat(reader.nextString()).isEqualTo("\u007F\u009F");
}
@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::nextBoolean);
assertThat(expected).hasMessageThat().startsWith("Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed" +
" JSON at line 1 column 1 path $");
reader = new JsonReader(reader("True"));
reader.setStrictness(Strictness.STRICT);
expected = assertThrows(IOException.class, reader::nextBoolean);
assertThat(expected).hasMessageThat().startsWith("Use JsonReader.setStrictness(Strictness.LENIENT) 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().startsWith("Use JsonReader.setStrictness(Strictness.LENIENT) 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().startsWith("Use JsonReader.setStrictness(Strictness.LENIENT) 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().startsWith("Use JsonReader.setStrictness(Strictness.LENIENT) 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().startsWith("Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed" +
" JSON at line 1 column 1 path $");
}
@Test @Test
public void testReadArray() throws IOException { public void testReadArray() throws IOException {
JsonReader reader = new JsonReader(reader("[true, true]")); JsonReader reader = new JsonReader(reader("[true, true]"));
@ -167,7 +306,7 @@ public final class JsonReaderTest {
@Test @Test
public void testSkipObjectNameSingleQuoted() throws IOException { public void testSkipObjectNameSingleQuoted() throws IOException {
JsonReader reader = new JsonReader(reader("{'a': 1}")); JsonReader reader = new JsonReader(reader("{'a': 1}"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginObject(); reader.beginObject();
reader.skipValue(); reader.skipValue();
assertThat(reader.peek()).isEqualTo(JsonToken.NUMBER); assertThat(reader.peek()).isEqualTo(JsonToken.NUMBER);
@ -178,7 +317,7 @@ public final class JsonReaderTest {
@Test @Test
public void testSkipObjectNameUnquoted() throws IOException { public void testSkipObjectNameUnquoted() throws IOException {
JsonReader reader = new JsonReader(reader("{a: 1}")); JsonReader reader = new JsonReader(reader("{a: 1}"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginObject(); reader.beginObject();
reader.skipValue(); reader.skipValue();
assertThat(reader.peek()).isEqualTo(JsonToken.NUMBER); assertThat(reader.peek()).isEqualTo(JsonToken.NUMBER);
@ -350,6 +489,50 @@ public final class JsonReaderTest {
assertThat(reader.peek()).isEqualTo(JsonToken.END_DOCUMENT); 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");
// And even in STRICT mode U+2028 and U+2029 are not considered control characters
// and can appear unescaped in JSON string
String jsonValid2028And2029 = "\"whatever\u2028\u2029\"";
JsonReader readerValid2028And2029 = new JsonReader(reader(jsonValid2028And2029));
readerValid2028And2029.setStrictness(Strictness.STRICT);
assertThat(readerValid2028And2029.nextString()).isEqualTo("whatever\u2028\u2029");
}
@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().startsWith("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 @Test
public void testUnescapingInvalidCharacters() throws IOException { public void testUnescapingInvalidCharacters() throws IOException {
String json = "[\"\\u000g\"]"; String json = "[\"\\u000g\"]";
@ -476,7 +659,7 @@ public final class JsonReaderTest {
public void testLenientNonFiniteDoubles() throws IOException { public void testLenientNonFiniteDoubles() throws IOException {
String json = "[NaN, -Infinity, Infinity]"; String json = "[NaN, -Infinity, Infinity]";
JsonReader reader = new JsonReader(reader(json)); JsonReader reader = new JsonReader(reader(json));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(Double.isNaN(reader.nextDouble())).isTrue(); assertThat(Double.isNaN(reader.nextDouble())).isTrue();
assertThat(reader.nextDouble()).isEqualTo(Double.NEGATIVE_INFINITY); assertThat(reader.nextDouble()).isEqualTo(Double.NEGATIVE_INFINITY);
@ -488,7 +671,7 @@ public final class JsonReaderTest {
public void testLenientQuotedNonFiniteDoubles() throws IOException { public void testLenientQuotedNonFiniteDoubles() throws IOException {
String json = "[\"NaN\", \"-Infinity\", \"Infinity\"]"; String json = "[\"NaN\", \"-Infinity\", \"Infinity\"]";
JsonReader reader = new JsonReader(reader(json)); JsonReader reader = new JsonReader(reader(json));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(Double.isNaN(reader.nextDouble())).isTrue(); assertThat(Double.isNaN(reader.nextDouble())).isTrue();
assertThat(reader.nextDouble()).isEqualTo(Double.NEGATIVE_INFINITY); assertThat(reader.nextDouble()).isEqualTo(Double.NEGATIVE_INFINITY);
@ -591,7 +774,7 @@ public final class JsonReaderTest {
@Test @Test
public void testPeekingUnquotedStringsPrefixedWithBooleans() throws IOException { public void testPeekingUnquotedStringsPrefixedWithBooleans() throws IOException {
JsonReader reader = new JsonReader(reader("[truey]")); JsonReader reader = new JsonReader(reader("[truey]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.peek()).isEqualTo(STRING); assertThat(reader.peek()).isEqualTo(STRING);
try { try {
@ -651,7 +834,7 @@ public final class JsonReaderTest {
private void assertNotANumber(String s) throws IOException { private void assertNotANumber(String s) throws IOException {
JsonReader reader = new JsonReader(reader(s)); JsonReader reader = new JsonReader(reader(s));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
assertThat(reader.peek()).isEqualTo(JsonToken.STRING); assertThat(reader.peek()).isEqualTo(JsonToken.STRING);
assertThat(reader.nextString()).isEqualTo(s); assertThat(reader.nextString()).isEqualTo(s);
@ -660,14 +843,14 @@ public final class JsonReaderTest {
strictReader.nextDouble(); strictReader.nextDouble();
fail("Should have failed reading " + s + " as double"); fail("Should have failed reading " + s + " as double");
} catch (MalformedJsonException e) { } catch (MalformedJsonException e) {
assertThat(e).hasMessageThat().startsWith("Use JsonReader.setLenient(true) to accept malformed JSON"); assertThat(e).hasMessageThat().startsWith("Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON");
} }
} }
@Test @Test
public void testPeekingUnquotedStringsPrefixedWithIntegers() throws IOException { public void testPeekingUnquotedStringsPrefixedWithIntegers() throws IOException {
JsonReader reader = new JsonReader(reader("[12.34e5x]")); JsonReader reader = new JsonReader(reader("[12.34e5x]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.peek()).isEqualTo(STRING); assertThat(reader.peek()).isEqualTo(STRING);
try { try {
@ -681,7 +864,7 @@ public final class JsonReaderTest {
@Test @Test
public void testPeekLongMinValue() throws IOException { public void testPeekLongMinValue() throws IOException {
JsonReader reader = new JsonReader(reader("[-9223372036854775808]")); JsonReader reader = new JsonReader(reader("[-9223372036854775808]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.peek()).isEqualTo(NUMBER); assertThat(reader.peek()).isEqualTo(NUMBER);
assertThat(reader.nextLong()).isEqualTo(-9223372036854775808L); assertThat(reader.nextLong()).isEqualTo(-9223372036854775808L);
@ -690,7 +873,7 @@ public final class JsonReaderTest {
@Test @Test
public void testPeekLongMaxValue() throws IOException { public void testPeekLongMaxValue() throws IOException {
JsonReader reader = new JsonReader(reader("[9223372036854775807]")); JsonReader reader = new JsonReader(reader("[9223372036854775807]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.peek()).isEqualTo(NUMBER); assertThat(reader.peek()).isEqualTo(NUMBER);
assertThat(reader.nextLong()).isEqualTo(9223372036854775807L); assertThat(reader.nextLong()).isEqualTo(9223372036854775807L);
@ -699,7 +882,7 @@ public final class JsonReaderTest {
@Test @Test
public void testLongLargerThanMaxLongThatWrapsAround() throws IOException { public void testLongLargerThanMaxLongThatWrapsAround() throws IOException {
JsonReader reader = new JsonReader(reader("[22233720368547758070]")); JsonReader reader = new JsonReader(reader("[22233720368547758070]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.peek()).isEqualTo(NUMBER); assertThat(reader.peek()).isEqualTo(NUMBER);
try { try {
@ -712,7 +895,7 @@ public final class JsonReaderTest {
@Test @Test
public void testLongLargerThanMinLongThatWrapsAround() throws IOException { public void testLongLargerThanMinLongThatWrapsAround() throws IOException {
JsonReader reader = new JsonReader(reader("[-22233720368547758070]")); JsonReader reader = new JsonReader(reader("[-22233720368547758070]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.peek()).isEqualTo(NUMBER); assertThat(reader.peek()).isEqualTo(NUMBER);
try { try {
@ -728,7 +911,7 @@ public final class JsonReaderTest {
@Test @Test
public void testNegativeZero() throws Exception { public void testNegativeZero() throws Exception {
JsonReader reader = new JsonReader(reader("[-0]")); JsonReader reader = new JsonReader(reader("[-0]"));
reader.setLenient(false); reader.setStrictness(Strictness.LEGACY_STRICT);
reader.beginArray(); reader.beginArray();
assertThat(reader.peek()).isEqualTo(NUMBER); assertThat(reader.peek()).isEqualTo(NUMBER);
assertThat(reader.nextString()).isEqualTo("-0"); assertThat(reader.nextString()).isEqualTo("-0");
@ -742,7 +925,7 @@ public final class JsonReaderTest {
@Ignore @Ignore
public void testPeekLargerThanLongMaxValue() throws IOException { public void testPeekLargerThanLongMaxValue() throws IOException {
JsonReader reader = new JsonReader(reader("[9223372036854775808]")); JsonReader reader = new JsonReader(reader("[9223372036854775808]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.peek()).isEqualTo(NUMBER); assertThat(reader.peek()).isEqualTo(NUMBER);
try { try {
@ -762,7 +945,7 @@ public final class JsonReaderTest {
@SuppressWarnings("FloatingPointLiteralPrecision") @SuppressWarnings("FloatingPointLiteralPrecision")
double d = -9223372036854775809d; double d = -9223372036854775809d;
JsonReader reader = new JsonReader(reader("[-9223372036854775809]")); JsonReader reader = new JsonReader(reader("[-9223372036854775809]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.peek()).isEqualTo(NUMBER); assertThat(reader.peek()).isEqualTo(NUMBER);
try { try {
@ -792,7 +975,7 @@ public final class JsonReaderTest {
@SuppressWarnings("FloatingPointLiteralPrecision") @SuppressWarnings("FloatingPointLiteralPrecision")
double d = -92233720368547758080d; double d = -92233720368547758080d;
JsonReader reader = new JsonReader(reader("[-92233720368547758080]")); JsonReader reader = new JsonReader(reader("[-92233720368547758080]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.peek()).isEqualTo(NUMBER); assertThat(reader.peek()).isEqualTo(NUMBER);
try { try {
@ -806,7 +989,7 @@ public final class JsonReaderTest {
@Test @Test
public void testQuotedNumberWithEscape() throws IOException { public void testQuotedNumberWithEscape() throws IOException {
JsonReader reader = new JsonReader(reader("[\"12\\u00334\"]")); JsonReader reader = new JsonReader(reader("[\"12\\u00334\"]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.peek()).isEqualTo(STRING); assertThat(reader.peek()).isEqualTo(STRING);
assertThat(reader.nextInt()).isEqualTo(1234); assertThat(reader.nextInt()).isEqualTo(1234);
@ -1021,13 +1204,13 @@ public final class JsonReaderTest {
@Test @Test
public void testLenientNameValueSeparator() throws IOException { public void testLenientNameValueSeparator() throws IOException {
JsonReader reader = new JsonReader(reader("{\"a\"=true}")); JsonReader reader = new JsonReader(reader("{\"a\"=true}"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginObject(); reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a"); assertThat(reader.nextName()).isEqualTo("a");
assertThat(reader.nextBoolean()).isTrue(); assertThat(reader.nextBoolean()).isTrue();
reader = new JsonReader(reader("{\"a\"=>true}")); reader = new JsonReader(reader("{\"a\"=>true}"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginObject(); reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a"); assertThat(reader.nextName()).isEqualTo("a");
assertThat(reader.nextBoolean()).isTrue(); assertThat(reader.nextBoolean()).isTrue();
@ -1109,17 +1292,17 @@ public final class JsonReaderTest {
@Test @Test
public void testLenientComments() throws IOException { public void testLenientComments() throws IOException {
JsonReader reader = new JsonReader(reader("[// comment \n true]")); JsonReader reader = new JsonReader(reader("[// comment \n true]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.nextBoolean()).isTrue(); assertThat(reader.nextBoolean()).isTrue();
reader = new JsonReader(reader("[# comment \n true]")); reader = new JsonReader(reader("[# comment \n true]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.nextBoolean()).isTrue(); assertThat(reader.nextBoolean()).isTrue();
reader = new JsonReader(reader("[/* comment */ true]")); reader = new JsonReader(reader("[/* comment */ true]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.nextBoolean()).isTrue(); assertThat(reader.nextBoolean()).isTrue();
} }
@ -1169,7 +1352,7 @@ public final class JsonReaderTest {
@Test @Test
public void testLenientUnquotedNames() throws IOException { public void testLenientUnquotedNames() throws IOException {
JsonReader reader = new JsonReader(reader("{a:true}")); JsonReader reader = new JsonReader(reader("{a:true}"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginObject(); reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a"); assertThat(reader.nextName()).isEqualTo("a");
} }
@ -1201,7 +1384,7 @@ public final class JsonReaderTest {
@Test @Test
public void testLenientSingleQuotedNames() throws IOException { public void testLenientSingleQuotedNames() throws IOException {
JsonReader reader = new JsonReader(reader("{'a':true}")); JsonReader reader = new JsonReader(reader("{'a':true}"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginObject(); reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a"); assertThat(reader.nextName()).isEqualTo("a");
} }
@ -1245,7 +1428,7 @@ public final class JsonReaderTest {
@Test @Test
public void testLenientUnquotedStrings() throws IOException { public void testLenientUnquotedStrings() throws IOException {
JsonReader reader = new JsonReader(reader("[a]")); JsonReader reader = new JsonReader(reader("[a]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.nextString()).isEqualTo("a"); assertThat(reader.nextString()).isEqualTo("a");
} }
@ -1265,7 +1448,7 @@ public final class JsonReaderTest {
@Test @Test
public void testLenientSingleQuotedStrings() throws IOException { public void testLenientSingleQuotedStrings() throws IOException {
JsonReader reader = new JsonReader(reader("['a']")); JsonReader reader = new JsonReader(reader("['a']"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.nextString()).isEqualTo("a"); assertThat(reader.nextString()).isEqualTo("a");
} }
@ -1297,7 +1480,7 @@ public final class JsonReaderTest {
@Test @Test
public void testLenientSemicolonDelimitedArray() throws IOException { public void testLenientSemicolonDelimitedArray() throws IOException {
JsonReader reader = new JsonReader(reader("[true;true]")); JsonReader reader = new JsonReader(reader("[true;true]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.nextBoolean()).isTrue(); assertThat(reader.nextBoolean()).isTrue();
assertThat(reader.nextBoolean()).isTrue(); assertThat(reader.nextBoolean()).isTrue();
@ -1331,7 +1514,7 @@ public final class JsonReaderTest {
@Test @Test
public void testLenientSemicolonDelimitedNameValuePair() throws IOException { public void testLenientSemicolonDelimitedNameValuePair() throws IOException {
JsonReader reader = new JsonReader(reader("{\"a\":true;\"b\":true}")); JsonReader reader = new JsonReader(reader("{\"a\":true;\"b\":true}"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginObject(); reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a"); assertThat(reader.nextName()).isEqualTo("a");
assertThat(reader.nextBoolean()).isTrue(); assertThat(reader.nextBoolean()).isTrue();
@ -1395,7 +1578,7 @@ public final class JsonReaderTest {
@Test @Test
public void testLenientUnnecessaryArraySeparators() throws IOException { public void testLenientUnnecessaryArraySeparators() throws IOException {
JsonReader reader = new JsonReader(reader("[true,,true]")); JsonReader reader = new JsonReader(reader("[true,,true]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.nextBoolean()).isTrue(); assertThat(reader.nextBoolean()).isTrue();
reader.nextNull(); reader.nextNull();
@ -1403,21 +1586,21 @@ public final class JsonReaderTest {
reader.endArray(); reader.endArray();
reader = new JsonReader(reader("[,true]")); reader = new JsonReader(reader("[,true]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
reader.nextNull(); reader.nextNull();
assertThat(reader.nextBoolean()).isTrue(); assertThat(reader.nextBoolean()).isTrue();
reader.endArray(); reader.endArray();
reader = new JsonReader(reader("[true,]")); reader = new JsonReader(reader("[true,]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.nextBoolean()).isTrue(); assertThat(reader.nextBoolean()).isTrue();
reader.nextNull(); reader.nextNull();
reader.endArray(); reader.endArray();
reader = new JsonReader(reader("[,]")); reader = new JsonReader(reader("[,]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
reader.nextNull(); reader.nextNull();
reader.nextNull(); reader.nextNull();
@ -1481,7 +1664,7 @@ public final class JsonReaderTest {
@Test @Test
public void testLenientMultipleTopLevelValues() throws IOException { public void testLenientMultipleTopLevelValues() throws IOException {
JsonReader reader = new JsonReader(reader("[] true {}")); JsonReader reader = new JsonReader(reader("[] true {}"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
reader.endArray(); reader.endArray();
assertThat(reader.nextBoolean()).isTrue(); assertThat(reader.nextBoolean()).isTrue();
@ -1563,7 +1746,7 @@ public final class JsonReaderTest {
@Test @Test
public void testLenientNonExecutePrefix() throws IOException { public void testLenientNonExecutePrefix() throws IOException {
JsonReader reader = new JsonReader(reader(")]}'\n []")); JsonReader reader = new JsonReader(reader(")]}'\n []"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
reader.endArray(); reader.endArray();
assertThat(reader.peek()).isEqualTo(JsonToken.END_DOCUMENT); assertThat(reader.peek()).isEqualTo(JsonToken.END_DOCUMENT);
@ -1572,7 +1755,7 @@ public final class JsonReaderTest {
@Test @Test
public void testLenientNonExecutePrefixWithLeadingWhitespace() throws IOException { public void testLenientNonExecutePrefixWithLeadingWhitespace() throws IOException {
JsonReader reader = new JsonReader(reader("\r\n \t)]}'\n []")); JsonReader reader = new JsonReader(reader("\r\n \t)]}'\n []"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
reader.endArray(); reader.endArray();
assertThat(reader.peek()).isEqualTo(JsonToken.END_DOCUMENT); assertThat(reader.peek()).isEqualTo(JsonToken.END_DOCUMENT);
@ -1581,7 +1764,7 @@ public final class JsonReaderTest {
@Test @Test
public void testLenientPartialNonExecutePrefix() throws IOException { public void testLenientPartialNonExecutePrefix() throws IOException {
JsonReader reader = new JsonReader(reader(")]}' []")); JsonReader reader = new JsonReader(reader(")]}' []"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
assertThat(reader.nextString()).isEqualTo(")"); assertThat(reader.nextString()).isEqualTo(")");
try { try {
reader.nextString(); reader.nextString();
@ -1667,7 +1850,7 @@ public final class JsonReaderTest {
private void testFailWithPosition(String message, String json) throws IOException { private void testFailWithPosition(String message, String json) throws IOException {
// Validate that it works reading the string normally. // Validate that it works reading the string normally.
JsonReader reader1 = new JsonReader(reader(json)); JsonReader reader1 = new JsonReader(reader(json));
reader1.setLenient(true); reader1.setStrictness(Strictness.LENIENT);
reader1.beginArray(); reader1.beginArray();
String unused1 = reader1.nextString(); String unused1 = reader1.nextString();
try { try {
@ -1679,7 +1862,7 @@ public final class JsonReaderTest {
// Also validate that it works when skipping. // Also validate that it works when skipping.
JsonReader reader2 = new JsonReader(reader(json)); JsonReader reader2 = new JsonReader(reader(json));
reader2.setLenient(true); reader2.setStrictness(Strictness.LENIENT);
reader2.beginArray(); reader2.beginArray();
reader2.skipValue(); reader2.skipValue();
try { try {
@ -1725,7 +1908,7 @@ public final class JsonReaderTest {
@Test @Test
public void testLenientVeryLongNumber() throws IOException { public void testLenientVeryLongNumber() throws IOException {
JsonReader reader = new JsonReader(reader("[0." + repeat('9', 8192) + "]")); JsonReader reader = new JsonReader(reader("[0." + repeat('9', 8192) + "]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.peek()).isEqualTo(JsonToken.STRING); assertThat(reader.peek()).isEqualTo(JsonToken.STRING);
assertThat(reader.nextDouble()).isEqualTo(1d); assertThat(reader.nextDouble()).isEqualTo(1d);
@ -1737,7 +1920,7 @@ public final class JsonReaderTest {
public void testVeryLongUnquotedLiteral() throws IOException { public void testVeryLongUnquotedLiteral() throws IOException {
String literal = "a" + repeat('b', 8192) + "c"; String literal = "a" + repeat('b', 8192) + "c";
JsonReader reader = new JsonReader(reader("[" + literal + "]")); JsonReader reader = new JsonReader(reader("[" + literal + "]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.nextString()).isEqualTo(literal); assertThat(reader.nextString()).isEqualTo(literal);
reader.endArray(); reader.endArray();
@ -1786,7 +1969,7 @@ public final class JsonReaderTest {
@Test @Test
public void testStringEndingInSlash() throws IOException { public void testStringEndingInSlash() throws IOException {
JsonReader reader = new JsonReader(reader("/")); JsonReader reader = new JsonReader(reader("/"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
try { try {
reader.peek(); reader.peek();
fail(); fail();
@ -1799,7 +1982,7 @@ public final class JsonReaderTest {
@Test @Test
public void testDocumentWithCommentEndingInSlash() throws IOException { public void testDocumentWithCommentEndingInSlash() throws IOException {
JsonReader reader = new JsonReader(reader("/* foo *//")); JsonReader reader = new JsonReader(reader("/* foo *//"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
try { try {
reader.peek(); reader.peek();
fail(); fail();
@ -1812,7 +1995,7 @@ public final class JsonReaderTest {
@Test @Test
public void testStringWithLeadingSlash() throws IOException { public void testStringWithLeadingSlash() throws IOException {
JsonReader reader = new JsonReader(reader("/x")); JsonReader reader = new JsonReader(reader("/x"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
try { try {
reader.peek(); reader.peek();
fail(); fail();
@ -1825,7 +2008,7 @@ public final class JsonReaderTest {
@Test @Test
public void testUnterminatedObject() throws IOException { public void testUnterminatedObject() throws IOException {
JsonReader reader = new JsonReader(reader("{\"a\":\"android\"x")); JsonReader reader = new JsonReader(reader("{\"a\":\"android\"x"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginObject(); reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a"); assertThat(reader.nextName()).isEqualTo("a");
assertThat(reader.nextString()).isEqualTo("android"); assertThat(reader.nextString()).isEqualTo("android");
@ -1857,7 +2040,7 @@ public final class JsonReaderTest {
String string = new String(stringChars); String string = new String(stringChars);
String json = "[" + string + "]"; String json = "[" + string + "]";
JsonReader reader = new JsonReader(reader(json)); JsonReader reader = new JsonReader(reader(json));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.nextString()).isEqualTo(string); assertThat(reader.nextString()).isEqualTo(string);
reader.endArray(); reader.endArray();
@ -1870,7 +2053,7 @@ public final class JsonReaderTest {
String string = new String(stringChars); String string = new String(stringChars);
String json = "[" + string; String json = "[" + string;
JsonReader reader = new JsonReader(reader(json)); JsonReader reader = new JsonReader(reader(json));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.nextString()).isEqualTo(string); assertThat(reader.nextString()).isEqualTo(string);
try { try {
@ -1883,7 +2066,7 @@ public final class JsonReaderTest {
@Test @Test
public void testSkipVeryLongUnquotedString() throws IOException { public void testSkipVeryLongUnquotedString() throws IOException {
JsonReader reader = new JsonReader(reader("[" + repeat('x', 8192) + "]")); JsonReader reader = new JsonReader(reader("[" + repeat('x', 8192) + "]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
reader.skipValue(); reader.skipValue();
reader.endArray(); reader.endArray();
@ -1892,7 +2075,7 @@ public final class JsonReaderTest {
@Test @Test
public void testSkipTopLevelUnquotedString() throws IOException { public void testSkipTopLevelUnquotedString() throws IOException {
JsonReader reader = new JsonReader(reader(repeat('x', 8192))); JsonReader reader = new JsonReader(reader(repeat('x', 8192)));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.skipValue(); reader.skipValue();
assertThat(reader.peek()).isEqualTo(JsonToken.END_DOCUMENT); assertThat(reader.peek()).isEqualTo(JsonToken.END_DOCUMENT);
} }
@ -1908,7 +2091,7 @@ public final class JsonReaderTest {
@Test @Test
public void testSkipTopLevelQuotedString() throws IOException { public void testSkipTopLevelQuotedString() throws IOException {
JsonReader reader = new JsonReader(reader("\"" + repeat('x', 8192) + "\"")); JsonReader reader = new JsonReader(reader("\"" + repeat('x', 8192) + "\""));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.skipValue(); reader.skipValue();
assertThat(reader.peek()).isEqualTo(JsonToken.END_DOCUMENT); assertThat(reader.peek()).isEqualTo(JsonToken.END_DOCUMENT);
} }
@ -1916,7 +2099,7 @@ public final class JsonReaderTest {
@Test @Test
public void testStringAsNumberWithTruncatedExponent() throws IOException { public void testStringAsNumberWithTruncatedExponent() throws IOException {
JsonReader reader = new JsonReader(reader("[123e]")); JsonReader reader = new JsonReader(reader("[123e]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.peek()).isEqualTo(STRING); assertThat(reader.peek()).isEqualTo(STRING);
} }
@ -1924,7 +2107,7 @@ public final class JsonReaderTest {
@Test @Test
public void testStringAsNumberWithDigitAndNonDigitExponent() throws IOException { public void testStringAsNumberWithDigitAndNonDigitExponent() throws IOException {
JsonReader reader = new JsonReader(reader("[123e4b]")); JsonReader reader = new JsonReader(reader("[123e4b]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.peek()).isEqualTo(STRING); assertThat(reader.peek()).isEqualTo(STRING);
} }
@ -1932,7 +2115,7 @@ public final class JsonReaderTest {
@Test @Test
public void testStringAsNumberWithNonDigitExponent() throws IOException { public void testStringAsNumberWithNonDigitExponent() throws IOException {
JsonReader reader = new JsonReader(reader("[123eb]")); JsonReader reader = new JsonReader(reader("[123eb]"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.peek()).isEqualTo(STRING); assertThat(reader.peek()).isEqualTo(STRING);
} }
@ -1940,7 +2123,7 @@ public final class JsonReaderTest {
@Test @Test
public void testEmptyStringName() throws IOException { public void testEmptyStringName() throws IOException {
JsonReader reader = new JsonReader(reader("{\"\":true}")); JsonReader reader = new JsonReader(reader("{\"\":true}"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
assertThat(reader.peek()).isEqualTo(BEGIN_OBJECT); assertThat(reader.peek()).isEqualTo(BEGIN_OBJECT);
reader.beginObject(); reader.beginObject();
assertThat(reader.peek()).isEqualTo(NAME); assertThat(reader.peek()).isEqualTo(NAME);
@ -1970,7 +2153,7 @@ public final class JsonReaderTest {
@Test @Test
public void testLenientExtraCommasInMaps() throws IOException { public void testLenientExtraCommasInMaps() throws IOException {
JsonReader reader = new JsonReader(reader("{\"a\":\"b\",}")); JsonReader reader = new JsonReader(reader("{\"a\":\"b\",}"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginObject(); reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a"); assertThat(reader.nextName()).isEqualTo("a");
assertThat(reader.nextString()).isEqualTo("b"); assertThat(reader.nextString()).isEqualTo("b");
@ -2039,7 +2222,7 @@ public final class JsonReaderTest {
@Test @Test
public void testUnterminatedStringFailure() throws IOException { public void testUnterminatedStringFailure() throws IOException {
JsonReader reader = new JsonReader(reader("[\"string")); JsonReader reader = new JsonReader(reader("[\"string"));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
reader.beginArray(); reader.beginArray();
assertThat(reader.peek()).isEqualTo(JsonToken.STRING); assertThat(reader.peek()).isEqualTo(JsonToken.STRING);
try { try {
@ -2062,13 +2245,13 @@ public final class JsonReaderTest {
} }
sb.append("\n)]}'\n3"); sb.append("\n)]}'\n3");
JsonReader reader = new JsonReader(reader(sb.toString())); JsonReader reader = new JsonReader(reader(sb.toString()));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
JsonToken token = reader.peek(); JsonToken token = reader.peek();
assertThat(token).isEqualTo(JsonToken.NUMBER); assertThat(token).isEqualTo(JsonToken.NUMBER);
} }
private static void assertStrictError(MalformedJsonException exception, String expectedLocation) { private static void assertStrictError(MalformedJsonException exception, String expectedLocation) {
assertThat(exception).hasMessageThat().isEqualTo("Use JsonReader.setLenient(true) to accept malformed JSON at " + expectedLocation assertThat(exception).hasMessageThat().isEqualTo("Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON at " + expectedLocation
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json"); + "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json");
} }
@ -2080,7 +2263,7 @@ public final class JsonReaderTest {
private void assertDocument(String document, Object... expectations) throws IOException { private void assertDocument(String document, Object... expectations) throws IOException {
JsonReader reader = new JsonReader(reader(document)); JsonReader reader = new JsonReader(reader(document));
reader.setLenient(true); reader.setStrictness(Strictness.LENIENT);
for (Object expectation : expectations) { for (Object expectation : expectations) {
if (expectation == BEGIN_OBJECT) { if (expectation == BEGIN_OBJECT) {
reader.beginObject(); reader.beginObject();

View File

@ -17,9 +17,11 @@
package com.google.gson.stream; package com.google.gson.stream;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import com.google.gson.FormattingStyle; import com.google.gson.FormattingStyle;
import com.google.gson.Strictness;
import com.google.gson.internal.LazilyParsedNumber; import com.google.gson.internal.LazilyParsedNumber;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
@ -30,6 +32,51 @@ import org.junit.Test;
@SuppressWarnings("resource") @SuppressWarnings("resource")
public final class JsonWriterTest { 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();
}
@SuppressWarnings("deprecation") // for JsonWriter.setLenient
@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();
}
@SuppressWarnings("deprecation") // for JsonWriter.setLenient
@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 @Test
public void testTopLevelValueTypes() throws IOException { public void testTopLevelValueTypes() throws IOException {
StringWriter string1 = new StringWriter(); StringWriter string1 = new StringWriter();
@ -67,11 +114,12 @@ public final class JsonWriterTest {
public void testInvalidTopLevelTypes() throws IOException { public void testInvalidTopLevelTypes() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.name("hello"); jsonWriter.name("hello"); // TODO: This should throw, see https://github.com/google/gson/issues/2407
try { try {
jsonWriter.value("world"); jsonWriter.value("world");
fail(); fail();
} catch (IllegalStateException expected) { } catch (IllegalStateException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Nesting problem.");
} }
} }
@ -85,6 +133,7 @@ public final class JsonWriterTest {
jsonWriter.name("a"); jsonWriter.name("a");
fail(); fail();
} catch (IllegalStateException expected) { } catch (IllegalStateException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Already wrote a name, expecting a value.");
} }
} }
@ -98,6 +147,7 @@ public final class JsonWriterTest {
jsonWriter.endObject(); jsonWriter.endObject();
fail(); fail();
} catch (IllegalStateException expected) { } catch (IllegalStateException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Dangling name: a");
} }
} }
@ -110,6 +160,7 @@ public final class JsonWriterTest {
jsonWriter.value(true); jsonWriter.value(true);
fail(); fail();
} catch (IllegalStateException expected) { } catch (IllegalStateException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Nesting problem.");
} }
} }
@ -118,11 +169,33 @@ public final class JsonWriterTest {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray().endArray(); jsonWriter.beginArray().endArray();
try {
jsonWriter.beginArray(); IllegalStateException expected = assertThrows(IllegalStateException.class, jsonWriter::beginArray);
fail(); assertThat(expected).hasMessageThat().isEqualTo("JSON must have only one top-level value.");
} catch (IllegalStateException expected) { }
}
@Test
public void testMultipleTopLevelValuesStrict() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setStrictness(Strictness.STRICT);
jsonWriter.beginArray().endArray();
IllegalStateException expected = assertThrows(IllegalStateException.class, jsonWriter::beginArray);
assertThat(expected).hasMessageThat().isEqualTo("JSON must have only one top-level value.");
}
@Test
public void testMultipleTopLevelValuesLenient() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter writer = new JsonWriter(stringWriter);
writer.setStrictness(Strictness.LENIENT);
writer.beginArray();
writer.endArray();
writer.beginArray();
writer.endArray();
writer.close();
assertThat(stringWriter.toString()).isEqualTo("[][]");
} }
@Test @Test
@ -135,6 +208,7 @@ public final class JsonWriterTest {
jsonWriter.endArray(); jsonWriter.endArray();
fail(); fail();
} catch (IllegalStateException expected) { } catch (IllegalStateException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Nesting problem.");
} }
} }
@ -148,6 +222,7 @@ public final class JsonWriterTest {
jsonWriter.endObject(); jsonWriter.endObject();
fail(); fail();
} catch (IllegalStateException expected) { } catch (IllegalStateException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Nesting problem.");
} }
} }
@ -192,24 +267,32 @@ public final class JsonWriterTest {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray(); jsonWriter.beginArray();
try {
jsonWriter.value(Float.NaN); IllegalArgumentException expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Float.NaN));
fail(); assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was NaN");
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was NaN"); expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Float.NEGATIVE_INFINITY));
} assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was -Infinity");
try {
jsonWriter.value(Float.NEGATIVE_INFINITY); expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Float.POSITIVE_INFINITY));
fail(); assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was Infinity");
} catch (IllegalArgumentException expected) { }
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was -Infinity");
} @Test
try { public void testNonFiniteFloatsWhenStrict() throws IOException {
jsonWriter.value(Float.POSITIVE_INFINITY); StringWriter stringWriter = new StringWriter();
fail(); JsonWriter jsonWriter = new JsonWriter(stringWriter);
} catch (IllegalArgumentException expected) { jsonWriter.setStrictness(Strictness.STRICT);
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was Infinity"); jsonWriter.beginArray();
}
IllegalArgumentException expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Float.NaN));
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was NaN");
expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Float.NEGATIVE_INFINITY));
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was -Infinity");
expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Float.POSITIVE_INFINITY));
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was Infinity");
} }
@Test @Test
@ -217,24 +300,32 @@ public final class JsonWriterTest {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray(); jsonWriter.beginArray();
try {
jsonWriter.value(Double.NaN); IllegalArgumentException expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Double.NaN));
fail(); assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was NaN");
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was NaN"); expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Double.NEGATIVE_INFINITY));
} assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was -Infinity");
try {
jsonWriter.value(Double.NEGATIVE_INFINITY); expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Double.POSITIVE_INFINITY));
fail(); assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was Infinity");
} catch (IllegalArgumentException expected) { }
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was -Infinity");
} @Test
try { public void testNonFiniteDoublesWhenStrict() throws IOException {
jsonWriter.value(Double.POSITIVE_INFINITY); StringWriter stringWriter = new StringWriter();
fail(); JsonWriter jsonWriter = new JsonWriter(stringWriter);
} catch (IllegalArgumentException expected) { jsonWriter.setStrictness(Strictness.STRICT);
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was Infinity"); jsonWriter.beginArray();
}
IllegalArgumentException expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Double.NaN));
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was NaN");
expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Double.NEGATIVE_INFINITY));
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was -Infinity");
expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Double.POSITIVE_INFINITY));
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was Infinity");
} }
@Test @Test
@ -242,37 +333,45 @@ public final class JsonWriterTest {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray(); jsonWriter.beginArray();
try {
jsonWriter.value(Double.valueOf(Double.NaN)); IllegalArgumentException expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Double.valueOf(Double.NaN)));
fail(); assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was NaN");
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was NaN"); expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Double.valueOf(Double.NEGATIVE_INFINITY)));
} assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was -Infinity");
try {
jsonWriter.value(Double.valueOf(Double.NEGATIVE_INFINITY)); expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Double.valueOf(Double.POSITIVE_INFINITY)));
fail(); assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was Infinity");
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was -Infinity"); expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(new LazilyParsedNumber("Infinity")));
} assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was Infinity");
try { }
jsonWriter.value(Double.valueOf(Double.POSITIVE_INFINITY));
fail(); @Test
} catch (IllegalArgumentException expected) { public void testNonFiniteNumbersWhenStrict() throws IOException {
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was Infinity"); StringWriter stringWriter = new StringWriter();
} JsonWriter jsonWriter = new JsonWriter(stringWriter);
try { jsonWriter.setStrictness(Strictness.STRICT);
jsonWriter.value(new LazilyParsedNumber("Infinity")); jsonWriter.beginArray();
fail();
} catch (IllegalArgumentException expected) { IllegalArgumentException expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Double.valueOf(Double.NaN)));
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was Infinity"); assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was NaN");
}
expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Double.valueOf(Double.NEGATIVE_INFINITY)));
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was -Infinity");
expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Double.valueOf(Double.POSITIVE_INFINITY)));
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was Infinity");
expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(new LazilyParsedNumber("Infinity")));
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was Infinity");
} }
@Test @Test
public void testNonFiniteFloatsWhenLenient() throws IOException { public void testNonFiniteFloatsWhenLenient() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setLenient(true); jsonWriter.setStrictness(Strictness.LENIENT);
jsonWriter.beginArray(); jsonWriter.beginArray();
jsonWriter.value(Float.NaN); jsonWriter.value(Float.NaN);
jsonWriter.value(Float.NEGATIVE_INFINITY); jsonWriter.value(Float.NEGATIVE_INFINITY);
@ -285,7 +384,7 @@ public final class JsonWriterTest {
public void testNonFiniteDoublesWhenLenient() throws IOException { public void testNonFiniteDoublesWhenLenient() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setLenient(true); jsonWriter.setStrictness(Strictness.LENIENT);
jsonWriter.beginArray(); jsonWriter.beginArray();
jsonWriter.value(Double.NaN); jsonWriter.value(Double.NaN);
jsonWriter.value(Double.NEGATIVE_INFINITY); jsonWriter.value(Double.NEGATIVE_INFINITY);
@ -298,7 +397,7 @@ public final class JsonWriterTest {
public void testNonFiniteNumbersWhenLenient() throws IOException { public void testNonFiniteNumbersWhenLenient() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setLenient(true); jsonWriter.setStrictness(Strictness.LENIENT);
jsonWriter.beginArray(); jsonWriter.beginArray();
jsonWriter.value(Double.valueOf(Double.NaN)); jsonWriter.value(Double.valueOf(Double.NaN));
jsonWriter.value(Double.valueOf(Double.NEGATIVE_INFINITY)); jsonWriter.value(Double.valueOf(Double.NEGATIVE_INFINITY));
@ -561,6 +660,8 @@ public final class JsonWriterTest {
jsonWriter.beginArray(); jsonWriter.beginArray();
jsonWriter.value("\u2028 \u2029"); jsonWriter.value("\u2028 \u2029");
jsonWriter.endArray(); jsonWriter.endArray();
// JSON specification does not require that they are escaped, but Gson escapes them for compatibility with JavaScript
// where they are considered line breaks
assertThat(stringWriter.toString()).isEqualTo("[\"\\u2028 \\u2029\"]"); assertThat(stringWriter.toString()).isEqualTo("[\"\\u2028 \\u2029\"]");
} }
@ -739,32 +840,6 @@ public final class JsonWriterTest {
assertThat(stringWriter.toString()).isEqualTo(expected); assertThat(stringWriter.toString()).isEqualTo(expected);
} }
@Test
public void testLenientWriterPermitsMultipleTopLevelValues() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter writer = new JsonWriter(stringWriter);
writer.setLenient(true);
writer.beginArray();
writer.endArray();
writer.beginArray();
writer.endArray();
writer.close();
assertThat(stringWriter.toString()).isEqualTo("[][]");
}
@Test
public void testStrictWriterDoesNotPermitMultipleTopLevelValues() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter writer = new JsonWriter(stringWriter);
writer.beginArray();
writer.endArray();
try {
writer.beginArray();
fail();
} catch (IllegalStateException expected) {
}
}
@Test @Test
public void testClosedWriterThrowsOnStructure() throws IOException { public void testClosedWriterThrowsOnStructure() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();