How to prevent Gson from expressing integers as floats

JavaAndroidGson

Java Problem Overview


Gson has some odd behavior when I try to convert a string to json. The code below transforms string draft into json responses. Is there a way to prevent gson from adding the '.0 to all integer values?

ArrayList<Hashtable<String, Object>> responses;
Type ResponseList = new TypeToken<ArrayList<Hashtable<String, Object>>>() {}.getType();
responses = new Gson().fromJson(draft, ResponseList);

draft:
[ {"id":4077395,"field_id":242566,"body":""},
  {"id":4077398,"field_id":242569,"body":[[273019,0],[273020,1],[273021,0]]},
  {"id":4077399,"field_id":242570,"body":[[273022,0],[273023,1],[273024,0]]}
]

responses:
[ {id=4077395.0, body=, field_id=242566.0},
  {id=4077398.0, body=[[273019.0, 0.0], [273020.0, 1.0], [273021.0, 0.0]], field_id=242569.0},
  {id=4077399.0, body=[[273022.0, 0.0], [273023.0, 1.0], [273024.0, 0.0]], field_id=242570.0}
]

Java Solutions


Solution 1 - Java

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.

Solution 2 - Java

This works:

 Gson gson = new GsonBuilder().
        registerTypeAdapter(Double.class,  new JsonSerializer<Double>() {	

	@Override
	public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) {
		if(src == src.longValue())
			return new JsonPrimitive(src.longValue());			
        return new JsonPrimitive(src);
	}
 }).create();
	

Solution 3 - Java

There is a solution provided by the library from 2.8.9 version.

We can set how Object is converted to a number using, setObjectToNumberStrategy

Implementation of LONG_OR_DOUBLE will work in this case. Can be used as

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

Refer to below link for details

https://github.com/google/gson/pull/1290

Solution 4 - Java

Basically, there is no perfect answer for this issue. All "solutions" work for some cases only. This is an issue reported to gson team, unfortunately seems they insist that "javascript has no integer type" as if they do not realize that gson is for java not javascript. So they refused to fix it until today (2018 now), despite other lib like jackson does not have such issue at all, despite how easy to fix it. So you may have to fix the issue yourself from gson source code and build your own gson.jar. The source file is gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java

case NUMBER:
   return in.nextDouble();

Solution 5 - Java

I'm late to the party, but I just ran into this myself. In my case, I didn't want to specify an Integer type in my ArrayList - since it could be a String or an Integer.

My solution is as follows:

GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Double.class,  new JsonSerializer<Double>() {

	public JsonElement serialize(Double src, Type typeOfSrc,
				JsonSerializationContext context) {
            Integer value = (int)Math.round(src);
            return new JsonPrimitive(value);
		}
    });

Gson gs = gsonBuilder.create();

Rather than using the default Gson definition with Gson gs = new Gson();, I have overridden the Double.class serialization to return an integer.

In my case, I have Strings and Integers within my JSON, but I do not have any doubles, so this doesn't pose a problem.

If you need a double or a float value, I suppose it would be possible to add some logic that tested the value for attributes specific to each datatype and returned an appropriate value. Something like

if(/*source has a decimal point*/){
  return new JsonPrimitive(src); 
} else if (/* source has some attribute of a Float */){
  Float value = /*convert the src value from double to a Float */;
  return new JsonPrimitive(value);
} else {
  //it looks like an integer
  Integer value = (int)Math.round(src);
  return new JsonPrimitive(value);
}

I don't know how to test for or convert those datatypes off the top of my head, but this should put you on the right path.

Solution 6 - Java

Custom serializer solution in Kotlin, it's a bit tricky because you have to distinguish between java.lang.Double and Double (kotlin.Double).

private val Gson: Gson = GsonBuilder().registerTypeAdapter(java.lang.Double::class.java, object : JsonSerializer<Double> {
    override fun serialize(src: Double, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
        return if (src == src.toLong().toDouble()) JsonPrimitive(src.toLong()) else JsonPrimitive(src)
    }
}).create()

Solution 7 - Java

This work for me.

Step 1: Copy the ObjectTypeAdapter in gson into the project, keeping the path the same as in gson Like this

com
  - xxx
    - xxx
com
  - google
    - gson
      - internal
        - bind
          ObjectTypeAdapter

Step 2: Modify ObjectTypeAdapter

case NUMBER:
  return in.nextDouble();

Modified to

case NUMBER:
  String number = in.nextString();
  try {
    return Long.valueOf(number);
  } catch (NumberFormatException e) {
    return Double.valueOf(number);
  }

OK. Gson will prioritizes the ObjectTypeAdapter in the project.

Solution 8 - Java

Use Jackson

    public static Map<String, Object> jsonToMap(final String jsonString) {
    try {
        final ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.convertValue(objectMapper.readTree(jsonString), new TypeReference<Map<String, Object>>() {
        });
    } catch (final Exception e) {
        throw new InternalServiceException("lol");
    }
}

Solution 9 - Java

    fun jsonToMap(json: JSONObject): Map<String, Any> {
        val doubles = Gson().fromJson<Map<String, Any>>(json.toString(), Map::class.java)
        fun doublesToLong(doubles: Map<String, Any>): Map<String, Any> = doubles
                .map { entry ->
                    Pair(entry.key, entry.value.let {
                        when (it) {
                            is Map<*, *> -> doublesToLong(it as Map<String, Any>)
                            is Double -> it.toLong()
                            else -> it
                        }
                    })
                }
                .toMap()
        return doublesToLong(doubles)
    }

Solution 10 - Java

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMark MurphyView Question on Stackoverflow
Solution 1 - Javadimo414View Answer on Stackoverflow
Solution 2 - JavaMartin WickmanView Answer on Stackoverflow
Solution 3 - JavaSeekerView Answer on Stackoverflow
Solution 4 - JavaLeonView Answer on Stackoverflow
Solution 5 - JavaSteve KallestadView Answer on Stackoverflow
Solution 6 - JavaTrevorView Answer on Stackoverflow
Solution 7 - JavaXiangYunView Answer on Stackoverflow
Solution 8 - Javaswarnim guptaView Answer on Stackoverflow
Solution 9 - JavakrekerView Answer on Stackoverflow
Solution 10 - JavaZarif ErgashevView Answer on Stackoverflow