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


71

当我尝试将字符串转换为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}
]


1
@PatrickKafka,但答案是金。
2013年

1
或者,尝试转换为杰克逊
柯比(Kirby)

另请参阅我针对类似问题撰写的答案;要注意的是,您将不得不将数据解析为Object,然后转换为所需的任何内容。
aidtsu退出是因为SE为EVIL,2017年

Answers:


44

您是在告诉Gson,它正在寻找字符串到对象的映射列表,这实际上是在对对象的类型做出最佳猜测。由于JSON不能区分整数和浮点字段,因此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解析器。


仅出于完整性考虑,Float并不比Integer更通用,因为它不能准确地表示与Integer相同的所有值。但是双人可以。
brianmearns 2015年

我的意思是就类型而言泛型-浮点数可以表示非整数值。当然,在实践中,float / double不能表示比int / long多的值,但是浮点数的限制在这里并不是真正的问题。
dimo414

糟糕的建议,对不起,但是如果值从int更改为string到其他内容,但又翻了一番,您就搞砸了……
Enerccio

@Enerccio你的意思是“如果值从int变为字符串,再变为double以外的其他值”?值的类型何时更改?如果文档的架构发生更改,则需要更新Java类定义。
dimo414 '17

@ dimo414好吧,它可能可以存储不同类型的不同值,无论如何,我通过具有TaggedValue并存储带有该值的类型
对它

31

这有效:

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

11
嗨,我找到了这个答案,并使用了您在本文中提到的方式,但是,当它应该为int时,我还是得到了加倍:-(
armnotstrong

@armnotstrong这对哪个号码无效?上面的代码应该适用于所有32位int值,因为对于Java double类型(具有64位),所有值都具有相应的确切值。在(整数)double和int值之间进行转换,并在int范围内进行强制转换。但是,进入64位长范围时,不能在所有情况下正确转换超过2的乘方的负值(即52的幂)(4,503,599,627,370,496)。
Alexander233

11

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

case NUMBER:
   return in.nextDouble();

7

我参加聚会很晚,但是我自己碰到了这个。就我而言,我不想在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定义Gson gs = new Gson();,我重写了Double.class序列化以返回整数。

就我而言,我的JSON中包含Strings和Integers,但是我没有任何双打,所以这不会造成问题。

如果您需要一个double或float值,我想可以添加一些逻辑来测试每种数据类型特定的属性的值并返回适当的值。就像是

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

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


3
在我看来,听到“可能是字符串或整数”是一个很大的危险信号。听起来您的JSON数据结构不好-您正在创建整数和字符串的列表吗?从技术上讲,JSON规范允许这样做,但是它将为尝试与其进行接口的每个反序列化器带来痛苦。相反,请考虑:a)如果只是巧合某些数字,则将整个列表保留为字符串; b)将数字拆分成自己的列表;或c)将列表类型更改为更复杂的对象,以更好地反映数据。
dimo414 2014年

@ dimo414很好,所有三个解决方案都有其缺陷:a)没有用,因为您不知道哪个是数字,哪个是字符串;b)丢失订购信息(然后需要清晰的索引列表和开销);c)复杂对象将使结果json膨胀
Enerccio

@Enerccio复杂数据需要复杂的表示形式,因此c)通常是可以接受的折衷方案。您的观点是正确的,但是在实践中,我会感到满意的是,混合数据类型通常会带来更多麻烦,而通常情况下,重新检查您的需求将发现一种适用于您目的的替代结构,而无需跳过通过这样的箍。随意张贴一个具体的用例的一个问题,我很乐意在权衡。
dimo414

1

这项工作对我来说。

步骤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。


那不是一个解决方案,而是一个危险的解决方法,它将使整个项目变成垃圾桶
Farid

0
    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)
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.