0

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?

Stormcloud
  • 2,065
  • 2
  • 21
  • 41

2 Answers2

0

In playwright, a microsoft library, and rust-rcon library, something similar happened to me. I leave you link.

This error occurs because you have installed jdk 11 or upper and a gson prior to 2.8.6

https://github.com/microsoft/playwright-java/issues/245#issuecomment-775351308 https://github.com/MrGraversen/rust-rcon/pull/2#event-4300625968

The solution was to go to the latest version of gson, although the version was the one they used, I added it to my POM to force maven to make the rest of the dependencies use the latest version. Try to see and tell me!

<dependency>
   <groupId>com.google.code.gson</groupId>
   <artifactId>gson</artifactId>
   <version>2.8.6</version>
</dependency>

Try this solution :D

Katakurinna
  • 103
  • 1
  • 1
  • 7
  • Thanks for the suggestion. I'm already using 2.8.6, but I've just tried a few older version in case it was a regression. They all fail to call SpecialSerializer – Stormcloud Feb 21 '21 at 20:57
  • Perhaps the way you do it is not correct, I always use jackson, and I highly recommend it, it is very simple and I think it is much better than gson. You can take a look here: https://github.com/Katakurinna/rustrcon/blob/9ab7b190e81c446de4526d6bc1105bd0f441feed/src/main/java/me/cerratolabs/rustrcon/client/RustClient.java#L49 – Katakurinna Feb 21 '21 at 21:16
0

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));
}
  • Can I just check test `testNumberJsonElement` with you; it exactly mathes my use case. You are using a serialiser that is configured for Number.class, and you have stored the number 123 in your `jsonElement`. What I see in your assert is that it's not being transformed. Is that what we expect? If so what's the workaround? – Stormcloud Feb 22 '21 at 18:24
  • @Stormcloud _Is that what we expect?_ Yes, absolutely. `JsonElement` and its subclasses are not meant to be transformed while serialization. Moreover, `JsonElement` is the result class for `toJsonTree` that _serializes_ into a JSON (not a string, but a tree). `JsonSerializer`/`JsonDeserializers` are meant to be used with classes other than `JsonElement`. – terrorrussia-keeps-killing Feb 22 '21 at 18:55
  • @Stormcloud I wouldn't say a word workaround here, it's misleading in this case. You have two choices: either using a mapping (see the `Value` class above) seriliazers and deserializers apply to (`testNumberObject`), or using `JsonElement`-based trees and put correct values to it yourself since trees are already "serialized". – terrorrussia-keeps-killing Feb 22 '21 at 18:57
  • I don't have a DTO class to serialise as my data is fed from an external source and doesn't have a predefined struture, so I'm going to have to stick with the JsonObject. It's certainly possible for me to use BigDecimal as my numeric type, I just need to find a way of representing it with out exponents – Stormcloud Feb 23 '21 at 18:25
  • @Stormcloud Please the update in the answer. – terrorrussia-keeps-killing Feb 23 '21 at 19:21