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 JsonElement
s 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)); }