Skip to content
Advertisement

Gson Failing to call custom Serializer

I’ve been trying to follow the advice given here to turn off scientific notation on numeric values represented in Json. The problem I’ve got is that my custom Serializer is never called.

I’ve tried different variations of the code and have eventually ended up with:

public class TestExternaliser {
    static class SpecialSerializer implements JsonSerializer<Object> {
        @Override
        public JsonElement serialize(Object x,
                                     Type type,
                                     JsonSerializationContext jsonSerializationContext) {
            return new JsonPrimitive("xxx");
        }
    }

    public static void main(String... args) {
        JsonObject root = new JsonObject();

        root.addProperty("String", "String");
        root.addProperty("Num", Integer.valueOf(123));
        root.addProperty("Bool", Boolean.TRUE);

        Gson gson = new GsonBuilder()
                .registerTypeHierarchyAdapter(Object.class, new SpecialSerializer())
                .setPrettyPrinting()
                .create();

        System.out.println(gson.toJson(root));
    }
}

If I’ve understood the API correctly then this code use the custom serialisation for all values so it should generate "xxx" for all values, but what I keep getting is:

{
  "String": "String",
  "Num": 123,
  "Bool": true
}

What’s going wrong?

Advertisement

Answer

What’s going wrong?

Nothing wrong because of the limitations Gson has by design: Object and JsonElement type adapter hierarchies cannot be overridden.

Here is the test covering all four object/number hierarchy and value/JSON tree pairs:

public final class LimitationsTest {

    private static final JsonSerializer<Object> defaultJsonSerializer = (src, typeOfSrc, context) -> new JsonPrimitive("xxx");

    private static final Gson objectDefaultsGson = new GsonBuilder()
            .registerTypeHierarchyAdapter(Object.class, defaultJsonSerializer)
            .create();

    private static final Gson numberDefaultsGson = new GsonBuilder()
            .registerTypeHierarchyAdapter(Number.class, defaultJsonSerializer)
            .create();

    private static final class Value {
        @SerializedName("String")
        private String string;
        @SerializedName("Num")
        private Number num;
        @SerializedName("Bool")
        private Boolean bool;
    }

    private static final Object object;
    private static final JsonElement jsonElement;

    static {
        final Value newObject = new Value();
        newObject.string = "String";
        newObject.num = 123;
        newObject.bool = Boolean.TRUE;
        object = newObject;
        final JsonObject newJsonElement = new JsonObject();
        newJsonElement.addProperty("String", "String");
        newJsonElement.addProperty("Num", 123);
        newJsonElement.addProperty("Bool", Boolean.TRUE);
        jsonElement = newJsonElement;
    }

    @Test
    public void testObjectObject() {
        Assertions.assertEquals(""xxx"", objectDefaultsGson.toJson(object));
    }

    @Test
    public void testObjectJsonElement() {
        Assertions.assertEquals("{"String":"String","Num":123,"Bool":true}", objectDefaultsGson.toJson(jsonElement));
    }

    @Test
    public void testNumberObject() {
        Assertions.assertEquals("{"String":"String","Num":"xxx","Bool":true}", numberDefaultsGson.toJson(object));
    }

    @Test
    public void testNumberJsonElement() {
        Assertions.assertEquals("{"String":"String","Num":123,"Bool":true}", numberDefaultsGson.toJson(jsonElement));
    }

}

In short JsonElements are considered already-serialized, so what you’re looking for is hidden in testNumberObject: define Number as a superclass (or Float/Double to be most precise), and serialize an object containing fields, not JsonElement. If you must use JsonElement, then put a “good-formattible” value right into the Num property (BigDecimal should work just fine).


Update 1.

@Test
public void testNoScientificNotationForJsonElement() {
    final JsonObject newJsonElement = new JsonObject();
    newJsonElement.addProperty("a", new BigDecimal(new BigDecimal("1E+10").toPlainString()));
    newJsonElement.addProperty("b", new BigDecimal("1E+10") {
        @Override
        public String toString() {
            return toPlainString();
        }
    });
    final Gson gson = new Gson();
    Assertions.assertEquals("{"a":10000000000,"b":10000000000}", gson.toJson(newJsonElement));
}
Advertisement