Gson将json字符串转map导致int型被转换成double的采坑之旅

栏目: 服务器 · 发布时间: 4年前

内容简介:前言:日常开发中,与json打交道的机会很多,一般对象json转都不会出现什么问题,但是json转对象就有可能出现问题了,今天就来说说json转map导致int型转换成double的问题接下来的操作大家都知道了,借助于网络平台,于是乎找到几种解决方式,细心的我发现有人评论解决他们的问题,看来有戏啊【手动滑稽】1、需要gson解析的类型 , 重写他的deserialize方法, 就是将其中json手动解析成map , 不对数据进行处理

前言:日常开发中,与json打交道的机会很多,一般对象json转都不会出现什么问题,但是json转对象就有可能出现问题了,今天就来说说json转map导致int型转换成double的问题

问题重现

  • 之前解决过long型被转化成科学计数法的问题,所有就拿以前的公用方法,一个泛型 工具
public class MyType<T> {
    public T gsonToMap(String strJson) {
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(new TypeToken<T>(){}.getType(),new MapTypeAdapter()).create();
        return gson.fromJson(strJson, new TypeToken<T>() {
        }.getType());
    }
}

String json = "{\"identifier\":\"18111111111\",\"opType\":1,\"platform\":0}";
Map<String, Object> map = new MyType<Map<String, Object>>().gsonToMap(json);
复制代码
  • 直接将需求类型对象传入泛型就好了。
    Gson将json字符串转map导致int型被转换成double的采坑之旅
  • 然而事与愿违,int成功的转换成double,1->1.0、0->0.0,如上图所示
Gson将json字符串转map导致int型被转换成double的采坑之旅

接下来的操作大家都知道了,借助于网络平台,于是乎找到几种解决方式,细心的我发现有人评论解决他们的问题,看来有戏啊【手动滑稽】

解决方案

1、需要gson解析的类型 , 重写他的deserialize方法, 就是将其中json手动解析成map , 不对数据进行处理

public HashMap<String,Object> gsonToMap(String strJson) {
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(
                new TypeToken<HashMap<String,Object>>(){}.getType(),
                        new JsonDeserializer<HashMap<String, Object>>() {
                            @Override
                            public HashMap<String, Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

                                HashMap<String, Object> hashMap = new HashMap<>();
                                JsonObject jsonObject = json.getAsJsonObject();
                                Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
                                for (Map.Entry<String, JsonElement> entry : entrySet) {
                                    hashMap.put(entry.getKey(), entry.getValue());
                                }
                                return hashMap;
                            }
                        }).create();

        return gson.fromJson(strJson, new TypeToken<HashMap<String,Object>>() {
        }.getType());
    }
复制代码
  • 经过实践,是可以转化成功,但是本着复用的思想,我把map替换成泛型,然后就不行,一脸蒙蔽;(问题暂时搁置一旁)

2、自定义TypeAdapter替代Gson默认的adapter(此处埋下伏笔【偷笑】)解决,自定义TypeAdapter如下:

public class MapTypeAdapter extends TypeAdapter<Object> {

    private final TypeAdapter<Object> delegate = new Gson().getAdapter(Object.class);

    @Override
    public Object read(JsonReader in) throws IOException {
        JsonToken token = in.peek();
        switch (token) {
            case BEGIN_ARRAY:
                List<Object> list = new ArrayList<>();
                in.beginArray();
                while (in.hasNext()) {
                    list.add(read(in));
                }
                in.endArray();
                return list;

            case BEGIN_OBJECT:
                Map<String, Object> map = new LinkedTreeMap<>();
                in.beginObject();
                while (in.hasNext()) {
                    map.put(in.nextName(), read(in));
                }
                in.endObject();
                return map;
                
            case STRING:
                return in.nextString();

            case NUMBER:
                /**
                 * 改写数字的处理逻辑,将数字值分为整型与浮点型。
                 */
                double dbNum = in.nextDouble();

                // 数字超过long的最大值,返回浮点类型
                if (dbNum > Long.MAX_VALUE) {
                    return String.valueOf(dbNum);
                }

                // 判断数字是否为整数值
                long lngNum = (long) dbNum;
                if (dbNum == lngNum) {
                    return String.valueOf(lngNum);
                } else {
                    return String.valueOf(dbNum);
                }

            case BOOLEAN:
                return in.nextBoolean();

            case NULL:
                in.nextNull();
                return null;

            default:
                throw new IllegalStateException();
        }
    }

