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 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
**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"
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.
**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))
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 ..."

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>
* 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
* comply with the JSON specification, regardless of whether {@link GsonBuilder#setLenient()}
* is used or not. If this behavior is not desired, the following workarounds can be used:
* comply with the JSON specification when no explicit {@linkplain Strictness strictness} is set (the default).
* To specify the strictness of a {@code Gson} instance, you should set it through
* {@link GsonBuilder#setStrictness(Strictness)}.
*
* <p>For older Gson versions, which don't have the strictness mode API, the following
* workarounds can be used:
*
* <h3>Serialization</h3>
* <ol>
@ -132,6 +136,10 @@ import java.util.concurrent.atomic.AtomicLongArray;
* to make sure there is no trailing data
* </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
*
* @author Inderjeet Singh
@ -140,7 +148,8 @@ import java.util.concurrent.atomic.AtomicLongArray;
*/
public final class Gson {
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 boolean DEFAULT_ESCAPE_HTML = true;
static final boolean DEFAULT_SERIALIZE_NULLS = false;
@ -184,7 +193,7 @@ public final class Gson {
final boolean generateNonExecutableJson;
final boolean htmlSafe;
final FormattingStyle formattingStyle;
final boolean lenient;
final Strictness strictness;
final boolean serializeSpecialFloatingPointValues;
final boolean useJdkUnsafe;
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
* consideration for serialization and deserialization. You can change this behavior through
* {@link GsonBuilder#excludeFieldsWithModifiers(int...)}.</li>
* <li>No explicit strictness is set. You can change this by calling
* {@link GsonBuilder#setStrictness(Strictness)}.</li>
* </ul>
*/
public Gson() {
this(Excluder.DEFAULT, DEFAULT_FIELD_NAMING_STRATEGY,
Collections.<Type, InstanceCreator<?>>emptyMap(), DEFAULT_SERIALIZE_NULLS,
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,
LongSerializationPolicy.DEFAULT, DEFAULT_DATE_PATTERN, DateFormat.DEFAULT, DateFormat.DEFAULT,
Collections.<TypeAdapterFactory>emptyList(), Collections.<TypeAdapterFactory>emptyList(),
@ -248,7 +259,7 @@ public final class Gson {
Gson(Excluder excluder, FieldNamingStrategy fieldNamingStrategy,
Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
FormattingStyle formattingStyle, boolean lenient, boolean serializeSpecialFloatingPointValues,
FormattingStyle formattingStyle, Strictness strictness, boolean serializeSpecialFloatingPointValues,
boolean useJdkUnsafe,
LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,
int timeStyle, List<TypeAdapterFactory> builderFactories,
@ -265,7 +276,7 @@ public final class Gson {
this.generateNonExecutableJson = generateNonExecutableGson;
this.htmlSafe = htmlSafe;
this.formattingStyle = formattingStyle;
this.lenient = lenient;
this.strictness = strictness;
this.serializeSpecialFloatingPointValues = serializeSpecialFloatingPointValues;
this.useJdkUnsafe = useJdkUnsafe;
this.longSerializationPolicy = longSerializationPolicy;
@ -802,7 +813,7 @@ public final class Gson {
* <pre>
* Type typeOfSrc = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType();
* </pre>
* @param writer Writer to which the JSON representation of src needs to be written.
* @param writer Writer to which the JSON representation of src needs to be written
* @throws JsonIOException if there was a problem writing to the writer
* @since 1.2
*
@ -822,24 +833,38 @@ public final class Gson {
* Writes the JSON representation of {@code src} of type {@code typeOfSrc} to
* {@code writer}.
*
* <p>The JSON data is written in {@linkplain JsonWriter#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided writer. The lenient mode setting
* of the writer is restored once this method returns.
* <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
* this setting will be used for writing the JSON regardless of the {@linkplain JsonWriter#getStrictness() strictness}
* of the provided {@link JsonWriter}. For legacy reasons, if the {@code Gson} instance has no explicit strictness setting
* and the writer does not have the strictness {@link Strictness#STRICT}, the JSON will be written in {@link Strictness#LENIENT}
* mode.<br>
* Note that in all cases the old strictness setting of the writer will be restored when this method returns.
*
* <p>The 'HTML-safe' and 'serialize {@code null}' settings of this {@code Gson} instance
* (configured by the {@link GsonBuilder}) are applied, and the original settings of the
* writer are restored once this method returns.
*
* @param src the object 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
*/
public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOException {
@SuppressWarnings("unchecked")
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();
writer.setHtmlSafe(htmlSafe);
boolean oldSerializeNulls = writer.getSerializeNulls();
writer.setHtmlSafe(htmlSafe);
writer.setSerializeNulls(serializeNulls);
try {
adapter.write(writer, src);
@ -848,7 +873,7 @@ public final class Gson {
} catch (AssertionError e) {
throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);
} finally {
writer.setLenient(oldLenient);
writer.setStrictness(oldStrictness);
writer.setHtmlSafe(oldHtmlSafe);
writer.setSerializeNulls(oldSerializeNulls);
}
@ -892,7 +917,10 @@ public final class Gson {
* <li>{@link GsonBuilder#disableHtmlEscaping()}</li>
* <li>{@link GsonBuilder#generateNonExecutableJson()}</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#setFormattingStyle(FormattingStyle)}</li>
* </ul>
@ -904,7 +932,7 @@ public final class Gson {
JsonWriter jsonWriter = new JsonWriter(writer);
jsonWriter.setFormattingStyle(formattingStyle);
jsonWriter.setHtmlSafe(htmlSafe);
jsonWriter.setLenient(lenient);
jsonWriter.setStrictness(strictness == null ? Strictness.LEGACY_STRICT : strictness);
jsonWriter.setSerializeNulls(serializeNulls);
return jsonWriter;
}
@ -914,35 +942,50 @@ public final class Gson {
*
* <p>The following settings are considered:
* <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>
*/
public JsonReader newJsonReader(Reader reader) {
JsonReader jsonReader = new JsonReader(reader);
jsonReader.setLenient(lenient);
jsonReader.setStrictness(strictness == null ? Strictness.LEGACY_STRICT : strictness);
return jsonReader;
}
/**
* Writes the JSON for {@code jsonElement} to {@code writer}.
*
* <p>The JSON data is written in {@linkplain JsonWriter#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided writer. The lenient mode setting
* of the writer is restored once this method returns.
* <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
* this setting will be used for writing the JSON regardless of the {@linkplain JsonWriter#getStrictness() strictness}
* of the provided {@link JsonWriter}. For legacy reasons, if the {@code Gson} instance has no explicit strictness setting
* and the writer does not have the strictness {@link Strictness#STRICT}, the JSON will be written in {@link Strictness#LENIENT}
* mode.<br>
* Note that in all cases the old strictness setting of the writer will be restored when this method returns.
*
* <p>The 'HTML-safe' and 'serialize {@code null}' settings of this {@code Gson} instance
* (configured by the {@link GsonBuilder}) are applied, and the original settings of the
* writer are restored once this method returns.
*
* @param jsonElement the JSON element to be written
* @param writer the JSON writer to which the provided element will be written
* @throws JsonIOException if there was a problem writing to the writer
*/
public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOException {
boolean oldLenient = writer.isLenient();
writer.setLenient(true);
Strictness oldStrictness = writer.getStrictness();
boolean oldHtmlSafe = writer.isHtmlSafe();
writer.setHtmlSafe(htmlSafe);
boolean oldSerializeNulls = writer.getSerializeNulls();
writer.setHtmlSafe(htmlSafe);
writer.setSerializeNulls(serializeNulls);
if (this.strictness != null) {
writer.setStrictness(this.strictness);
} else if (writer.getStrictness() != Strictness.STRICT) {
writer.setStrictness(Strictness.LENIENT);
}
try {
Streams.write(jsonElement, writer);
} catch (IOException e) {
@ -950,7 +993,7 @@ public final class Gson {
} catch (AssertionError e) {
throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);
} finally {
writer.setLenient(oldLenient);
writer.setStrictness(oldStrictness);
writer.setHtmlSafe(oldHtmlSafe);
writer.setSerializeNulls(oldSerializeNulls);
}
@ -1169,9 +1212,12 @@ public final class Gson {
* <p>Unlike the other {@code fromJson} methods, no exception is thrown if the JSON data has
* multiple top-level JSON elements, or if there is trailing data.
*
* <p>The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided reader. The lenient mode setting
* of the reader is restored once this method returns.
* <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
* this setting will be used for reading the JSON regardless of the {@linkplain JsonReader#getStrictness() strictness}
* of the provided {@link JsonReader}. For legacy reasons, if the {@code Gson} instance has no explicit strictness setting
* and the reader does not have the strictness {@link Strictness#STRICT}, the JSON will be written in {@link Strictness#LENIENT}
* mode.<br>
* Note that in all cases the old strictness setting of the reader will be restored when this method returns.
*
* @param <T> the type of the desired object
* @param reader the reader whose next JSON value should be deserialized
@ -1198,9 +1244,12 @@ public final class Gson {
* <p>Unlike the other {@code fromJson} methods, no exception is thrown if the JSON data has
* multiple top-level JSON elements, or if there is trailing data.
*
* <p>The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided reader. The lenient mode setting
* of the reader is restored once this method returns.
* <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
* this setting will be used for reading the JSON regardless of the {@linkplain JsonReader#getStrictness() strictness}
* of the provided {@link JsonReader}. For legacy reasons, if the {@code Gson} instance has no explicit strictness setting
* and the reader does not have the strictness {@link Strictness#STRICT}, the JSON will be written in {@link Strictness#LENIENT}
* mode.<br>
* Note that in all cases the old strictness setting of the reader will be restored when this method returns.
*
* @param <T> the type of the desired object
* @param reader the reader whose next JSON value should be deserialized
@ -1220,8 +1269,14 @@ public final class Gson {
*/
public <T> T fromJson(JsonReader reader, TypeToken<T> typeOfT) throws JsonIOException, JsonSyntaxException {
boolean isEmpty = true;
boolean oldLenient = reader.isLenient();
reader.setLenient(true);
Strictness oldStrictness = reader.getStrictness();
if (this.strictness != null) {
reader.setStrictness(this.strictness);
} else if (reader.getStrictness() != Strictness.STRICT) {
reader.setStrictness(Strictness.LENIENT);
}
try {
JsonToken unused = reader.peek();
isEmpty = false;
@ -1244,7 +1299,7 @@ public final class Gson {
} catch (AssertionError e) {
throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);
} 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_FORMATTING_STYLE;
import static com.google.gson.Gson.DEFAULT_JSON_NON_EXECUTABLE;
import static com.google.gson.Gson.DEFAULT_LENIENT;
import static com.google.gson.Gson.DEFAULT_NUMBER_TO_NUMBER_STRATEGY;
import static com.google.gson.Gson.DEFAULT_OBJECT_TO_NUMBER_STRATEGY;
import static com.google.gson.Gson.DEFAULT_SERIALIZE_NULLS;
import static com.google.gson.Gson.DEFAULT_SPECIALIZE_FLOAT_VALUES;
import static com.google.gson.Gson.DEFAULT_STRICTNESS;
import static com.google.gson.Gson.DEFAULT_USE_JDK_UNSAFE;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.InlineMe;
import com.google.gson.annotations.Since;
import com.google.gson.annotations.Until;
import com.google.gson.internal.$Gson$Preconditions;
@ -71,12 +72,16 @@ import java.util.Objects;
* .create();
* </pre>
*
* <p>NOTES:
* <p>Notes:
* <ul>
* <li> the order of invocation of configuration methods does not matter.</li>
* <li> The default serialization of {@link Date} and its subclasses in Gson does
* <li>The order of invocation of configuration methods does not matter.</li>
* <li>The default serialization of {@link Date} and its subclasses in Gson does
* not contain time-zone information. So, if you are using date/time instances,
* use {@code GsonBuilder} and its {@code setDateFormat} methods.</li>
* <li>By default no explicit {@link Strictness} is set; some of the {@link Gson} methods
* behave as if {@link Strictness#LEGACY_STRICT} was used whereas others behave as
* if {@link Strictness#LENIENT} was used. Prefer explicitly setting a strictness
* with {@link #setStrictness(Strictness)} to avoid this legacy behavior.
* </ul>
*
* @author Inderjeet Singh
@ -100,7 +105,7 @@ public final class GsonBuilder {
private boolean escapeHtmlChars = DEFAULT_ESCAPE_HTML;
private FormattingStyle formattingStyle = DEFAULT_FORMATTING_STYLE;
private boolean generateNonExecutableJson = DEFAULT_JSON_NON_EXECUTABLE;
private boolean lenient = DEFAULT_LENIENT;
private Strictness strictness = DEFAULT_STRICTNESS;
private boolean useJdkUnsafe = DEFAULT_USE_JDK_UNSAFE;
private ToNumberStrategy objectToNumberStrategy = DEFAULT_OBJECT_TO_NUMBER_STRATEGY;
private ToNumberStrategy numberToNumberStrategy = DEFAULT_NUMBER_TO_NUMBER_STRATEGY;
@ -130,7 +135,7 @@ public final class GsonBuilder {
this.generateNonExecutableJson = gson.generateNonExecutableJson;
this.escapeHtmlChars = gson.htmlSafe;
this.formattingStyle = gson.formattingStyle;
this.lenient = gson.lenient;
this.strictness = gson.strictness;
this.serializeSpecialFloatingPointValues = gson.serializeSpecialFloatingPointValues;
this.longSerializationPolicy = gson.longSerializationPolicy;
this.datePattern = gson.datePattern;
@ -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
* whether this builder method is used.
* @deprecated This method is equivalent to calling {@link #setStrictness(Strictness)} with
* {@link Strictness#LENIENT}: {@code setStrictness(Strictness.LENIENT)}
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @see JsonReader#setLenient(boolean)
* @see JsonWriter#setLenient(boolean)
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern.
* @see JsonReader#setStrictness(Strictness)
* @see JsonWriter#setStrictness(Strictness)
* @see #setStrictness(Strictness)
*/
@Deprecated
@InlineMe(replacement = "this.setStrictness(Strictness.LENIENT)", imports = "com.google.gson.Strictness")
@CanIgnoreReturnValue
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;
}
@ -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,
* <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
@ -804,7 +831,7 @@ public final class GsonBuilder {
return new Gson(excluder, fieldNamingPolicy, new HashMap<>(instanceCreators),
serializeNulls, complexMapKeySerialization,
generateNonExecutableJson, escapeHtmlChars, formattingStyle, lenient,
generateNonExecutableJson, escapeHtmlChars, formattingStyle, strictness,
serializeSpecialFloatingPointValues, useJdkUnsafe, longSerializationPolicy,
datePattern, dateStyle, timeStyle, new ArrayList<>(this.factories),
new ArrayList<>(this.hierarchyFactories), factories,

View File

@ -321,7 +321,8 @@ public abstract class JsonElement {
try {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setLenient(true);
// Make writer lenient because toString() must not fail, even if for example JsonPrimitive contains NaN
jsonWriter.setStrictness(Strictness.LENIENT);
Streams.write(this, jsonWriter);
return stringWriter.toString();
} catch (IOException e) {

View File

@ -41,7 +41,7 @@ public final class JsonParser {
* An exception is thrown if the JSON string has multiple top-level JSON elements,
* or if there is trailing data.
*
* <p>The JSON string is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode}.
* <p>The JSON string is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode}.
*
* @param json JSON text
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
@ -57,7 +57,7 @@ public final class JsonParser {
* An exception is thrown if the JSON string has multiple top-level JSON elements,
* or if there is trailing data.
*
* <p>The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode}.
* <p>The JSON data is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode}.
*
* @param reader JSON text
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
@ -87,8 +87,8 @@ public final class JsonParser {
* Unlike the other {@code parse} methods, no exception is thrown if the JSON data has
* multiple top-level JSON elements, or if there is trailing data.
*
* <p>The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided reader. The lenient mode setting
* <p>The JSON data is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode},
* regardless of the strictness setting of the provided reader. The strictness setting
* of the reader is restored once this method returns.
*
* @throws JsonParseException if there is an IOException or if the specified
@ -97,8 +97,8 @@ public final class JsonParser {
*/
public static JsonElement parseReader(JsonReader reader)
throws JsonIOException, JsonSyntaxException {
boolean lenient = reader.isLenient();
reader.setLenient(true);
Strictness strictness = reader.getStrictness();
reader.setStrictness(Strictness.LENIENT);
try {
return Streams.parse(reader);
} catch (StackOverflowError e) {
@ -106,7 +106,7 @@ public final class JsonParser {
} catch (OutOfMemoryError e) {
throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e);
} 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
* 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
* 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) {
parser = new JsonReader(reader);
parser.setLenient(true);
parser.setStrictness(Strictness.LENIENT);
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}.
* Unlike Gson's similar {@link Gson#toJson(JsonElement, Appendable) toJson}
* method, this write is strict. Create a {@link
* JsonWriter#setLenient(boolean) lenient} {@code JsonWriter} and call
* {@link #write(JsonWriter, Object)} for lenient writing.
* The strictness {@link Strictness#LEGACY_STRICT} is used for writing the JSON data.
* To use a different strictness setting create a {@link JsonWriter}, call its
* {@link JsonWriter#setStrictness(Strictness)} method and then use
* {@link #write(JsonWriter, Object)} for writing.
*
* @param value the Java object to convert. May be null.
* @since 2.2
@ -207,10 +207,11 @@ public abstract class TypeAdapter<T> {
}
/**
* Converts {@code value} to a JSON document. Unlike Gson's similar {@link
* Gson#toJson(Object) toJson} method, this write is strict. Create a {@link
* JsonWriter#setLenient(boolean) lenient} {@code JsonWriter} and call
* {@link #write(JsonWriter, Object)} for lenient writing.
* Converts {@code value} to a JSON document.
* The strictness {@link Strictness#LEGACY_STRICT} is used for writing the JSON data.
* To use a different strictness setting create a {@link JsonWriter}, call its
* {@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)}
* @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;
/**
* Converts the JSON document in {@code in} to a Java object. Unlike Gson's
* similar {@link Gson#fromJson(Reader, Class) fromJson} method, this
* read is strict. Create a {@link JsonReader#setLenient(boolean) lenient}
* {@code JsonReader} and call {@link #read(JsonReader)} for lenient reading.
* Converts the JSON document in {@code in} to a Java object. The strictness
* {@link Strictness#LEGACY_STRICT} is used for reading the JSON data. To use a different
* strictness setting create a {@link JsonReader}, call its {@link JsonReader#setStrictness(Strictness)}
* method and then use {@link #read(JsonReader)} for reading.
*
* <p>No exception is thrown if the JSON data has multiple top-level JSON elements,
* 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
* similar {@link Gson#fromJson(String, Class) fromJson} method, this read is
* strict. Create a {@link JsonReader#setLenient(boolean) lenient} {@code
* JsonReader} and call {@link #read(JsonReader)} for lenient reading.
* Converts the JSON document in {@code json} to a Java object. The strictness
* {@link Strictness#LEGACY_STRICT} is used for reading the JSON data. To use a different
* strictness setting create a {@link JsonReader}, call its {@link JsonReader#setStrictness(Strictness)}
* method and then use {@link #read(JsonReader)} for reading.
*
* <p>No exception is thrown if the JSON data has multiple top-level JSON elements,
* or if there is trailing data.

View File

@ -16,6 +16,7 @@
package com.google.gson.stream;
import com.google.gson.Strictness;
import com.google.gson.internal.JsonReaderInternalAccess;
import com.google.gson.internal.TroubleshootingGuide;
import com.google.gson.internal.bind.JsonTreeReader;
@ -27,7 +28,7 @@ import java.util.Arrays;
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
* values (strings, numbers, booleans, and nulls) as well as the begin and
* 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
* by {@code <script>} tags, disarming the attack. Since the prefix is malformed
* 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.
*
* <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. */
private final Reader in;
/** True to accept non-spec compliant JSON */
private boolean lenient = false;
private Strictness strictness = Strictness.LEGACY_STRICT;
static final int BUFFER_SIZE = 1024;
/**
@ -293,55 +293,101 @@ public class JsonReader implements Closeable {
}
/**
* Configure this parser to be liberal in what it accepts. By default,
* 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:
* Sets the strictness of this reader.
*
* <ul>
* <li>Streams that start with the <a href="#nonexecuteprefix">non-execute
* prefix</a>, <code>")]}'\n"</code>.
* <li>Streams that include multiple top-level values. With strict parsing,
* 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>
* @deprecated Please use {@link #setStrictness(Strictness)} instead.
* {@code JsonReader.setLenient(true)} should be replaced by {@code JsonReader.setStrictness(Strictness.LENIENT)}
* and {@code JsonReader.setLenient(false)} should be replaced by {@code JsonReader.setStrictness(Strictness.LEGACY_STRICT)}.<br>
* However, if you used {@code setLenient(false)} before, you might prefer {@link Strictness#STRICT} now instead.
*
* <p>Note: Even in strict mode there are slight derivations from the JSON
* specification:
* <ul>
* <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>
* @param lenient whether this reader should be lenient. If true, the strictness is set to {@link Strictness#LENIENT}.
* If false, the strictness is set to {@link Strictness#LEGACY_STRICT}.
* @see #setStrictness(Strictness)
*/
@Deprecated
@SuppressWarnings("InlineMeSuggester") // Don't specify @InlineMe, so caller with `setLenient(false)` becomes aware of new Strictness.STRICT
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() {
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
* beginning of a new array.
@ -540,7 +586,7 @@ public class JsonReader implements Closeable {
throw syntaxError("Expected ':'");
}
} else if (peekStack == JsonScope.EMPTY_DOCUMENT) {
if (lenient) {
if (strictness == Strictness.LENIENT) {
consumeNonExecutePrefix();
}
stack[stackSize - 1] = JsonScope.NONEMPTY_DOCUMENT;
@ -610,6 +656,8 @@ public class JsonReader implements Closeable {
String keyword;
String keywordUpper;
int peeking;
// Look at the first letter to determine what keyword we are trying to match.
if (c == 't' || c == 'T') {
keyword = "true";
keywordUpper = "TRUE";
@ -626,14 +674,18 @@ public class JsonReader implements Closeable {
return PEEKED_NONE;
}
// Confirm that chars [1..length) match the keyword.
// Upper cased keywords are not allowed in STRICT mode
boolean allowsUpperCased = strictness != Strictness.STRICT;
// Confirm that chars [0..length) match the keyword.
int length = keyword.length();
for (int i = 1; i < length; i++) {
for (int i = 0; i < length; i++) {
if (pos + i >= limit && !fillBuffer(i + 1)) {
return PEEKED_NONE;
}
c = buffer[pos + i];
if (c != keyword.charAt(i) && c != keywordUpper.charAt(i)) {
boolean matched = c == keyword.charAt(i) || (allowsUpperCased && c == keywordUpper.charAt(i));
if (!matched) {
return PEEKED_NONE;
}
}
@ -894,7 +946,7 @@ public class JsonReader implements Closeable {
* @throws NumberFormatException if the next literal value cannot be parsed
* as a double.
* @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 {
int p = peeked;
@ -921,7 +973,7 @@ public class JsonReader implements Closeable {
peeked = PEEKED_BUFFERED;
double result = Double.parseDouble(peekedString); // don't catch this NumberFormatException.
if (!lenient && (Double.isNaN(result) || Double.isInfinite(result))) {
if (strictness != Strictness.LENIENT && (Double.isNaN(result) || Double.isInfinite(result))) {
throw syntaxError("JSON forbids NaN and infinities: " + result);
}
peekedString = null;
@ -1007,7 +1059,10 @@ public class JsonReader implements Closeable {
while (p < l) {
int c = buffer[p++];
if (c == quote) {
// In strict mode, throw an exception when meeting unescaped control characters (U+0000 through U+001F)
if (strictness == Strictness.STRICT && c < 0x20) {
throw syntaxError("Unescaped control characters (\\u0000-\\u001F) are not allowed in strict mode");
} else if (c == quote) {
pos = p;
int len = p - start - 1;
if (builder == null) {
@ -1461,8 +1516,8 @@ public class JsonReader implements Closeable {
}
private void checkLenient() throws IOException {
if (!lenient) {
throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON");
if (strictness != Strictness.LENIENT) {
throw syntaxError("Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON");
}
}
@ -1636,11 +1691,17 @@ public class JsonReader implements Closeable {
return '\f';
case '\n':
if (strictness == Strictness.STRICT) {
throw syntaxError("Cannot escape a newline character in strict mode");
}
lineNumber++;
lineStart = pos;
// fall-through
case '\'':
if (strictness == Strictness.STRICT) {
throw syntaxError("Invalid escaped character \"'\" in strict mode");
}
case '"':
case '\\':
case '/':

View File

@ -26,6 +26,7 @@ import static com.google.gson.stream.JsonScope.NONEMPTY_OBJECT;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gson.FormattingStyle;
import com.google.gson.Strictness;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
@ -39,7 +40,7 @@ import java.util.concurrent.atomic.AtomicLong;
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
* literal values (strings, numbers, booleans and nulls) as well as the begin
* 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]+)?");
/*
* From RFC 7159, "All Unicode characters may be placed within the
* From RFC 8259, "All Unicode characters may be placed within the
* quotation marks except for the characters that must be escaped:
* quotation mark, reverse solidus, and the control characters
* (U+0000 through U+001F)."
@ -188,7 +189,7 @@ public class JsonWriter implements Closeable, Flushable {
private String formattedComma;
private boolean usesEmptyNewlineAndIndent;
private boolean lenient;
private Strictness strictness = Strictness.LEGACY_STRICT;
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
* only emits well-formed JSON as specified by <a
* href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>. Setting the writer
* to lenient permits the following:
* <ul>
* <li>Numbers may be {@link Double#isNaN() NaNs} or {@link
* Double#isInfinite() infinities}.
* </ul>
* Sets the strictness of this writer.
*
* @deprecated Please use {@link #setStrictness(Strictness)} instead.
* {@code JsonWriter.setLenient(true)} should be replaced by {@code JsonWriter.setStrictness(Strictness.LENIENT)}
* and {@code JsonWriter.setLenient(false)} should be replaced by {@code JsonWriter.setStrictness(Strictness.LEGACY_STRICT)}.<br>
* However, if you used {@code setLenient(false)} before, you might prefer {@link Strictness#STRICT} now instead.
*
* @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) {
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() {
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 >},
* {@code &} and {@code =} before writing them to the stream. Without this
* 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 {
Objects.requireNonNull(name, "name == null");
if (deferredName != null) {
throw new IllegalStateException();
throw new IllegalStateException("Already wrote a name, expecting a value.");
}
if (stackSize == 0) {
throw new IllegalStateException("JsonWriter is closed.");
@ -545,18 +586,18 @@ public class JsonWriter implements Closeable, Flushable {
/**
* 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()
* infinity}.
* @return this writer.
* @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
*/
@CanIgnoreReturnValue
public JsonWriter value(float value) throws IOException {
writeDeferredName();
if (!lenient && (Float.isNaN(value) || Float.isInfinite(value))) {
if (strictness != Strictness.LENIENT && (Float.isNaN(value) || Float.isInfinite(value))) {
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
}
beforeValue();
@ -567,16 +608,16 @@ public class JsonWriter implements Closeable, Flushable {
/**
* 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}.
* @return this writer.
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is
* not {@link #setLenient(boolean) lenient}.
* not {@link #setStrictness(Strictness) lenient}.
*/
@CanIgnoreReturnValue
public JsonWriter value(double value) throws IOException {
writeDeferredName();
if (!lenient && (Double.isNaN(value) || Double.isInfinite(value))) {
if (strictness != Strictness.LENIENT && (Double.isNaN(value) || Double.isInfinite(value))) {
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
}
beforeValue();
@ -612,11 +653,11 @@ public class JsonWriter implements Closeable, Flushable {
* 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.
*
* @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}.
* @return this writer.
* @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.
*/
@CanIgnoreReturnValue
@ -628,7 +669,7 @@ public class JsonWriter implements Closeable, Flushable {
writeDeferredName();
String string = value.toString();
if (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN")) {
if (!lenient) {
if (strictness != Strictness.LENIENT) {
throw new IllegalArgumentException("Numeric values must be finite, but was " + string);
}
} else {
@ -737,7 +778,7 @@ public class JsonWriter implements Closeable, Flushable {
private void beforeValue() throws IOException {
switch (peek()) {
case NONEMPTY_DOCUMENT:
if (!lenient) {
if (strictness != Strictness.LENIENT) {
throw new IllegalStateException(
"JSON must have only one top-level value.");
}

View File

@ -16,11 +16,12 @@
package com.google.gson.stream;
import com.google.gson.Strictness;
import java.io.IOException;
/**
* 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 {
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.JsonWriter;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
@ -224,4 +226,32 @@ public class GsonBuilderTest {
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_NUMBER_TO_NUMBER_STRATEGY = ToNumberPolicy.LAZILY_PARSED_NUMBER;
@Test
public void testStrictnessDefault() {
assertThat(new Gson().strictness).isNull();
}
@Test
public void testOverridesDefaultExcluder() {
Gson gson = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false,
FormattingStyle.PRETTY, true, false, true,
FormattingStyle.PRETTY, Strictness.LENIENT, false, true,
LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
@ -80,7 +85,7 @@ public final class GsonTest {
public void testClonedTypeAdapterFactoryListsAreIndependent() {
Gson original = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false,
FormattingStyle.PRETTY, true, false, true,
FormattingStyle.PRETTY, Strictness.LENIENT, false, true,
LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
@ -286,6 +291,7 @@ public final class GsonTest {
assertThat(writer.toString()).isEqualTo("{\"\\u003ctest2\":true}");
}
@SuppressWarnings({"deprecation", "InlineMeInliner"}) // for GsonBuilder.setLenient
@Test
public void testNewJsonWriter_Custom() throws IOException {
StringWriter writer = new StringWriter();
@ -319,6 +325,7 @@ public final class GsonTest {
jsonReader.close();
}
@SuppressWarnings({"deprecation", "InlineMeInliner"}) // for GsonBuilder.setLenient
@Test
public void testNewJsonReader_Custom() throws IOException {
String json = "test"; // String without quotes

View File

@ -163,7 +163,7 @@ public class JsonParserTest {
CharArrayReader reader = new CharArrayReader(writer.toCharArray());
JsonReader parser = new JsonReader(reader);
parser.setLenient(true);
parser.setStrictness(Strictness.LENIENT);
JsonElement element1 = Streams.parse(parser);
JsonElement element2 = Streams.parse(parser);
BagOfPrimitives actualOne = gson.fromJson(element1, BagOfPrimitives.class);
@ -171,4 +171,16 @@ public class JsonParserTest {
BagOfPrimitives actualTwo = gson.fromJson(element2, BagOfPrimitives.class);
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();
}
@SuppressWarnings("deprecation") // for JsonReader.setLenient
@Test
public void testReaderDoesNotMutateState() throws IOException {
Gson gson = new Gson();
@ -96,6 +97,7 @@ public final class MixedStreamTest {
assertThat(jsonReader.isLenient()).isTrue();
}
@SuppressWarnings("deprecation") // for JsonWriter.setLenient
@Test
public void testWriteDoesNotMutateState() throws IOException {
Gson gson = new Gson();

View File

@ -70,17 +70,17 @@ public class ToNumberPolicyTest {
e = assertThrows(MalformedJsonException.class, () -> strategy.readNumber(fromString("NaN")));
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");
e = assertThrows(MalformedJsonException.class, () -> strategy.readNumber(fromString("Infinity")));
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");
e = assertThrows(MalformedJsonException.class, () -> strategy.readNumber(fromString("-Infinity")));
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");
}
@ -120,7 +120,7 @@ public class ToNumberPolicyTest {
private static JsonReader fromStringLenient(String json) {
JsonReader jsonReader = fromString(json);
jsonReader.setLenient(true);
jsonReader.setStrictness(Strictness.LENIENT);
return jsonReader;
}
}

View File

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

View File

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

View File

@ -26,7 +26,6 @@ import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.junit.Before;
@ -92,7 +91,7 @@ public class PrettyPrintingTest {
assertThat(json).isEqualTo("[\n [\n 1,\n 2\n ],\n [\n 3,\n 4\n ],\n [\n 5,\n 6\n ],"
+ "\n [\n 7,\n 8\n ],\n [\n 9,\n 0\n ],\n [\n 10\n ]\n]");
}
@Test
public void testMap() {
Map<String, Integer> map = new LinkedHashMap<>();

View File

@ -28,7 +28,6 @@ import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.common.TestTypes.ClassOverridingEquals;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
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.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.Strictness;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.MalformedJsonException;
import java.io.IOException;
@ -45,7 +46,7 @@ public final class JsonElementReaderTest {
public void testLenientNansAndInfinities() throws IOException {
JsonElement element = JsonParser.parseString("[NaN, -Infinity, Infinity]");
JsonTreeReader reader = new JsonTreeReader(element);
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(Double.isNaN(reader.nextDouble())).isTrue();
assertThat(reader.nextDouble()).isEqualTo(Double.NEGATIVE_INFINITY);
@ -57,7 +58,7 @@ public final class JsonElementReaderTest {
public void testStrictNansAndInfinities() throws IOException {
JsonElement element = JsonParser.parseString("[NaN, -Infinity, Infinity]");
JsonTreeReader reader = new JsonTreeReader(element);
reader.setLenient(false);
reader.setStrictness(Strictness.LEGACY_STRICT);
reader.beginArray();
try {
reader.nextDouble();

View File

@ -144,7 +144,7 @@ public class JsonTreeReaderTest {
*/
@Test
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);
}
}

View File

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

View File

@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue;
import com.google.gson.JsonElement;
import com.google.gson.Strictness;
import com.google.gson.internal.Streams;
import com.google.gson.internal.bind.JsonTreeReader;
import java.io.IOException;
@ -201,7 +202,7 @@ public class JsonReaderPathTest {
assumeTrue(factory == Factory.STRING_READER);
JsonReader reader = factory.create("[][]");
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
reader.endArray();
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.NUMBER;
import static com.google.gson.stream.JsonToken.STRING;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import com.google.gson.Strictness;
import java.io.EOFException;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.Arrays;
import com.google.gson.stream.JsonToken;
import org.junit.Ignore;
import org.junit.Test;
@SuppressWarnings("resource")
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
public void testReadArray() throws IOException {
JsonReader reader = new JsonReader(reader("[true, true]"));
@ -167,7 +306,7 @@ public final class JsonReaderTest {
@Test
public void testSkipObjectNameSingleQuoted() throws IOException {
JsonReader reader = new JsonReader(reader("{'a': 1}"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginObject();
reader.skipValue();
assertThat(reader.peek()).isEqualTo(JsonToken.NUMBER);
@ -178,7 +317,7 @@ public final class JsonReaderTest {
@Test
public void testSkipObjectNameUnquoted() throws IOException {
JsonReader reader = new JsonReader(reader("{a: 1}"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginObject();
reader.skipValue();
assertThat(reader.peek()).isEqualTo(JsonToken.NUMBER);
@ -350,6 +489,50 @@ public final class JsonReaderTest {
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
public void testUnescapingInvalidCharacters() throws IOException {
String json = "[\"\\u000g\"]";
@ -476,7 +659,7 @@ public final class JsonReaderTest {
public void testLenientNonFiniteDoubles() throws IOException {
String json = "[NaN, -Infinity, Infinity]";
JsonReader reader = new JsonReader(reader(json));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(Double.isNaN(reader.nextDouble())).isTrue();
assertThat(reader.nextDouble()).isEqualTo(Double.NEGATIVE_INFINITY);
@ -488,7 +671,7 @@ public final class JsonReaderTest {
public void testLenientQuotedNonFiniteDoubles() throws IOException {
String json = "[\"NaN\", \"-Infinity\", \"Infinity\"]";
JsonReader reader = new JsonReader(reader(json));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(Double.isNaN(reader.nextDouble())).isTrue();
assertThat(reader.nextDouble()).isEqualTo(Double.NEGATIVE_INFINITY);
@ -591,7 +774,7 @@ public final class JsonReaderTest {
@Test
public void testPeekingUnquotedStringsPrefixedWithBooleans() throws IOException {
JsonReader reader = new JsonReader(reader("[truey]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.peek()).isEqualTo(STRING);
try {
@ -651,7 +834,7 @@ public final class JsonReaderTest {
private void assertNotANumber(String s) throws IOException {
JsonReader reader = new JsonReader(reader(s));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
assertThat(reader.peek()).isEqualTo(JsonToken.STRING);
assertThat(reader.nextString()).isEqualTo(s);
@ -660,14 +843,14 @@ public final class JsonReaderTest {
strictReader.nextDouble();
fail("Should have failed reading " + s + " as double");
} 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
public void testPeekingUnquotedStringsPrefixedWithIntegers() throws IOException {
JsonReader reader = new JsonReader(reader("[12.34e5x]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.peek()).isEqualTo(STRING);
try {
@ -681,7 +864,7 @@ public final class JsonReaderTest {
@Test
public void testPeekLongMinValue() throws IOException {
JsonReader reader = new JsonReader(reader("[-9223372036854775808]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.peek()).isEqualTo(NUMBER);
assertThat(reader.nextLong()).isEqualTo(-9223372036854775808L);
@ -690,7 +873,7 @@ public final class JsonReaderTest {
@Test
public void testPeekLongMaxValue() throws IOException {
JsonReader reader = new JsonReader(reader("[9223372036854775807]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.peek()).isEqualTo(NUMBER);
assertThat(reader.nextLong()).isEqualTo(9223372036854775807L);
@ -699,7 +882,7 @@ public final class JsonReaderTest {
@Test
public void testLongLargerThanMaxLongThatWrapsAround() throws IOException {
JsonReader reader = new JsonReader(reader("[22233720368547758070]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.peek()).isEqualTo(NUMBER);
try {
@ -712,7 +895,7 @@ public final class JsonReaderTest {
@Test
public void testLongLargerThanMinLongThatWrapsAround() throws IOException {
JsonReader reader = new JsonReader(reader("[-22233720368547758070]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.peek()).isEqualTo(NUMBER);
try {
@ -728,7 +911,7 @@ public final class JsonReaderTest {
@Test
public void testNegativeZero() throws Exception {
JsonReader reader = new JsonReader(reader("[-0]"));
reader.setLenient(false);
reader.setStrictness(Strictness.LEGACY_STRICT);
reader.beginArray();
assertThat(reader.peek()).isEqualTo(NUMBER);
assertThat(reader.nextString()).isEqualTo("-0");
@ -742,7 +925,7 @@ public final class JsonReaderTest {
@Ignore
public void testPeekLargerThanLongMaxValue() throws IOException {
JsonReader reader = new JsonReader(reader("[9223372036854775808]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.peek()).isEqualTo(NUMBER);
try {
@ -762,7 +945,7 @@ public final class JsonReaderTest {
@SuppressWarnings("FloatingPointLiteralPrecision")
double d = -9223372036854775809d;
JsonReader reader = new JsonReader(reader("[-9223372036854775809]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.peek()).isEqualTo(NUMBER);
try {
@ -792,7 +975,7 @@ public final class JsonReaderTest {
@SuppressWarnings("FloatingPointLiteralPrecision")
double d = -92233720368547758080d;
JsonReader reader = new JsonReader(reader("[-92233720368547758080]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.peek()).isEqualTo(NUMBER);
try {
@ -806,7 +989,7 @@ public final class JsonReaderTest {
@Test
public void testQuotedNumberWithEscape() throws IOException {
JsonReader reader = new JsonReader(reader("[\"12\\u00334\"]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.peek()).isEqualTo(STRING);
assertThat(reader.nextInt()).isEqualTo(1234);
@ -1021,13 +1204,13 @@ public final class JsonReaderTest {
@Test
public void testLenientNameValueSeparator() throws IOException {
JsonReader reader = new JsonReader(reader("{\"a\"=true}"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a");
assertThat(reader.nextBoolean()).isTrue();
reader = new JsonReader(reader("{\"a\"=>true}"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a");
assertThat(reader.nextBoolean()).isTrue();
@ -1109,17 +1292,17 @@ public final class JsonReaderTest {
@Test
public void testLenientComments() throws IOException {
JsonReader reader = new JsonReader(reader("[// comment \n true]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.nextBoolean()).isTrue();
reader = new JsonReader(reader("[# comment \n true]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.nextBoolean()).isTrue();
reader = new JsonReader(reader("[/* comment */ true]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.nextBoolean()).isTrue();
}
@ -1169,7 +1352,7 @@ public final class JsonReaderTest {
@Test
public void testLenientUnquotedNames() throws IOException {
JsonReader reader = new JsonReader(reader("{a:true}"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a");
}
@ -1201,7 +1384,7 @@ public final class JsonReaderTest {
@Test
public void testLenientSingleQuotedNames() throws IOException {
JsonReader reader = new JsonReader(reader("{'a':true}"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a");
}
@ -1245,7 +1428,7 @@ public final class JsonReaderTest {
@Test
public void testLenientUnquotedStrings() throws IOException {
JsonReader reader = new JsonReader(reader("[a]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.nextString()).isEqualTo("a");
}
@ -1265,7 +1448,7 @@ public final class JsonReaderTest {
@Test
public void testLenientSingleQuotedStrings() throws IOException {
JsonReader reader = new JsonReader(reader("['a']"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.nextString()).isEqualTo("a");
}
@ -1297,7 +1480,7 @@ public final class JsonReaderTest {
@Test
public void testLenientSemicolonDelimitedArray() throws IOException {
JsonReader reader = new JsonReader(reader("[true;true]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.nextBoolean()).isTrue();
assertThat(reader.nextBoolean()).isTrue();
@ -1331,7 +1514,7 @@ public final class JsonReaderTest {
@Test
public void testLenientSemicolonDelimitedNameValuePair() throws IOException {
JsonReader reader = new JsonReader(reader("{\"a\":true;\"b\":true}"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a");
assertThat(reader.nextBoolean()).isTrue();
@ -1395,7 +1578,7 @@ public final class JsonReaderTest {
@Test
public void testLenientUnnecessaryArraySeparators() throws IOException {
JsonReader reader = new JsonReader(reader("[true,,true]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.nextBoolean()).isTrue();
reader.nextNull();
@ -1403,21 +1586,21 @@ public final class JsonReaderTest {
reader.endArray();
reader = new JsonReader(reader("[,true]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
reader.nextNull();
assertThat(reader.nextBoolean()).isTrue();
reader.endArray();
reader = new JsonReader(reader("[true,]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.nextBoolean()).isTrue();
reader.nextNull();
reader.endArray();
reader = new JsonReader(reader("[,]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
reader.nextNull();
reader.nextNull();
@ -1481,7 +1664,7 @@ public final class JsonReaderTest {
@Test
public void testLenientMultipleTopLevelValues() throws IOException {
JsonReader reader = new JsonReader(reader("[] true {}"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
reader.endArray();
assertThat(reader.nextBoolean()).isTrue();
@ -1563,7 +1746,7 @@ public final class JsonReaderTest {
@Test
public void testLenientNonExecutePrefix() throws IOException {
JsonReader reader = new JsonReader(reader(")]}'\n []"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
reader.endArray();
assertThat(reader.peek()).isEqualTo(JsonToken.END_DOCUMENT);
@ -1572,7 +1755,7 @@ public final class JsonReaderTest {
@Test
public void testLenientNonExecutePrefixWithLeadingWhitespace() throws IOException {
JsonReader reader = new JsonReader(reader("\r\n \t)]}'\n []"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
reader.endArray();
assertThat(reader.peek()).isEqualTo(JsonToken.END_DOCUMENT);
@ -1581,7 +1764,7 @@ public final class JsonReaderTest {
@Test
public void testLenientPartialNonExecutePrefix() throws IOException {
JsonReader reader = new JsonReader(reader(")]}' []"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
assertThat(reader.nextString()).isEqualTo(")");
try {
reader.nextString();
@ -1667,7 +1850,7 @@ public final class JsonReaderTest {
private void testFailWithPosition(String message, String json) throws IOException {
// Validate that it works reading the string normally.
JsonReader reader1 = new JsonReader(reader(json));
reader1.setLenient(true);
reader1.setStrictness(Strictness.LENIENT);
reader1.beginArray();
String unused1 = reader1.nextString();
try {
@ -1679,7 +1862,7 @@ public final class JsonReaderTest {
// Also validate that it works when skipping.
JsonReader reader2 = new JsonReader(reader(json));
reader2.setLenient(true);
reader2.setStrictness(Strictness.LENIENT);
reader2.beginArray();
reader2.skipValue();
try {
@ -1725,7 +1908,7 @@ public final class JsonReaderTest {
@Test
public void testLenientVeryLongNumber() throws IOException {
JsonReader reader = new JsonReader(reader("[0." + repeat('9', 8192) + "]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.peek()).isEqualTo(JsonToken.STRING);
assertThat(reader.nextDouble()).isEqualTo(1d);
@ -1737,7 +1920,7 @@ public final class JsonReaderTest {
public void testVeryLongUnquotedLiteral() throws IOException {
String literal = "a" + repeat('b', 8192) + "c";
JsonReader reader = new JsonReader(reader("[" + literal + "]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.nextString()).isEqualTo(literal);
reader.endArray();
@ -1786,7 +1969,7 @@ public final class JsonReaderTest {
@Test
public void testStringEndingInSlash() throws IOException {
JsonReader reader = new JsonReader(reader("/"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
try {
reader.peek();
fail();
@ -1799,7 +1982,7 @@ public final class JsonReaderTest {
@Test
public void testDocumentWithCommentEndingInSlash() throws IOException {
JsonReader reader = new JsonReader(reader("/* foo *//"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
try {
reader.peek();
fail();
@ -1812,7 +1995,7 @@ public final class JsonReaderTest {
@Test
public void testStringWithLeadingSlash() throws IOException {
JsonReader reader = new JsonReader(reader("/x"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
try {
reader.peek();
fail();
@ -1825,7 +2008,7 @@ public final class JsonReaderTest {
@Test
public void testUnterminatedObject() throws IOException {
JsonReader reader = new JsonReader(reader("{\"a\":\"android\"x"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a");
assertThat(reader.nextString()).isEqualTo("android");
@ -1857,7 +2040,7 @@ public final class JsonReaderTest {
String string = new String(stringChars);
String json = "[" + string + "]";
JsonReader reader = new JsonReader(reader(json));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.nextString()).isEqualTo(string);
reader.endArray();
@ -1870,7 +2053,7 @@ public final class JsonReaderTest {
String string = new String(stringChars);
String json = "[" + string;
JsonReader reader = new JsonReader(reader(json));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.nextString()).isEqualTo(string);
try {
@ -1883,7 +2066,7 @@ public final class JsonReaderTest {
@Test
public void testSkipVeryLongUnquotedString() throws IOException {
JsonReader reader = new JsonReader(reader("[" + repeat('x', 8192) + "]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
reader.skipValue();
reader.endArray();
@ -1892,7 +2075,7 @@ public final class JsonReaderTest {
@Test
public void testSkipTopLevelUnquotedString() throws IOException {
JsonReader reader = new JsonReader(reader(repeat('x', 8192)));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.skipValue();
assertThat(reader.peek()).isEqualTo(JsonToken.END_DOCUMENT);
}
@ -1908,7 +2091,7 @@ public final class JsonReaderTest {
@Test
public void testSkipTopLevelQuotedString() throws IOException {
JsonReader reader = new JsonReader(reader("\"" + repeat('x', 8192) + "\""));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.skipValue();
assertThat(reader.peek()).isEqualTo(JsonToken.END_DOCUMENT);
}
@ -1916,7 +2099,7 @@ public final class JsonReaderTest {
@Test
public void testStringAsNumberWithTruncatedExponent() throws IOException {
JsonReader reader = new JsonReader(reader("[123e]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.peek()).isEqualTo(STRING);
}
@ -1924,7 +2107,7 @@ public final class JsonReaderTest {
@Test
public void testStringAsNumberWithDigitAndNonDigitExponent() throws IOException {
JsonReader reader = new JsonReader(reader("[123e4b]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.peek()).isEqualTo(STRING);
}
@ -1932,7 +2115,7 @@ public final class JsonReaderTest {
@Test
public void testStringAsNumberWithNonDigitExponent() throws IOException {
JsonReader reader = new JsonReader(reader("[123eb]"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.peek()).isEqualTo(STRING);
}
@ -1940,7 +2123,7 @@ public final class JsonReaderTest {
@Test
public void testEmptyStringName() throws IOException {
JsonReader reader = new JsonReader(reader("{\"\":true}"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
assertThat(reader.peek()).isEqualTo(BEGIN_OBJECT);
reader.beginObject();
assertThat(reader.peek()).isEqualTo(NAME);
@ -1970,7 +2153,7 @@ public final class JsonReaderTest {
@Test
public void testLenientExtraCommasInMaps() throws IOException {
JsonReader reader = new JsonReader(reader("{\"a\":\"b\",}"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a");
assertThat(reader.nextString()).isEqualTo("b");
@ -2039,7 +2222,7 @@ public final class JsonReaderTest {
@Test
public void testUnterminatedStringFailure() throws IOException {
JsonReader reader = new JsonReader(reader("[\"string"));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
reader.beginArray();
assertThat(reader.peek()).isEqualTo(JsonToken.STRING);
try {
@ -2062,13 +2245,13 @@ public final class JsonReaderTest {
}
sb.append("\n)]}'\n3");
JsonReader reader = new JsonReader(reader(sb.toString()));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
JsonToken token = reader.peek();
assertThat(token).isEqualTo(JsonToken.NUMBER);
}
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");
}
@ -2080,7 +2263,7 @@ public final class JsonReaderTest {
private void assertDocument(String document, Object... expectations) throws IOException {
JsonReader reader = new JsonReader(reader(document));
reader.setLenient(true);
reader.setStrictness(Strictness.LENIENT);
for (Object expectation : expectations) {
if (expectation == BEGIN_OBJECT) {
reader.beginObject();

View File

@ -17,9 +17,11 @@
package com.google.gson.stream;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import com.google.gson.FormattingStyle;
import com.google.gson.Strictness;
import com.google.gson.internal.LazilyParsedNumber;
import java.io.IOException;
import java.io.StringWriter;
@ -30,6 +32,51 @@ import org.junit.Test;
@SuppressWarnings("resource")
public final class JsonWriterTest {
@Test
public void testDefaultStrictness() throws IOException {
JsonWriter jsonWriter = new JsonWriter(new StringWriter());
assertThat(jsonWriter.getStrictness()).isEqualTo(Strictness.LEGACY_STRICT);
jsonWriter.value(false);
jsonWriter.close();
}
@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
public void testTopLevelValueTypes() throws IOException {
StringWriter string1 = new StringWriter();
@ -67,11 +114,12 @@ public final class JsonWriterTest {
public void testInvalidTopLevelTypes() throws IOException {
StringWriter stringWriter = new 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 {
jsonWriter.value("world");
fail();
} catch (IllegalStateException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Nesting problem.");
}
}
@ -85,6 +133,7 @@ public final class JsonWriterTest {
jsonWriter.name("a");
fail();
} catch (IllegalStateException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Already wrote a name, expecting a value.");
}
}
@ -98,6 +147,7 @@ public final class JsonWriterTest {
jsonWriter.endObject();
fail();
} catch (IllegalStateException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Dangling name: a");
}
}
@ -110,6 +160,7 @@ public final class JsonWriterTest {
jsonWriter.value(true);
fail();
} catch (IllegalStateException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Nesting problem.");
}
}
@ -118,11 +169,33 @@ public final class JsonWriterTest {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray().endArray();
try {
jsonWriter.beginArray();
fail();
} catch (IllegalStateException expected) {
}
IllegalStateException expected = assertThrows(IllegalStateException.class, jsonWriter::beginArray);
assertThat(expected).hasMessageThat().isEqualTo("JSON must have only one top-level value.");
}
@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
@ -135,6 +208,7 @@ public final class JsonWriterTest {
jsonWriter.endArray();
fail();
} catch (IllegalStateException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Nesting problem.");
}
}
@ -148,6 +222,7 @@ public final class JsonWriterTest {
jsonWriter.endObject();
fail();
} catch (IllegalStateException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Nesting problem.");
}
}
@ -192,24 +267,32 @@ public final class JsonWriterTest {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray();
try {
jsonWriter.value(Float.NaN);
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was NaN");
}
try {
jsonWriter.value(Float.NEGATIVE_INFINITY);
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was -Infinity");
}
try {
jsonWriter.value(Float.POSITIVE_INFINITY);
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was Infinity");
}
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
public void testNonFiniteFloatsWhenStrict() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setStrictness(Strictness.STRICT);
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
@ -217,24 +300,32 @@ public final class JsonWriterTest {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray();
try {
jsonWriter.value(Double.NaN);
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was NaN");
}
try {
jsonWriter.value(Double.NEGATIVE_INFINITY);
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was -Infinity");
}
try {
jsonWriter.value(Double.POSITIVE_INFINITY);
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was Infinity");
}
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
public void testNonFiniteDoublesWhenStrict() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setStrictness(Strictness.STRICT);
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
@ -242,37 +333,45 @@ public final class JsonWriterTest {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray();
try {
jsonWriter.value(Double.valueOf(Double.NaN));
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was NaN");
}
try {
jsonWriter.value(Double.valueOf(Double.NEGATIVE_INFINITY));
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was -Infinity");
}
try {
jsonWriter.value(Double.valueOf(Double.POSITIVE_INFINITY));
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was Infinity");
}
try {
jsonWriter.value(new LazilyParsedNumber("Infinity"));
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Numeric values must be finite, but was Infinity");
}
IllegalArgumentException expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Double.valueOf(Double.NaN)));
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
public void testNonFiniteNumbersWhenStrict() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setStrictness(Strictness.STRICT);
jsonWriter.beginArray();
IllegalArgumentException expected = assertThrows(IllegalArgumentException.class, () -> jsonWriter.value(Double.valueOf(Double.NaN)));
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
public void testNonFiniteFloatsWhenLenient() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setLenient(true);
jsonWriter.setStrictness(Strictness.LENIENT);
jsonWriter.beginArray();
jsonWriter.value(Float.NaN);
jsonWriter.value(Float.NEGATIVE_INFINITY);
@ -285,7 +384,7 @@ public final class JsonWriterTest {
public void testNonFiniteDoublesWhenLenient() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setLenient(true);
jsonWriter.setStrictness(Strictness.LENIENT);
jsonWriter.beginArray();
jsonWriter.value(Double.NaN);
jsonWriter.value(Double.NEGATIVE_INFINITY);
@ -298,7 +397,7 @@ public final class JsonWriterTest {
public void testNonFiniteNumbersWhenLenient() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setLenient(true);
jsonWriter.setStrictness(Strictness.LENIENT);
jsonWriter.beginArray();
jsonWriter.value(Double.valueOf(Double.NaN));
jsonWriter.value(Double.valueOf(Double.NEGATIVE_INFINITY));
@ -561,6 +660,8 @@ public final class JsonWriterTest {
jsonWriter.beginArray();
jsonWriter.value("\u2028 \u2029");
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\"]");
}
@ -739,32 +840,6 @@ public final class JsonWriterTest {
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
public void testClosedWriterThrowsOnStructure() throws IOException {
StringWriter stringWriter = new StringWriter();