'How to prevent Gson from converting a long number (a json string ) to scientific notation format?

I need to convert json string to java object and display it as a long. The json string is a fixed array of long numbers:

{numbers
[ 268627104, 485677888, 506884800 ] }

The code to convert works fine in all cases except for numbers ending in 0. It converts those to a scientific notation number format:

   public static Object fromJson(HttpResponse response, Class<?> classOf)
    throws IOException {
    InputStream instream = response.getResponseInputStream();                   

    Object obj = null;
    try {
        Reader reader = new InputStreamReader(instream, HTTP.UTF_8);

        Gson gson = new Gson();

        obj = gson.fromJson(reader, classOf); 

        Logger.d(TAG, "json --> "+gson.toJson(obj));
    } catch (UnsupportedEncodingException e) {
        Logger.e(TAG, "unsupported encoding", e);
    } catch (Exception e) {
        Logger.e(TAG, "json parsing error", e);
    }

    return obj;
}

The actual result: Java object : 268627104, 485677888, 5.068848E+8

Notice the last number is converted to a scientific notation format. Can anyone suggest what could be done to work around it or prevent it or undo it? I'm using Gson v1.7.1



Solution 1:[1]

If serializing to a String is an option for you, you can configure GSON to do so with:

GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setLongSerializationPolicy( LongSerializationPolicy.STRING );
Gson gson = gsonBuilder.create();

This will produce something like:

{numbers : [ "268627104", "485677888", "506884800" ] }

Solution 2:[2]

Another work around is to use the JsonParser class instead. This will return the Gson object representations (JsonElement) rather than a user defined class, but avoids the problem of conversion to scientific notation.

import java.lang.reflect.Type;
import java.util.Map;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;

public class GsonTest
{
    public static void main(String[] args)
    {
        String json = "{numbers:[268627104,485677888,506884800]}";

        Gson gson = new Gson();
        Type type = new TypeToken<Map<String, Object>>(){}.getType();
        Map<String, Object> jsonMap = gson.fromJson(json, type);
        System.out.println("Gson output:");
        System.out.println(jsonMap);

        JsonParser jsonParser = new JsonParser();
        JsonElement jsonElement = jsonParser.parse(json);
        System.out.println("JsonParser output:");
        System.out.println(jsonElement);
    }
}

Code Output:

Gson output:  
{numbers=[2.68627104E8, 4.85677888E8, 5.068848E8]}  
JsonParser output:  
{"numbers":[268627104,485677888,506884800]}

Solution 3:[3]

I had a similar problem, and it not only converts integers to double, but it actually loses precision for certain long numbers, as described in this related question.

I tracked down this conversion to ObjectTypeAdapter's read method, specifically:

case NUMBER:
  return in.nextDouble();

It may be possible to plug in a modified TypeAdapter for Object, but I couldn't get that to work, so instead I just copied the read method (Object read(JsonReader in)) to my own code and modified the above lines to this:

case NUMBER:
    final String s = in.nextString();
    try {
        return Integer.parseInt(s);
    } catch (NumberFormatException e) {
        // ignore
    }
    try {
        return Long.parseLong(s);
    } catch (NumberFormatException e) {
        // ignore
    }
    return Double.parseDouble(s);

I wish Gson did this by default..

Then I put the other connecting pieces in a helper method that looks something like this:

public static Object parse(final Reader r) {
    try (final JsonReader jr = new JsonReader(r)) {
        jr.setLenient(true);
        boolean empty = true;
        Object o = null;
        try {
            jr.peek();
            empty = false;
            o = read(jr);
        } catch (EOFException e) {
            if (!empty) {
                throw new JsonSyntaxException(e);
            }
        }
        if (o != null && jr.peek() != JsonToken.END_DOCUMENT) {
            throw new JsonIOException("JSON document was not fully consumed.");
        }
        return o;
    } catch (IOException e) {
        throw new JsonIOException(e);
    }
}

So now instead of new Gson().fromJson(r, Object.class), I call parse(r).

This works well for me because I want to be able to parse json data with any structure, but if you have a particular class you're targeting, you probably just need to eliminate occurrences of Object within that class's members.

Solution 4:[4]

Got the same issue, after some investigation here is what I found.

