1

I've written the following Spock test to demonstrate a problem that occurs when I deserialize a JSON object containing a number

class GsonFactorySpec extends Specification {

    private Gson gson = GsonFactory.instance.gsonInstance

    private String JSON = '{"number": 2}'

    def 'deserialize a JSON document with an integer value to a Map'() {

        when:
        Map deserialized = gson.fromJson(JSON, Map)

        then:
        deserialized.number instanceof Integer
    }
}

The test fails because the number is deserialized to a Double with value 2.0 instead of the Integer 2.

In the factory class where I create the Gson instance, I tried registering an adapter for instances of Number

@Singleton
class GsonFactory {

    private final JsonSerializer<Number> numberSerializer = new JsonSerializer<Number>() {
        @Override
        JsonElement serialize(Number number, Type type, JsonSerializationContext context) {
            Integer intValue = number.toInteger()
            intValue == number ? new JsonPrimitive(intValue) : new JsonPrimitive(number)
        }
    }

    Gson getGsonInstance() {
        new GsonBuilder().serializeNulls().registerTypeHierarchyAdapter(
            Number, numberSerializer).create()
    }
} 

But it seems this adapter is not invoked during deserialization. Is it possible to force whole numbers to be deserialized as integers?

Update

A new feature added in version 2.8.5 of Gson looks like it might help to solve this issue

Dónal
  • 185,044
  • 174
  • 569
  • 824

1 Answers1

0

You're missing the correct type information: Map.class provides no type parameterization so Gson is using String keys and Object values (Object (de)serialization cannot be overridden though, so this is why Object-hierarchy strategy would not work). In Java, it would be something like:

private static final Type mapStringToNumberType = new TypeToken<Map<String, Number>>() {}.getType();

public static void main(final String... args) {
    @SuppressWarnings("unchecked")
    final Map<String, Number> map = gson.fromJson("{\"number\": 2}", mapStringToNumberType);
    System.out.println(map.get("number"));
}

You might also be interested in this stale PR that addresses a similar issue.