Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

respect the lenient flag in Gson.fromJson #804

Closed
wants to merge 1 commit into from

Conversation

sgbrown
Copy link
Contributor

@sgbrown sgbrown commented Mar 3, 2016

This fix causes Gson to respect the lenient flag without affecting the default behavior. Gson will continue to be lenient by default, but now the lenient flag will be respected if it is set to false in the GsonBuilder. The lenient flag will also be respected from a JsonReader. It is unfortunate that the default behavior of Gson and the default behavior of JsonReader are different with respect to the lenient flag; however, this seems to be the only solution that will respect the settings used without drastically changing the expected behavior.

The following is an explanation of some of the issues with the behavior prior to my changes. I think Gson.lenient flag probably does more harm than good in the previous state. The comments on the GsonBuilder.setLenient() method say

By default, Gson is strict and only accepts JSON as specified by RFC 4627.

The comments on Gson.setLenient() also references the JsonReader.setLenient(boolean) method which has a great explanation of what should and shouldn't be considered when using the lenient flag.

The unfortunate fact here is that due to the Gson.fromJson(JsonReader, Type) method always setting the lenient flag to true when parsing, almost none of what is commented in GsonBuilder about the default behavior of Gson is true. The only affect that setting the lenient flag on Gson will do is to allow comments to be at the end of your json buffer when calling Gson.fromJson(Reader, Type) since the assertFullConsumption(Object, JsonReader) method will be called from here and only checks for data at the end of the buffer having not been consumed (e.g. comments at the end).

Consider, for example, the test in com.google.gson.functional.LeniencyTest

    @Override
   protected void setUp() throws Exception {
     super.setUp();
     gson = new GsonBuilder().setLenient().create();
   }

   public void testLenientFromJson() {
     List<String> json = gson.fromJson(""
         + "[ # One!\n"
         + "  'Hi' #Element!\n"
         + "] # Array!", new TypeToken<List<String>>() {}.getType());
     assertEquals(singletonList("Hi"), json);
   }

If you were to remove the comment at the end of the String ("# Array!"), then it would not make any difference at all whether or not the lenient flag had been set. The following would also pass:

     List<String> json = new GsonBuilder().create().fromJson(""
         + "[ # One!\n"
         + "  'Hi' #Element!\n"
         + "]", new TypeToken<List<String>>() {}.getType());
     assertEquals(singletonList("Hi"), json);

It seems counterintuitive that with the lenient flag unset, comments in the middle of the JSON data would be ignored but comments at the end would cause a failure when the Gson parser is supposed to be "strict".

*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @see JsonReader#setLenient(boolean)
*/
public GsonBuilder setLenient() {
lenient = true;
public GsonBuilder setLenient(boolean lenient) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an API incompatible change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to maintain compatibility, we could add a setLenient(boolean) method to GsonBuilder in addition to keeping the setLenient(void) method even though calling the setLenient(void) method would not have much effect. Perhaps the setLenient(void) method should also be marked deprecated in order to discourage usage of a method that has little effect.

I also considered adding a setStrict(void) method which would set the lenient flag to false instead of having the setLenient(boolean) method

Would you be okay with either of these solutions?

@JakeWharton
Copy link
Contributor

The unfortunate fact here is that due to the Gson.fromJson(JsonReader, Type) method always setting the lenient flag to true when parsing

I've found it a best practice to avoid using convenience methods on Gson for this very reason. The Gson instance just becomes a place to lookup type adapters.

@sgbrown
Copy link
Contributor Author

sgbrown commented Mar 10, 2016

I've found it a best practice to avoid using convenience methods on Gson for this very reason. The Gson instance just becomes a place to lookup type adapters.

Having the behavior of Gson force a best practice of avoiding using convenience methods seems contrary to the first goal listed in the gson goals

Also, it may be worth referring to a comment @inder123 made on pull request #806 when considering your suggestion that Gson should just be used as a place to lookup type adapters:

Gson.toJson()/fromJson() work really well for most use-cases. Getting an adapter and using it is cumbersome, and sometimes doesn't produce correct results (I have had issues in the past where it will serialize nulls incorrectly, or not pretty print).

}

/**
* @deprecated Gson is lenient by default. Use {@link #setLenient(boolean)} if
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to deprecate.

@inder123
Copy link
Collaborator

👍 I like this change as it is a simple, backward-compatible way to enable a strict mode.
@swankjesse @JakeWharton Any concerns from your side? I am ready to merge this.

@inder123 inder123 self-assigned this Mar 11, 2016
@sgbrown sgbrown force-pushed the gson_lenient branch 2 times, most recently from 294fc67 to b76e3d1 Compare March 11, 2016 15:32
@sgbrown
Copy link
Contributor Author

sgbrown commented Mar 11, 2016

I just incorporated the comment from @inder123 about not deprecating the GsonBuilder.setLenient(void) method; and rebased and squashed my commits.

@swankjesse
Copy link
Collaborator

It's a good solution, well implemented.

I'm reluctant to change behavior like this in Gson. I think it's incompatible in potentially surprising ways.

An alternative is to recommend avoiding the convenience methods on Gson that suffer this problem. That's what I'll do in my applications. It's also what I recommend anyone who cares about performance does. Doing extra work for every serialized object is sloppy.

Since I'm not using these APIs, I'm okay if we merge this.

@sshock
Copy link

sshock commented Sep 9, 2020

Why wasn't this ever merged? It is very confusing to a newcomer that Gson is supposedly strict by default (as indicated both by comments and by the fact that DEFAULT_LENIENT = false), yet in reality it is not strict by default, at least not when using fromJson(), and there is also no way to make this strict.

@JakeWharton
Copy link
Contributor

JakeWharton commented Sep 10, 2020 via email

@sshock
Copy link

sshock commented Sep 10, 2020

Consider using Moshi where we fixed this behavior.

Good to know, but is there a reason not to merge this for Gson? Is it deemed too risky? If so, maybe the least we could do here is add a comment or something to indicate that the convenience methods like fromJson() are always lenient.

@JakeWharton
Copy link
Contributor

JakeWharton commented Sep 10, 2020 via email

@Marcono1234
Copy link
Collaborator

This seems to be obsolete with the Strictness mode API introduced by #2437 for Gson version 2.11.0, in particular the new method GsonBuilder#setStrictness.

Thanks nonetheless for the work you have put into this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants