4

The API we ingest returns some malformed JSON (in particular Double.NaN is serialized to a String with "NaN"). To allow this gson always sets lenient to true and correctly serializes this back to Double.NaN.

Now, we wanted to use the RuntimeTypeAdapterFactory class from gson-extras to remove some boilerplate code. The just described behaviour does not apply here anymore, we get the message

java.lang.NumberFormatException: JSON forbids NaN and infinities: NaN

which should have been ignored because of the lenient option normally set from gson.

Consider this small example:

class Base
{
   String type;
   double malformedField;
}

class A extends Base{}
class B extends Base{}

We use the type field in order to determine which subclass we want, because of course otherwise we couldn't infer the correct type.

This is the setup we use to create our subclasses and deserialize:

RuntimeTypeAdapterFactory<Base> adapterFactory = RuntimeTypeAdapterFactory.of(Base.class, "type", true)
    .registerSubtype(A.class, "A")
    .registerSubtype(B.class, "B");

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(adapterFactory)
    //.setLenient() // Has no effect at all
    .create();

String json = "{\"type\":\"A\", \"malformedField\": \"NaN\"}";
Base a = gson.fromJson(json, Base.class); // throws said Exception

Implementation in RuntimeTypeAdapterFactory:

Upon checking the code from the RuntimeTypeAdapterFactory I noticed that the lenient option is never set to true, because we invoke the fromJsonTree method of com.google.gson.TypeAdapter, in which we create a new JsonTreeReader without a possiblity to change the lenient option.

public final T fromJsonTree(JsonElement jsonTree) {
    try {
        JsonReader jsonReader = new JsonTreeReader(jsonTree);
        return read(jsonReader);
    } catch (IOException e) {
        throw new JsonIOException(e);
    }
}

I tried to manually set this through debugging to true and that seemed to work as expected (no NumberFormatException and object gets deserialized correctly with specified type).

Now the question is: Is there something I am missing about why we can't use the lenient option here? At a first glance this seems to be a bug from gson-extras but I am unsure.

Dennis K
  • 113
  • 7

1 Answers1

1

It is not quite a bug from gson-extras since com.google.gson.TypeAdapter class comes from core library. I would suggest to register one more TypeAdapterFactory and force lenient for all JsonReader instances:

class AlwaysLenientTypeAdapterFactory implements TypeAdapterFactory {

    public final <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
        return createCustomTypeAdapter(delegate);
    }

    private <T> TypeAdapter<T> createCustomTypeAdapter(TypeAdapter<T> delegate) {
        return new TypeAdapter<T>() {
            @Override
            public void write(JsonWriter out, T value) throws IOException {
                delegate.write(out, value);
            }

            @Override
            public T read(JsonReader in) throws IOException {
                in.setLenient(true);
                return delegate.read(in);
            }
        };
    }
}

Usage:

Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(adapterFactory)
        .registerTypeAdapterFactory(new AlwaysLenientTypeAdapterFactory())
        .create();

See also:

Michał Ziober
  • 37,175
  • 18
  • 99
  • 146