How to Prevent Gson from Expressing Integers as Floats

How to prevent Gson from expressing integers as floats

You're telling Gson it's looking for a list of maps of Strings to Objects, which essentially says for it to make a best guess as to the type of the Object. Since JSON doesn't distinguish between integer and floating point fields Gson has to default to Float/Double for numeric fields.

Gson is fundamentally built to inspect the type of the object you want to populate in order to determine how to parse the data. If you don't give it any hint, it's not going to work very well. One option is to define a custom JsonDeserializer, however better would be to not use a HashMap (and definitely don't use Hashtable!) and instead give Gson more information about the type of data it's expecting.

class Response {
int id;
int field_id;
ArrayList<ArrayList<Integer>> body; // or whatever type is most apropriate
}

responses = new Gson()
.fromJson(draft, new TypeToken<ArrayList<Response>>(){}.getType());

Again, the whole point of Gson is to seamlessly convert structured data into structured objects. If you ask it to create a nearly undefined structure like a list of maps of objects, you're defeating the whole point of Gson, and might as well use some more simplistic JSON parser.

How can I prevent gson from converting integers to doubles

1) You have to create custom JsonDeserializer and not JsonSerializer like in your question.

2) I don't think this behavior comes from Double deserializer. it is more like json object/map problem

Here is from source code:

case NUMBER:
return in.nextDouble();

So you can try approach with custom deserializer for Map<String, Object> (or some more generic map if you want) :

public static class MapDeserializerDoubleAsIntFix implements JsonDeserializer<Map<String, Object>>{

@Override @SuppressWarnings("unchecked")
public Map<String, Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return (Map<String, Object>) read(json);
}

public Object read(JsonElement in) {

if(in.isJsonArray()){
List<Object> list = new ArrayList<Object>();
JsonArray arr = in.getAsJsonArray();
for (JsonElement anArr : arr) {
list.add(read(anArr));
}
return list;
}else if(in.isJsonObject()){
Map<String, Object> map = new LinkedTreeMap<String, Object>();
JsonObject obj = in.getAsJsonObject();
Set<Map.Entry<String, JsonElement>> entitySet = obj.entrySet();
for(Map.Entry<String, JsonElement> entry: entitySet){
map.put(entry.getKey(), read(entry.getValue()));
}
return map;
}else if( in.isJsonPrimitive()){
JsonPrimitive prim = in.getAsJsonPrimitive();
if(prim.isBoolean()){
return prim.getAsBoolean();
}else if(prim.isString()){
return prim.getAsString();
}else if(prim.isNumber()){

Number num = prim.getAsNumber();
// here you can handle double int/long values
// and return any type you want
// this solution will transform 3.0 float to long values
if(Math.ceil(num.doubleValue()) == num.longValue())
return num.longValue();
else{
return num.doubleValue();
}
}
}
return null;
}
}

To use it you will have to give proper TypeToken to registerTypeAdapter and gson.fromJson function:

String json="[{\"id\":1,\"quantity\":2,\"name\":\"apple\"}, {\"id\":3,\"quantity\":4,\"name\":\"orange\"}]";

GsonBuilder gsonBuilder = new GsonBuilder();

gsonBuilder.registerTypeAdapter(new TypeToken<Map <String, Object>>(){}.getType(), new MapDeserializerDoubleAsIntFix());

Gson gson = gsonBuilder.create();
List<Map<String, Object>> l = gson.fromJson(json, new TypeToken<List<Map<String, Object>>>(){}.getType() );

for(Map<String, Object> item : l)
System.out.println(item);

String serialized = gson.toJson(l);
System.out.println(serialized);

Result:

{id=1, quantity=2, name=apple}
{id=3, quantity=4, name=orange}
Serialized back to: [{"id":1,"quantity":2,"name":"apple"},{"id":3,"quantity":4,"name":"orange"}]

PS: It is just one more option you can try. Personally i feel like creating custom object for your json instead of List<Map<String, Integer>> is much cooler and easier to read way

Why does Gson parse an Integer as a Double?

Gson is a simple parser. It uses always Double as a default number type if you are parsing data to Object.

Check this question for more information: How to prevent Gson from expressing integers as floats

I suggest you to use Jackson Mapper. Jackson distinguish between type even if you are parsing to an Object:

  • "2" as Integer
  • "2.0" as Double

