如何防止Gson将长数字(一个json字符串)转换为科学计数法格式?

31

我需要将JSON字符串转换为Java对象,并将其显示为长整型。JSON字符串是一个固定的长整型数组:

{numbers
[ 268627104, 485677888, 506884800 ] }

代码在所有情况下都可以很好地进行转换,除了以0结尾的数字。它会将这些数字转换为科学计数法格式:

   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;
}

实际结果为: Java对象: 268627104,485677888,5.068848E+8

注意最后一个数字转换为了科学计数法格式。有人能建议如何解决它或防止它或撤消它吗?我正在使用Gson v1.7.1。


1
这只是以0结尾的数字,还是第三个数字越过了某些溢出边界? - BlackVegetable
@BlackVegetable 我只看到这种情况发生在以0结尾的数字上。另一个例子是163341520 -> 1.6334152E+8。一个更大的数字(不以0结尾的10位数)可以被完美地处理。 - lini sax
如果它是一个JSON 字符串(用引号括起来),它不会被转换为科学计数法。你有没有检查过实际的Java对象类型?你怎么知道差异不仅仅在于double的值如何打印呢? - Hot Licks
9个回答

19

如果将序列化为字符串作为一种选项,您可以使用以下方法配置GSON:

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

这将会产生如下的结果:

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

4
这将转换为字符串而不是长整型。请注意引号。 - Dimuthu
2
这仅适用于Long类型。整数、小数和浮点数仍将显示为无双引号的形式。 - Aman Mohammed
所以显然这个选项指示gson检测长整数并将其转换为字符串。那么将长整数转换为长整数的策略在哪里?没有反对String或者Map或者Field,但它是一个长整数。它不是一个Map。它不是一个String,也不是一个Field。它是一个长整数,所以给我一个长整数。 - Rick O'Shea

11

另一个解决方案是使用JsonParser类。这将返回Gson对象表示(JsonElement)而不是用户定义的类,但避免了转换为科学计数法的问题。

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

代码输出:

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

JsonParser方法应该可以工作。由于我的json字符串非常复杂,我不得不采用格式化方法来提取每个数字,以便在显示目的中使用它们。 - lini sax
只是将输出转换为字符串会给你正确的格式,但如果你尝试序列化这个json元素的类,我得到的长整型值不正确。 - Dmitry Nelepov
Gson Javadoc for JsonElement 没有提到该类实现了可序列化接口,所以我可以看出这可能会成为一个问题。不过你总是可以将数据从Gson对象转移到你自己的对象中。 - melbyts

8

我曾遇到类似的问题,它不仅将整数转换为双精度浮点数,而且对于某些长数字实际上会失去精度,就像在这个相关问题中所描述的那样。

我追踪了这种转换到ObjectTypeAdapterread方法,具体来说:

case NUMBER:
  return in.nextDouble();

也许可以插入一个修改过的TypeAdapter来适配Object,但我无法使其工作,所以我只是将read方法(Object read(JsonReader in))复制到我的代码中,并将上述行修改为以下内容:

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);

我希望Gson默认可以做到这一点。

然后,我将其他连接部分放在一个辅助方法中,看起来大致如下:

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

现在,我不再使用new Gson().fromJson(r, Object.class),而是调用parse(r)。这对我很有帮助,因为我想解析任何结构的JSON数据,但如果你有一个特定的类需要解析,你可能只需要消除该类成员中Object的出现。

5

遇到了同样的问题,经过一番调查,我找到了以下内容。

问题表现:

  • Gson
    对于没有小数部分的数字,Gson 会将其转换为 Double 类型。
  • Jackson
    对于没有小数部分的数字,Jackson 会将其转换为 Integer 或者 Long 类型,具体取决于数字的大小。

可能的解决方案:

  • 显式地将 Gson 返回值从 Double 转换为 Long
  • 使用 Jackson
    我更喜欢这个方案。

代码 - 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
    ]
}

运行方法:

  • 该测试用例基于 JacksonTestNG
  • numbers.json 文件放在与 ParseNumberTest.java 相同的包中。
  • 作为 testng 测试运行,然后它会打印出解析结果的类型和值。

输出:

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

1
我们可以使用以下代码解决方案来处理长数字:
Document doc = documentCursor.next();  

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

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

谢谢!这解决了我在POJO中使用NumberLong对象而不是long属性的问题。 - Bruno Poultier

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

鉴于上述的 value:Any 属性,我认为 Gson 遇到 JSON 值为 1(而不是 "1")时,不能推断出显而易见的真相: 1 是一个 Int。相反,Gson 将整数值转换为双精度 1.0

当 Gson 遇到类型为 Any 的属性时,它应该(非常简单地)从接收到的 JSON 值中推断出类型,只有当上面的 value 属性是 FloatDouble 类型时,Gson 才应将 JSON 值从 1 转换为 1.0。不幸的是,它没有这样做,而是更喜欢通过将它们强制转换为 Double 来破坏传入的整数值,这很容易导致异常。

我找不到解决 Gson 解析器这种奇怪行为的合理方法。因此,我被迫在使用 Gson 后手动将所有这些双精度值转换回整数值,或者为 Gson 实现自己的通用自定义类型适配器。这两个选项都不太令人愉快。


0

不是很聪明,但还在工作的方法是在数字开头和结尾添加 "。然后在处理完成后将其删除。


0

如果您需要将它们作为字符串使用,最好的解决方案是在进行转换之前强制属性用单引号引用。

像这样进行更改:

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

-1

我没有找到解决gson格式化以0结尾数字为科学计数法的问题的方法。相反,我使用了一个变通方法将这个科学计数法转换成一个带有逗号格式的double类型。"value"是json字符串。

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

            return newNumber;
}

这并没有回答所提出的问题,但是它是一个额外的步骤来解决问题,以按系统要求显示数字。


2
这是不安全的。有效数字精度为52位。这样会丢失信息。当您将long用作精度很重要的ID值时,这就成为了一个问题。 - Tazzy531
为了保持精度,可以使用Java.math中的BigDecimal。此外,Gson可以内部处理科学和常规符号。 - d3day

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接