The behavior:

  • Gson
    For a number without fractional part, Gson would convert it as Double,
  • Jackson
    For a number without fractional part, Jackson would convert it as Integer or Long, depends on how large the number is.

Possible solutions:

  • Convert Gson's return value from Double to Long, explicitly.
  • Use Jackson instead.
    I prefer this.

Code - test for Jackson

ParseNumberTest.java:

import java.util.List;

import org.testng.Assert;
import org.testng.annotations.Test;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * test - jackson parse numbers,
 * 
 * @author eric
 * @date Jan 13, 2018 12:28:36 AM
 */
public class ParseNumberTest {
    @Test
    public void test() throws Exception {
    String jsonFn = "numbers.json";

    ObjectMapper mapper = new ObjectMapper();

    DummyData dd = mapper.readValue(this.getClass().getResourceAsStream(jsonFn), DummyData.class);
    for (Object data : dd.dataList) {
        System.out.printf("data type: %s, value: %s\n", data.getClass().getName(), data.toString());
        Assert.assertTrue(data.getClass() == Double.class || data.getClass() == Long.class || data.getClass() == Integer.class);

        System.out.printf("%s\n\n", "------------");
    }
    }

    static class DummyData {
    List<Object> dataList;

    public List<Object> getDataList() {
        return dataList;
    }

    public void setDataList(List<Object> dataList) {
        this.dataList = dataList;
    }
    }
}

numbers.json:

{
    "dataList": [
        150000000000,
        150778742934,
        150000,
        150000.0
    ]
}

How to run:

  • The test case is based on Jackson & TestNG.
  • Put numbers.json at the same package as ParseNumberTest.java.
  • Run as testng test, then it would print type & value of the parse result.

Output:

data type: java.lang.Long, value: 150000000000
------------

data type: java.lang.Long, value: 150778742934
------------

data type: java.lang.Integer, value: 150000
------------

data type: java.lang.Double, value: 150000.0
------------

PASSED: test

Solution 5:[5]

Not smart, but still working method is to add " at the start and at the end of the number. Then after processing is finished, delete it.

Solution 6:[6]

We can use the below code solution for number Long:

Document doc = documentCursor.next();  

JsonWriterSettings relaxed = JsonWriterSettings.builder().outputMode(JsonMode.RELAXED).build();  

CustomeObject obj = gson.fromJson(doc.toJson(relaxed), CustomeObject.class);

Solution 7:[7]

The best solution in case you need them as String was to force attributes to be quoted with a single quote before doing the conversion.

Do changes like this:

String readerAsString = convertReaderToString(reader);
readerAsString = readerAsString.toString().replace("=", "='");
readerAsString = readerAsString.toString().replace(",", "',");
readerAsString = readerAsString.toString().replace("}]", "'}]");

Solution 8:[8]

data class Answer(
    val question: String,
    val value: Any
)

Given the value:Any property above, I find it dubious that Gson encounters a JSON value of 1 (not "1") and cannot INFER the blatantly obvious truth: 1 is an Int. Instead, Gson converts the integer value to the double 1.0.

Gson should only convert a JSON value from 1 to 1.0 if the value property above was of type Float or Double. When Gson encounters a property whose type is Any, it should (quite simply) infer the type from the JSON value it receives. Unfortunately, it doesn't, preferring to actually corrupt incoming integer values by casting them to Double, which unsurprisingly immediately causes exceptions.

I can find no reasonable solution to this peculiarity of the Gson parser. As such, I'm forced to either manually convert all those double values back into int values after using Gson or implement my own generic custom type adapter for Gson. Neither of these options is at all appealing.

Solution 9:[9]

I did not find a solution to my problem of gson formatting numbers ending in 0 to scientific notation. I instead used a work-around to convert this scientific notation into a double that I formatted with commas. "value" is the json string.

  private String formatNumber(String value) { 
    double dValue = Double.parseDouble(value);
    String pattern = "#,###";
    DecimalFormat formatter = new DecimalFormat(pattern);
    String newNumber = formatter.format(dValue);

            return newNumber;
}

This doesn't answer the question asked but is an added step to work-around the problem to display the numbers as required by the system.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 melbyts
Solution 3 Community
Solution 4
Solution 5
Solution 6 Dom
Solution 7 GooDeeJAY
Solution 8
Solution 9 lini sax