From 3360c93a76a74d1182b912052973e08ffa868b43 Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Sun, 17 Jan 2016 03:03:04 -0500 Subject: [PATCH] Add setting for leniency on Gson instance. Add a JsonReader factory (for parity with the JsonWriter one) which provides a configured instance using the Gson settings. --- gson/src/main/java/com/google/gson/Gson.java | 30 +++++++++--- .../java/com/google/gson/GsonBuilder.java | 37 ++++++++++++--- .../com/google/gson/stream/JsonReader.java | 2 +- .../google/gson/functional/LeniencyTest.java | 46 +++++++++++++++++++ 4 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 gson/src/test/java/com/google/gson/functional/LeniencyTest.java diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index 2ba47cce..85fa6741 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -101,6 +101,12 @@ import com.google.gson.stream.MalformedJsonException; */ public final class Gson { static final boolean DEFAULT_JSON_NON_EXECUTABLE = false; + static final boolean DEFAULT_LENIENT = false; + static final boolean DEFAULT_PRETTY_PRINT = false; + static final boolean DEFAULT_ESCAPE_HTML = true; + static final boolean DEFAULT_SERIALIZE_NULLS = false; + static final boolean DEFAULT_COMPLEX_MAP_KEYS = false; + static final boolean DEFAULT_SPECIALIZE_FLOAT_VALUES = false; private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n"; @@ -124,6 +130,7 @@ public final class Gson { private final boolean htmlSafe; private final boolean generateNonExecutableJson; private final boolean prettyPrinting; + private final boolean lenient; final JsonDeserializationContext deserializationContext = new JsonDeserializationContext() { @SuppressWarnings("unchecked") @@ -177,15 +184,16 @@ public final class Gson { */ public Gson() { this(Excluder.DEFAULT, FieldNamingPolicy.IDENTITY, - Collections.>emptyMap(), false, false, DEFAULT_JSON_NON_EXECUTABLE, - true, false, false, LongSerializationPolicy.DEFAULT, - Collections.emptyList()); + Collections.>emptyMap(), DEFAULT_SERIALIZE_NULLS, + DEFAULT_COMPLEX_MAP_KEYS, DEFAULT_JSON_NON_EXECUTABLE, DEFAULT_ESCAPE_HTML, + DEFAULT_PRETTY_PRINT, DEFAULT_LENIENT, DEFAULT_SPECIALIZE_FLOAT_VALUES, + LongSerializationPolicy.DEFAULT, Collections.emptyList()); } Gson(final Excluder excluder, final FieldNamingStrategy fieldNamingPolicy, final Map> instanceCreators, boolean serializeNulls, boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe, - boolean prettyPrinting, boolean serializeSpecialFloatingPointValues, + boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues, LongSerializationPolicy longSerializationPolicy, List typeAdapterFactories) { this.constructorConstructor = new ConstructorConstructor(instanceCreators); @@ -193,6 +201,7 @@ public final class Gson { this.generateNonExecutableJson = generateNonExecutableGson; this.htmlSafe = htmlSafe; this.prettyPrinting = prettyPrinting; + this.lenient = lenient; List factories = new ArrayList(); @@ -704,6 +713,15 @@ public final class Gson { return jsonWriter; } + /** + * Returns a new JSON writer configured for the settings on this Gson instance. + */ + public JsonReader newJsonReader(Reader reader) { + JsonReader jsonReader = new JsonReader(reader); + jsonReader.setLenient(lenient); + return jsonReader; + } + /** * Writes the JSON for {@code jsonElement} to {@code writer}. * @throws JsonIOException if there was a problem writing to the writer @@ -795,7 +813,7 @@ public final class Gson { * @since 1.2 */ public T fromJson(Reader json, Class classOfT) throws JsonSyntaxException, JsonIOException { - JsonReader jsonReader = new JsonReader(json); + JsonReader jsonReader = newJsonReader(json); Object object = fromJson(jsonReader, classOfT); assertFullConsumption(object, jsonReader); return Primitives.wrap(classOfT).cast(object); @@ -822,7 +840,7 @@ public final class Gson { */ @SuppressWarnings("unchecked") public T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException { - JsonReader jsonReader = new JsonReader(json); + JsonReader jsonReader = newJsonReader(json); T object = (T) fromJson(jsonReader, typeOfT); assertFullConsumption(object, jsonReader); return object; diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java index e6c0b8c0..ca87fbb8 100644 --- a/gson/src/main/java/com/google/gson/GsonBuilder.java +++ b/gson/src/main/java/com/google/gson/GsonBuilder.java @@ -16,6 +16,7 @@ package com.google.gson; +import com.google.gson.stream.JsonReader; import java.lang.reflect.Type; import java.sql.Timestamp; import java.text.DateFormat; @@ -31,6 +32,14 @@ import com.google.gson.internal.Excluder; import com.google.gson.internal.bind.TypeAdapters; import com.google.gson.reflect.TypeToken; +import static com.google.gson.Gson.DEFAULT_COMPLEX_MAP_KEYS; +import static com.google.gson.Gson.DEFAULT_ESCAPE_HTML; +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_PRETTY_PRINT; +import static com.google.gson.Gson.DEFAULT_SERIALIZE_NULLS; +import static com.google.gson.Gson.DEFAULT_SPECIALIZE_FLOAT_VALUES; + /** *

Use this builder to construct a {@link Gson} instance when you need to set configuration * options other than the default. For {@link Gson} with default configuration, it is simpler to @@ -74,15 +83,16 @@ public final class GsonBuilder { private final List factories = new ArrayList(); /** tree-style hierarchy factories. These come after factories for backwards compatibility. */ private final List hierarchyFactories = new ArrayList(); - private boolean serializeNulls; + private boolean serializeNulls = DEFAULT_SERIALIZE_NULLS; private String datePattern; private int dateStyle = DateFormat.DEFAULT; private int timeStyle = DateFormat.DEFAULT; - private boolean complexMapKeySerialization; - private boolean serializeSpecialFloatingPointValues; - private boolean escapeHtmlChars = true; - private boolean prettyPrinting; - private boolean generateNonExecutableJson; + private boolean complexMapKeySerialization = DEFAULT_COMPLEX_MAP_KEYS; + private boolean serializeSpecialFloatingPointValues = DEFAULT_SPECIALIZE_FLOAT_VALUES; + private boolean escapeHtmlChars = DEFAULT_ESCAPE_HTML; + private boolean prettyPrinting = DEFAULT_PRETTY_PRINT; + private boolean generateNonExecutableJson = DEFAULT_JSON_NON_EXECUTABLE; + private boolean lenient = DEFAULT_LENIENT; /** * Creates a GsonBuilder instance that can be used to build Gson with various configuration @@ -351,6 +361,19 @@ public final class GsonBuilder { return this; } + /** + * By default, Gson is strict and only accepts JSON as specified by + * RFC 4627. This option makes the parser + * liberal in what it accepts. + * + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @see JsonReader#setLenient(boolean) + */ + public GsonBuilder setLenient() { + lenient = true; + return this; + } + /** * By default, Gson escapes HTML characters such as < > etc. Use this option to configure * Gson to pass-through HTML characters as is. @@ -544,7 +567,7 @@ public final class GsonBuilder { return new Gson(excluder, fieldNamingPolicy, instanceCreators, serializeNulls, complexMapKeySerialization, - generateNonExecutableJson, escapeHtmlChars, prettyPrinting, + generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient, serializeSpecialFloatingPointValues, longSerializationPolicy, factories); } diff --git a/gson/src/main/java/com/google/gson/stream/JsonReader.java b/gson/src/main/java/com/google/gson/stream/JsonReader.java index 04dea075..ea738f82 100644 --- a/gson/src/main/java/com/google/gson/stream/JsonReader.java +++ b/gson/src/main/java/com/google/gson/stream/JsonReader.java @@ -294,7 +294,7 @@ public class JsonReader implements Closeable { } /** - * Configure this parser to be be liberal in what it accepts. By default, + * Configure this parser to be liberal in what it accepts. By default, * this parser is strict and only accepts JSON as specified by RFC 4627. Setting the * parser to lenient causes it to ignore the following syntax errors: diff --git a/gson/src/test/java/com/google/gson/functional/LeniencyTest.java b/gson/src/test/java/com/google/gson/functional/LeniencyTest.java new file mode 100644 index 00000000..6b5375e4 --- /dev/null +++ b/gson/src/test/java/com/google/gson/functional/LeniencyTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2016 The Gson Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.gson.functional; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import java.util.List; +import junit.framework.TestCase; + +import static java.util.Collections.singletonList; + +/** + * Functional tests for leniency option. + */ +public class LeniencyTest extends TestCase { + + private Gson gson; + + @Override + protected void setUp() throws Exception { + super.setUp(); + gson = new GsonBuilder().setLenient().create(); + } + + public void testLenientFromJson() { + List json = gson.fromJson("" + + "[ # One!\n" + + " 'Hi' #Element!\n" + + "] # Array!", new TypeToken>() {}.getType()); + assertEquals(singletonList("Hi"), json); + } +}