如何防止 Gson 将整数表示为浮点数

Posted

技术标签:

【中文标题】如何防止 Gson 将整数表示为浮点数【英文标题】:How to prevent Gson from expressing integers as floats 【发布时间】:2013-03-08 15:06:33 【问题描述】:

当我尝试将字符串转换为 json 时,Gson 有一些奇怪的行为。下面的代码将字符串草稿转换为 json 响应。有没有办法阻止 gson 将 '.0 添加到所有整数值?

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
]

【问题讨论】:

double to int的类似问题:***.com/questions/12067206/… @PatrickKafka 但这里的答案是金子。 或者,尝试转换为 Jackson 另见this answer 我写了一个类似的问题;问题是您必须将数据解析为Object,然后转换为您需要的任何内容。 使用github.com/FasterXML/jackson-databind***.com/a/583634/592217 【参考方案1】:

您告诉 Gson 它正在寻找字符串到对象的映射列表,这实质上是为了对对象的类型做出最佳猜测。由于JSON doesn't distinguish between integer and floating point fields Gson 对于数字字段必须默认为 Float/Double。

Gson 从根本上构建是为了检查您要填充的对象的类型,以确定如何解析数据。如果你不给它任何提示,它就不会很好地工作。一种选择是定义一个自定义的 JsonDeserializer,但最好不要使用 HashMap(并且绝对不要使用 Hashtable!),而是向 Gson 提供有关它所期望的数据类型的更多信息。

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

同样,Gson 的重点是将结构化数据无缝转换为结构化对象。如果您要求它创建一个几乎未定义的结构,例如对象映射列表,那么您就完全违背了 Gson 的观点,还不如使用一些更简单的 JSON 解析器。

【讨论】:

为了完整起见,浮点数并不比整数更通用,因为它不能准确地表示与整数相同的所有值。但是 Double 可以。 我的意思是泛型的类型——浮点可以表示非整数值。当然在实践中 float/double 不能代表比 int/long 更多的值,但浮点的限制在这里并不是真正的问题。 糟糕的建议,对不起,但如果值从 int 变为 string 到其他东西但加倍,你就完蛋了...... @Enerccio 你所说的“如果值从 int 变为 string 到其他东西但 double”是什么意思?值的类型何时会改变?如果您的文档架构发生变化,您需要更新您的 Java 类定义。 @dimo414 也许它可以存储不同类型的不同值,无论如何我通过使用TaggedValue 对它进行排序并使用值存储类型【参考方案2】:

这行得通:

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

【讨论】:

嗨,我找到了这个答案,并使用了你在这篇文章中提到的方式,但是,当它应该是 int 时,我得到了两倍:-( @armnotstrong 这对哪个号码不起作用?上面的代码应该适用于所有 32 位 int 值,因为它们都有对应于 Java double 类型(具有 64 位)的精确值。 (整数) double 和 int 值之间的转换以及返回在 int 范围内是精确的。但是,进入 64 位长范围时,超过 2 的 52 次方 (4,503,599,627,370,496) 的正值或负值无法在所有情况下都正确转换。【参考方案3】:

基本上,这个问题没有完美的答案。所有“解决方案”仅适用于某些情况。这是向 gson 团队报告的问题,不幸的是,他们似乎坚持认为“javascript 没有整数类型”,好像他们没有意识到 gson 是用于 java 而不是 javascript。所以他们直到今天(现在是 2018 年)都拒绝修复它,尽管像杰克逊这样的其他库根本没有这样的问题,尽管修复它很容易。因此,您可能必须自己从 gson 源代码解决问题并构建自己的 gson.jar。源文件为gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java

case NUMBER:
   return in.nextDouble();

【讨论】:

【参考方案4】:

我参加聚会迟到了,但我自己也遇到了。就我而言,我不想在 ArrayList 中指定 Integer 类型 - 因为它可能是 String 或 Integer。

我的解决方法如下:

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

我没有使用Gson gs = new Gson(); 的默认 Gson 定义,而是重写了 Double.class 序列化以返回一个整数。

在我的情况下,我的 JSON 中有字符串和整数,但我没有任何双精度数,所以这不会造成问题。

如果您需要双精度或浮点值,我想可以添加一些逻辑来测试每种数据类型特定属性的值并返回适当的值。类似的东西

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

我不知道如何测试或转换这些数据类型,但这应该会让你走上正确的道路。

【讨论】:

听到“它可能是字符串或整数”在我看来是一个很大的危险信号。听起来您的 JSON 数据结构不正确 - 您正在创建一个包含整数和字符串的列表?从技术上讲,JSON 规范允许这样做,但它会给每个试图与之交互的反序列化器带来痛苦。而是考虑 a) 保留整个列表 Strings,如果某些是数字只是巧合,b) 将数字拆分到自己的列表中,或者 c) 将列表类型更改为更复杂的对象,以更好地反映数据。 @dimo414 好吧,这三种解决方案都有其缺陷:a) 没用,因为您不知道哪个是数字,哪个是字符串; b)丢失排序信息(然后需要单独的索引列表和开销); c) 复杂对象会使结果 json 膨胀 @Enerccio 复杂数据需要复杂的表示,因此 c) 通常是可以接受的折衷方案。你的观点很好,但实际上我会满足于混合数据类型一般会比它的价值更麻烦,通常重新检查你的要求会发现一个可行的替代结构为您的目的而无需跳过这些箍。随意发布一个带有具体用例的问题,我很乐意参与。【参考方案5】:

从 2.8.9 版本开始有库提供的解决方案。

我们可以使用 setObjectToNumberStrategy 来设置如何将 Object 转换为数字

在这种情况下,LONG_OR_DOUBLE 的实现将起作用。可以用作

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

详情请参考以下链接

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

【讨论】:

这应该是公认的解决方案。谢谢。【参考方案6】:

这对我有用。

第 1 步: 将gson中的ObjectTypeAdapter复制到项目中,保持路径与gson中相同 像这样

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

第 2 步: 修改 ObjectTypeAdapter

case NUMBER:
  return in.nextDouble();

修改为

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

好的。 Gson 会优先考虑项目中的 ObjectTypeAdapter。

【讨论】:

这不是一个解决方案,而是一个危险的解决方法,它将使整个项目变成一个垃圾箱【参考方案7】:

Kotlin 中的自定义序列化解决方案,这有点棘手,因为您必须区分 java.lang.Double 和 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()

【讨论】:

【参考方案8】:
    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)
    

【讨论】:

【参考方案9】:

使用杰克逊

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

【讨论】:

以上是关于如何防止 Gson 将整数表示为浮点数的主要内容,如果未能解决你的问题,请参考以下文章

关于整数输出为浮点数的问题

JS判断是不是为数字,是不是为整数,是不是为浮点数

如何将表示为字符串的数字转换为浮点数

Android - 如何正确地将字符串解析为浮点数(价格数字格式:整数、小数或两者兼有)

如何在 x86(32 位)程序集中将无符号整数转换为浮点数?

Python:将 2 个整数转换为浮点数