Here is an example:

Map<String, Object> hashMap = new HashMap<String, Object>();
hashMap.put("data", "{\"rowNum\":0,\"colNum\":2,\"text\":\"math\"}");
ObjectMapper mapper = new ObjectMapper();
TypeReference<HashMap<String, Object>> typeRef = new TypeReference<HashMap<String, Object>>() {};

HashMap<String, Object> o = mapper.readValue(hashMap.get("data").toString(), typeRef);

maven:

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>

Make Gson to parse integers and not as floats

I couldn't find a better way than to completely override the Gson function. It is quite a hack, but no other solution seem to fix the issue:

public class Gson {

private static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
public <T> TypeAdapter<T> create(com.google.gson.Gson gson, TypeToken<T> type) {
return type.getRawType() == Object.class ? (TypeAdapter<T>) new LongObjectTypeAdapter(gson) : null;
}
};

static {
try {
Field field = ObjectTypeAdapter.class.getDeclaredField("FACTORY");

Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

field.setAccessible(true);
field.set(null, FACTORY);

} catch (Exception e) {
throw new IllegalStateException(e);
}
}

private com.google.gson.Gson gson = new com.google.gson.Gson();

public <T> T fromJson(String payload, Class<T> c) {
return gson.fromJson(payload, c);
}

public String toJson(Object object) {
return gson.toJson(object);
}

public static class LongObjectTypeAdapter extends TypeAdapter<Object> {

private com.google.gson.Gson gson;

public LongObjectTypeAdapter(com.google.gson.Gson gson) {
this.gson = gson;
}

public Object read(JsonReader in) throws IOException {
JsonToken token = in.peek();

switch (token) {
case BEGIN_ARRAY:
ArrayList list = new ArrayList();
in.beginArray();

while(in.hasNext()) {
list.add(this.read(in));
}

in.endArray();
return list;
case BEGIN_OBJECT:
LinkedTreeMap map = new LinkedTreeMap();
in.beginObject();

while(in.hasNext()) {
map.put(in.nextName(), this.read(in));
}

in.endObject();
return map;
case STRING:
return in.nextString();
case NUMBER:
String value = in.nextString();
if (value.contains(".")) {
return Double.valueOf(value);
} else {
return Long.valueOf(value);
}

case BOOLEAN:
return Boolean.valueOf(in.nextBoolean());
case NULL:
in.nextNull();
return null;
default:
throw new IllegalStateException();
}
}

public void write(JsonWriter out, Object value) throws IOException {
if (value == null) {
out.nullValue();
} else {
TypeAdapter typeAdapter = gson.getAdapter(value.getClass());
if (typeAdapter instanceof LongObjectTypeAdapter) {
out.beginObject();
out.endObject();
} else {
typeAdapter.write(out, value);
}
}
}
}
}

Retrofit & Gson - prevent gson from converting Long to scientific notation

Just in case someone might run into the same problem,

I was using a Wrapper-Class for the response, of which the Data Type was specified was Object. For this, Gson was trying to convert it to Scientific notation no matter what.

I must actually use JsonElement from Gson

public class GenericResponse_ implements Serializable {

@SerializedName("status")
@Expose
public int status;

@SerializedName("error")
@Expose
public String error;

@SerializedName("data")
@Expose
public JsonElement data; //This was of type Object, changed to JsonElement
}

Deserializing arbitrary JSON in Java with Gson and respecting integers

As the post you link suggested, you should create custom class, so I did and it's working correctly:

public class Test {
public static void main(String[] args) {
final Gson gson = new GsonBuilder()
.registerTypeAdapter(MyClass.class, new DoubleToInt())
.create();
String stringValue = "{contact: {name: \"John\", \"age\": 5, children: [{\"name\": \"Mary\"}]}}";

MyClass newValue = gson.fromJson(stringValue, MyClass.class);
System.out.println(newValue.toString());
}

private static class DoubleToInt implements JsonDeserializer<MyClass> {

@Override
public MyClass deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

// do my custom stuff here

return new MyClass(json);
}
}
}

class MyClass {
private JsonElement element;

MyClass(JsonElement element) {
this.element = element;
}

@Override
public String toString() {
return element.toString();
}
}


Related Topics



Leave a reply



Submit