    @Override
    public void write(JsonWriter out, Object value) throws IOException {
        delegate.write(out,value);
    }
}
复制代码
  • 然后如法炮制,仍然固执的使用泛型,并将我们自定义的注册到gson上
public T gsonToMap(String strJson) {
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(new TypeToken<T>(){}.getType(),new MapTypeAdapter()).create();
        return gson.fromJson(strJson, new TypeToken<T>() {
        }.getType());
    }
    
String json = "{\"identifier\":\"18111111111\",\"opType\":1,\"platform\":0}";
Map<String, Object> map = new MyType<Map<String, Object>>().gsonToMap(json);
复制代码
  • 等待结果中...,每错就是这么刺激,int一样会转化成double
Gson将json字符串转map导致int型被转换成double的采坑之旅
  • 把泛型直接替换成目标对象类型,再试了试,证明是没问题的
public static Map<String, Object> gsonToMap(String strJson) {
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(new TypeToken<Map<String,Object>>(){}.getType(),new MapTypeAdapter()).create();
        return gson.fromJson(strJson, new TypeToken<Map<String, Object>>() {
        }.getType());
    }
    
String json = "{\"identifier\":\"18111111111\",\"opType\":1,\"platform\":0}";
Map<String, Object> map = new MyType<Map<String, Object>>().gsonToMap(json);
复制代码
Gson将json字符串转map导致int型被转换成double的采坑之旅

上述方案的确是可以解决我的问题,但是却给我留下了疑问;本着知其然知其所以然的目的,觉得解决这些疑惑

解决疑惑

  • 为什么传递泛型不行?
  • 为什么是把int转化成了double,而不是其他类型比如string?

1、关于泛型这里就要提到 泛型擦除 ,及泛型只在编译阶段有效,运行时就无效了

  • 跟踪源码会发现 TypeAdapter 就已经是一个泛型抽象类了
public abstract class TypeAdapter<T>
复制代码
  • 我在外层又传了一次泛型,运行时根本就不认识我传递的目标对象类型了
    Gson将json字符串转map导致int型被转换成double的采坑之旅
  • 在外层直接传递目标对象类型,这里我传递的是HashMap<String,Object>,可我完全正确的识别出来
Gson将json字符串转map导致int型被转换成double的采坑之旅
  • 所以我这里的操作完全是符合泛型擦除,所以运行时代码根本不认识这是个什么东西,自然不回你达到我们想要的效果了

2、int转double,其实这是 Gson 在源码中故意为之的,其实不仅是 int , long 也会转化成 double ,接下来我们去寻找证据

  • 跟踪源码,走你 => 过程省略1000步,忽略1000000字,我们会来到Gson下的这个地方
    Gson将json字符串转map导致int型被转换成double的采坑之旅
  • 这里处理Number型的adapter,除此之外还有
