Merchuk Hul
Merchuk Hul

Reputation: 275

Make GSON deserialize numbers to integers or doubles

I am having a hard time with GSON.

I have a simple JSON that I want to deserialize to a Map<String,Object>.

It's really intuitive to me that 123 should be parsed as an int (or long), 123.4 as a float( or double).

GSON on the other hand creates Doubles all the time.

Can I tell GSON to not abuse double all the time?

My actual code:

Type mapType = new TypeToken<Map<String, Object>>() {}.getType();
GSON gson = new Gson();
Map<String, Object> map = gson.fromJson(someString, mapType);

Upvotes: 12

Views: 18407

Answers (5)

Steven Kerdudou
Steven Kerdudou

Reputation: 33

Since gson 2.8.9, you can now provide number conversion policy.

new GsonBuilder()
   .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)
   .create()

Upvotes: 3

xleon90
xleon90

Reputation: 1316

If you are not bound to specifically use gson library you can solve this deserializing using jackson one as follow:

new ObjectMapper().readValue(yourJson, new TypeReference<Map<String,Object>>() {});

Here a complete junit test that expose the differences between the two libraries deserializing an integer value:

@Test
public void integerDeserializationTest() throws Exception {

    final String jsonSource = "{\"intValue\":1,\"doubleValue\":2.0,\"stringValue\":\"value\"}";

    //Using gson library "intValue" is deserialized to 1.0
    final String gsonWrongResult = "{\"intValue\":1.0,\"doubleValue\":2.0,\"stringValue\":\"value\"}";
    Map<String,Object> gsonMap = new Gson().fromJson(jsonSource, new TypeToken<Map<String, Object>>() {
    }.getType());
    assertThat(new Gson().toJson(gsonMap),is(gsonWrongResult));


    //Using jackson library "intValue" is deserialized to 1
    Map<String,Object> jacksonMap = new ObjectMapper().readValue(jsonSource, new TypeReference<Map<String,Object>>() {});
    assertThat(new ObjectMapper().writeValueAsString(jacksonMap),is(jsonSource));

}

Upvotes: 3

g00dnatur3
g00dnatur3

Reputation: 1193

The following code compiles & works:

package test;

import java.lang.reflect.Type;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;

public class Main {

    public static void main(String[] args) {
        GsonBuilder builder = new GsonBuilder();
        builder.registerTypeAdapter(Object.class, new MyObjectDeserializer());
        Gson gson = builder.create();
        String array = "[1, 2.5, 4, 5.66]";

        Type objectListType = new TypeToken<ArrayList<Object>>() {}.getType();
        List<Object> obj = gson.fromJson(array, objectListType);

        System.out.println(Arrays.toString(obj.toArray()));
    }

    public static class MyObjectDeserializer implements JsonDeserializer<Object> {

        public Object deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) 
            throws JsonParseException {

          Number num = null;
          try {
              num = NumberFormat.getInstance().parse(json.getAsJsonPrimitive().getAsString());
          } catch (Exception e) {
              //ignore
          }
          if (num == null) {
              return context.deserialize(json, typeOfT);
          } else {
              return num;
          }
      }
    }

}

My solution will first try to parse the string as a number, if that fails it will let the standard Gson deserializer do the work.

If you need a number parser that is not locale specific use this method to parse a number:

private static Number parse(String str) {
    Number number = null;
    try {
        number = Float.parseFloat(str);
    } catch(NumberFormatException e) {
    try {
        number = Double.parseDouble(str);
    } catch(NumberFormatException e1) {
        try {
            number = Integer.parseInt(str);
        } catch(NumberFormatException e2) {
            try {
                number = Long.parseLong(str);
            } catch(NumberFormatException e3) {
                throw e3;
            }       
        }       
    }       
}
    return number;
}

Upvotes: 6

Praveen Kumar
Praveen Kumar

Reputation: 180

You can do the following changes in order to parse that data:

   testData1={"GOAL1":123, "GOAL2":123.45,"GOAL5":1256,"GOAL6":345.98}

and below is your code to actually parse it.

Type mapType = new TypeToken<Map<String, Object>>() {
}.getType();
String str = prop.getProperty("testData1");
System.out.println(str);
Gson gson = new Gson();
Map<String, Object> map = gson.fromJson(str, mapType);
for (String key : map.keySet()) {
    String a = null;
    try {

        if (map.get(key) instanceof Double) {
            a = "" + map.get(key);

        } else {
            if (map.get(key) instanceof String) {
                a = (String) map.get(key);

            } else {
                a = null;
            }
        }

        if (a != null && a.contains(".") && !a.endsWith(".0")) {

            // Convert it into Double/Float
        } else {
            // Work as Integer
        }

    } catch (Exception ex) {
        ex.printStackTrace();
    }

}

Upvotes: 0

mayr
mayr

Reputation: 471

It's not a good aproach to mix types like this (integers with doubles). Since you are using Object as a type, you won't be able to get both Integers and Doubles from the map. Gson decides which type is more apropriate for Object. In your case it is Double, because all values CAN BE doubles, but all values CAN'T BE integers.

If you really need to mix types, try to use Number class instead of Object. Example:

public static void main(String[] args){
        String array = "[1, 2.5, 4, 5.66]";
        Gson gson = new Gson();

        Type type = new TypeToken<ArrayList<Number>>() {}.getType();
        List<Number> obj = gson.fromJson(array, type);

        System.out.println(Arrays.toString(obj.toArray()));
    }

Output: [1, 2.5, 4, 5.66]

While this:

public static void main(String[] args){
    String array = "[1, 2.5, 4, 5.66]";
    Gson gson = new Gson();

    Type type = new TypeToken<ArrayList<Object>>() {}.getType();
    List<Object> obj = gson.fromJson(array, type);

    System.out.println(Arrays.toString(obj.toArray()));
}

will give you output: [1.0, 2.5, 4.0, 5.66]

Upvotes: 1

Related Questions