一、问题复现
class BaseResponse<T>(
val code: Int = -1,
val message: String? = null
) {
var data: T? = null
fun isSuccess(): Boolean {
return code == 0
}
}
1.2、不做任何处理,直接用Gson.fromJson解析
val json = "{\"code\":200,\"message\":\"成功\",\"data\":{\"ints\":200,\"doubles\":200.98,\"floats\":29.0986,\"longs\":29323627832875342,\"string\":\"字符串\"}}"
val baseResponse = Gson().fromJson(json, BaseResponse::class.java)
println(JsonUtils.toJson(baseResponse))
1.3、解析结果
I/System.out: {"code":200,"data":{"ints":200.0,"doubles":200.98,"floats":29.0986,"longs":2.9323627832875344E16,"string":"字符串"},"message":"成功"}
1.4、问题
Int类型的"ints"被转换成了double类型、Long类型的"longs"也被转换了
二、解决方案
网上千篇一律就是新建TypeAdapter接口的子类,手动转换。MyDataTypeAdapter
package com.xxx.baseapp.net;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.ToNumberPolicy;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.LinkedTreeMap;
import com.google.gson.internal.bind.ObjectTypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public final class MyDataTypeAdapter 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 MapTypeAdapter(gson);
}
return null;
}
};
private final Gson gson;
private MyDataTypeAdapter(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<>();
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:
String s = in.nextString();
if (s.contains(".")) {
return Double.valueOf(s);
} else {
try {
return Integer.valueOf(s);
} catch (Exception e) {
return Long.valueOf(s);
}
}
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 {
if (value == null) {
out.nullValue();
return;
}
//noinspection unchecked
TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) gson.getAdapter(value.getClass());
if (typeAdapter instanceof ObjectTypeAdapter) {
out.beginObject();
out.endObject();
return;
}
typeAdapter.write(out, value);
}
/**
* 使用自定义工厂方法取代 Gson 实例中的工厂方法
*/
public Gson getGson() {
Gson gson = new GsonBuilder().create();
try {
Field factories = Gson.class.getDeclaredField("factories");
factories.setAccessible(true);
Object o = factories.get(gson);
Class<?>[] declaredClasses = Collections.class.getDeclaredClasses();
for (Class c : declaredClasses) {
if ("java.util.Collections$UnmodifiableList".equals(c.getName())) {
Field listField = c.getDeclaredField("list");
listField.setAccessible(true);
List<TypeAdapterFactory> list = (List<TypeAdapterFactory>) listField.get(o);
int i = list.indexOf(ObjectTypeAdapter.getFactory(ToNumberPolicy.DOUBLE));
list.set(i, MyDataTypeAdapter.FACTORY);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return gson;
}
}
通过分析GsonBuilder的构造方法源码:
/**
* Creates a GsonBuilder instance that can be used to build Gson with various configuration
* settings. GsonBuilder follows the builder pattern, and it is typically used by first
* invoking various configuration methods to set desired options, and finally calling
* {@link #create()}.
*/
public GsonBuilder() {
}
/**
* Constructs a GsonBuilder instance from a Gson instance. The newly constructed GsonBuilder
* has the same configuration as the previously built Gson instance.
*
* @param gson the gson instance whose configuration should by applied to a new GsonBuilder.
*/
GsonBuilder(Gson gson) {
......
this.objectToNumberStrategy = gson.objectToNumberStrategy;
this.numberToNumberStrategy = gson.numberToNumberStrategy;
this.reflectionFilters.addAll(gson.reflectionFilters);
}
/**
* Configures Gson to apply a specific number strategy during deserialization of {@link Object}.
*
* @param objectToNumberStrategy the actual object-to-number strategy
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @see ToNumberPolicy#DOUBLE The default object-to-number strategy
* @since 2.8.9
*/
public GsonBuilder setObjectToNumberStrategy(ToNumberStrategy objectToNumberStrategy) {
this.objectToNumberStrategy = Objects.requireNonNull(objectToNumberStrategy);
return this;
}
可以发现,在构造的时候默认了Object和Number类转换成数字类型的处理策略,可以看到,Object转换默认为ToNumberPolicy.DOUBLE的dobule的类型,因此不单单是int会被转换成double,Long类型等等数值类型都会默认被转换成dobule类型。
2.2.2、策略类
/**
* An enumeration that defines two standard number reading strategies and a couple of
* strategies to overcome some historical Gson limitations while deserializing numbers as
* {@link Object} and {@link Number}.
*
* @see ToNumberStrategy
* @since 2.8.9
*/
public enum ToNumberPolicy implements ToNumberStrategy {
/**
* Using this policy will ensure that numbers will be read as {@link Double} values.
* This is the default strategy used during deserialization of numbers as {@link Object}.
*/
DOUBLE {
@Override public Double readNumber(JsonReader in) throws IOException {
return in.nextDouble();
}
},
/**
* Using this policy will ensure that numbers will be read as a lazily parsed number backed
* by a string. This is the default strategy used during deserialization of numbers as
* {@link Number}.
*/
LAZILY_PARSED_NUMBER {
@Override public Number readNumber(JsonReader in) throws IOException {
return new LazilyParsedNumber(in.nextString());
}
},
/**
* Using this policy will ensure that numbers will be read as {@link Long} or {@link Double}
* values depending on how JSON numbers are represented: {@code Long} if the JSON number can
* be parsed as a {@code Long} value, or otherwise {@code Double} if it can be parsed as a
* {@code Double} value. If the parsed double-precision number results in a positive or negative
* infinity ({@link Double#isInfinite()}) or a NaN ({@link Double#isNaN()}) value and the
* {@code JsonReader} is not {@link JsonReader#isLenient() lenient}, a {@link MalformedJsonException}
* is thrown.
*/
LONG_OR_DOUBLE {
@Override public Number readNumber(JsonReader in) throws IOException, JsonParseException {
String value = in.nextString();
try {
return Long.parseLong(value);
} catch (NumberFormatException longE) {
try {
Double d = Double.valueOf(value);
if ((d.isInfinite() || d.isNaN()) && !in.isLenient()) {
throw new MalformedJsonException("JSON forbids NaN and infinities: " + d + "; at path " + in.getPreviousPath());
}
return d;
} catch (NumberFormatException doubleE) {
throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPreviousPath(), doubleE);
}
}
}
},
/**
* Using this policy will ensure that numbers will be read as numbers of arbitrary length
* using {@link BigDecimal}.
*/
BIG_DECIMAL {
@Override public BigDecimal readNumber(JsonReader in) throws IOException {
String value = in.nextString();
try {
return new BigDecimal(value);
} catch (NumberFormatException e) {
throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPreviousPath(), e);
}
}
}
}
通过上面的策略类源码,我们发现:LAZILY_PARSED_NUMBER 就是处理数值类型的枚举,查看LazilyParsedNumber源码,发现确实进行了细致的数值类型处理划分:
/**
* This class holds a number value that is lazily converted to a specific number type
*
* @author Inderjeet Singh
*/
@SuppressWarnings("serial") // ignore warning about missing serialVersionUID
public final class LazilyParsedNumber extends Number {
private final String value;
/** @param value must not be null */
public LazilyParsedNumber(String value) {
this.value = value;
}
@Override
public int intValue() {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
try {
return (int) Long.parseLong(value);
} catch (NumberFormatException nfe) {
return new BigDecimal(value).intValue();
}
}
}
@Override
public long longValue() {
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
return new BigDecimal(value).longValue();
}
}
@Override
public float floatValue() {
return Float.parseFloat(value);
}
@Override
public double doubleValue() {
return Double.parseDouble(value);
}
@Override
public String toString() {
return value;
}
/**
* If somebody is unlucky enough to have to serialize one of these, serialize
* it as a BigDecimal so that they won't need Gson on the other side to
* deserialize it.
*/
private Object writeReplace() throws ObjectStreamException {
return new BigDecimal(value);
}
private void readObject(ObjectInputStream in) throws IOException {
// Don't permit directly deserializing this class; writeReplace() should have written a replacement
throw new InvalidObjectException("Deserialization is unsupported");
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof LazilyParsedNumber) {
LazilyParsedNumber other = (LazilyParsedNumber) obj;
return value == other.value || value.equals(other.value);
}
return false;
}
}
2.2.2、最终解决方案
既然知道构造的时候设置了默认的 ToNumberPolicy.DOUBLE ,那修改起来就简单了,我们调用设置转换的策略方法即可:
fun getGson(): Gson {
val gsonBuilder = GsonBuilder()
gsonBuilder.setObjectToNumberStrategy(ToNumberPolicy.LAZILY_PARSED_NUMBER)
return gsonBuilder.create()
}
调用修改策略方案后的getGson解析
val json = "{\"code\":200,\"message\":\"成功\",\"data\":{\"ints\":200,\"doubles\":200.98,\"floats\":29.0986,\"longs\":29323627832875342,\"string\":\"字符串\"}}"
val baseResponse = getGson().fromJson(json, BaseResponse::class.java)
println(JsonUtils.toJson(baseResponse))
测试结果:
I/System.out: {"code":200,"data":{"ints":200,"doubles":200.98,"floats":29.0986,"longs":29323627832875342,"string":"字符串"},"message":"成功"}
至此完美解决!!!!
网上千篇一律的复制粘贴说这可以解决那个可以解决,结果然并卵。这个问题困扰了我2天,最后还是完美解决