处理double:
private TypeAdapter<Number> doubleAdapter(boolean serializeSpecialFloatingPointValues) {
    if (serializeSpecialFloatingPointValues) {
      return TypeAdapters.DOUBLE;
    }
    return new TypeAdapter<Number>() {
      @Override public Double read(JsonReader in) throws IOException {
        if (in.peek() == JsonToken.NULL) {
          in.nextNull();
          return null;
        }
        return in.nextDouble();
      }
      @Override public void write(JsonWriter out, Number value) throws IOException {
        if (value == null) {
          out.nullValue();
          return;
        }
        double doubleValue = value.doubleValue();
        checkValidFloatingPoint(doubleValue);
        out.value(value);
      }
    };
  }
  
  处理float:
  private TypeAdapter<Number> floatAdapter(boolean serializeSpecialFloatingPointValues) {
    if (serializeSpecialFloatingPointValues) {
      return TypeAdapters.FLOAT;
    }
    return new TypeAdapter<Number>() {
      @Override public Float read(JsonReader in) throws IOException {
        if (in.peek() == JsonToken.NULL) {
          in.nextNull();
          return null;
        }
        return (float) in.nextDouble();
      }
      @Override public void write(JsonWriter out, Number value) throws IOException {
        if (value == null) {
          out.nullValue();
          return;
        }
        float floatValue = value.floatValue();
        checkValidFloatingPoint(floatValue);
        out.value(value);
      }
    };
  }
复制代码
  • 其实这里就是在寻找与我们目标对象想匹配的类型,但是如果找不到相匹配的类型,就会去调用 ObjectTypeAdapter ,继续跟踪,它终于要在这里正式寻找喜欢的适配器了【斜眼笑】
Gson将json字符串转map导致int型被转换成double的采坑之旅
  • 咋们运气比较好,这 for (TypeAdapterFactory factory : factories) 里有40几个适配器,第二个就是我们寻找的 ObjectTypeAdapter
    Gson将json字符串转map导致int型被转换成double的采坑之旅
  • 它一看大家都是 T 就你跟我长得最像了,那就调用你了,于是乎就来到新世界
public final class ObjectTypeAdapter extends TypeAdapter<Object> {
  public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
    @SuppressWarnings("unchecked")
    @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
      if (type.getRawType() == Object.class) {
        return (TypeAdapter<T>) new ObjectTypeAdapter(gson);
      }
      return null;
    }
  };

  private final Gson gson;

  ObjectTypeAdapter(Gson gson) {
    this.gson = gson;
  }

  @Override public Object read(JsonReader in) throws IOException {
    JsonToken token = in.peek();
    switch (token) {
    case BEGIN_ARRAY:
      List<Object> list = new ArrayList<Object>();
      in.beginArray();
      while (in.hasNext()) {
        list.add(read(in));
      }
      in.endArray();
      return list;

    case BEGIN_OBJECT:
      Map<String, Object> map = new LinkedTreeMap<String, Object>();
      in.beginObject();
      while (in.hasNext()) {
        map.put(in.nextName(), read(in));
      }
      in.endObject();
      return map;

    case STRING:
      return in.nextString();

    case NUMBER:
      return in.nextDouble();

    case BOOLEAN:
      return in.nextBoolean();

    case NULL:
      in.nextNull();
      return null;

    default:
      throw new IllegalStateException();
    }
  }

  @SuppressWarnings("unchecked")
  @Override public void write(JsonWriter out, Object value) throws IOException {
    if (value == null) {
      out.nullValue();
      return;
    }

    TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) gson.getAdapter(value.getClass());
    if (typeAdapter instanceof ObjectTypeAdapter) {
      out.beginObject();
      out.endObject();
      return;
    }

    typeAdapter.write(out, value);
  }
}
复制代码
  • 是不是跟我们之前自定义的adapter一模一样,这就是为什么我们要复写这个 TypeAdapter ,重点看下面
case NUMBER:
      return in.nextDouble();
复制代码
  • 只要是 Number (包括 int、long、float、double 等)型,都会被强制转化成 double ,至于为什么这么做,因为这里所有的类型都可以转换成 double ,而反过来则不行。
Gson将json字符串转map导致int型被转换成double的采坑之旅

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

计算机常用算法

计算机常用算法

徐士良 / 第2版 (1995年11月1日) / 1995-11 / 25.0

《计算机常用算法(第2版)》由清华大学出版社出版。一起来看看 《计算机常用算法》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

SHA 加密
SHA 加密

SHA 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试