【问题标题】:How to prevent Gson from expressing integers as floats如何防止 Gson 将整数表示为浮点数
【发布时间】: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}
]

【问题讨论】:

标签: java android gson


【解决方案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");
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2014-06-14
          • 2016-07-30
          • 2020-01-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-29
          相关资源
          最近更新 更多