当类型为 Object 时如何使用 GSON 将专用浮点值反序列化为 Double
How to deserialize specialized floating values with GSON to Double when type is Object
我有一个 class,它有一个 Object[] 字段来存储值,我需要能够使用 gson 对其进行反序列化,以便专门的浮点值是 Double 而不是 String。
这是一些测试代码。第二个断言失败,因为特殊值被反序列化为字符串:
@Test
public void testGson() {
Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
ValuesObject toJson = new ValuesObject(0, new Object[] { 0.0, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN });
String json = gson.toJson(toJson);
assertEquals("{\"type\":0,\"values\":[0.0,Infinity,-Infinity,NaN]}", json);
ValuesObject fromJson = gson.fromJson(json, ValuesObject.class);
assertEquals(toJson, fromJson); // fails
}
private class ValuesObject {
private int type;
private Object[] values;
public ValuesObject(int type, Object[] values) {
this.type = type;
this.values = values;
}
// snip... equals and hashCode
}
我知道答案在于自定义 TypeAdapter 或 Deserializer,但我没有看到最好的方法。我真正想要做的就是覆盖 ObjectTypeAdapter 来处理这三个特殊情况的 String 值,但代码不可扩展。看来需要实现一整套适配器。
基于 ,我有一些可行的方法,但这是最好的方法吗?
class ValuesObjectTypeAdapterFactory implements TypeAdapterFactory {
private ValuesObjectTypeAdapterFactory() { }
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType() != Object[].class) {
return null;
}
// Get the default adapter as delegate
// Cast is safe due to `type` check at method start
@SuppressWarnings("unchecked")
TypeAdapter<Object> delegate = (TypeAdapter<Object>) gson.getDelegateAdapter(this, type);
// Cast is safe because `T` is ValuesObject or subclass (due to `type` check at method start)
@SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new TypeAdapter<>() {
@Override
public void write(JsonWriter out, Object value) throws IOException {
delegate.write(out, value);
}
@Override
public Object read(JsonReader in) {
JsonElement jsonObject = new JsonParser().parse(in);
Object value = delegate.fromJsonTree(jsonObject);
if (value instanceof Object[]) {
Object[] array = (Object[]) value;
for (int i=0; i<array.length; i++) {
if (array[i] instanceof String) {
switch ((String)array[i]) {
case "Infinity":
case "-Infinity":
case "NaN":
array[i] = Double.valueOf((String) array[i]);
}
}
}
}
return value;
}
};
return adapter;
}
}
如果只有那个特定的字段应该以这种方式反序列化,您可以使用 @JsonAdapter
on that field instead of registering the type adapter factory globally with a GsonBuilder
to avoid affecting other unrelated fields. You then need to replace the call to gson.getDelegateAdapter
with gson.getAdapter
due to this Gson issue.
你应该避免在这里使用JsonParser
;它总是在宽松模式下解析 JSON 而不管 JsonReader
设置如何(尽管这里并不重要,因为无论如何您都必须使用宽松模式来解析非有限数字)。不幸的是,目前还没有很好的记录。相反,您可以使用 gson.getAdapter(JsonElement.class)
并使用返回的适配器来解析 JSON。但是,对于您的用例,甚至不需要将 JSON 解析为 JsonElement
,您可以直接调用 delegate.read(in)
.
以下显示了建议的更改。它使用“diff-like”格式,其中应删除以 -
开头的每一行,并应添加以 +
开头的每一行:
+/**
+ * Should only be used with Gson's {@link JsonAdapter}; otherwise infinite recursion can occur.
+ */
class ValuesObjectTypeAdapterFactory implements TypeAdapterFactory {
- private ValuesObjectTypeAdapterFactory() { }
+ // Default constructor for Gson's @JsonAdapter
+ public ValuesObjectTypeAdapterFactory() { }
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType() != Object[].class) {
return null;
}
// Get the default adapter as delegate
// Cast is safe due to `type` check at method start
@SuppressWarnings("unchecked")
- TypeAdapter<Object> delegate = (TypeAdapter<Object>) gson.getDelegateAdapter(this, type);
+ // Uses `getAdapter` as workaround for https://github.com/google/gson/issues/1028
+ TypeAdapter<Object> delegate = (TypeAdapter<Object>) gson.getAdapter(type);
@SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new TypeAdapter<>() {
@Override
public void write(JsonWriter out, Object value) throws IOException {
delegate.write(out, value);
}
@Override
public Object read(JsonReader in) throws IOException {
- JsonElement jsonObject = new JsonParser().parse(in);
-
- Object value = delegate.fromJsonTree(jsonObject);
+ Object value = delegate.read(in);
if (value instanceof Object[]) {
Object[] array = (Object[]) value;
for (int i=0; i<array.length; i++) {
if (array[i] instanceof String) {
switch ((String)array[i]) {
case "Infinity":
case "-Infinity":
case "NaN":
array[i] = Double.valueOf((String) array[i]);
}
}
}
}
return value;
}
};
return adapter;
}
}
private class ValuesObject {
private final int type;
+ @JsonAdapter(ValuesObjectTypeAdapterFactory.class)
private final Object[] values;
public ValuesObject(int type, Object[] values) {
this.type = type;
this.values = values;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.deepHashCode(values);
result = prime * result + Objects.hash(type);
return result;
}
// snip... equals and hashCode
}
我有一个 class,它有一个 Object[] 字段来存储值,我需要能够使用 gson 对其进行反序列化,以便专门的浮点值是 Double 而不是 String。
这是一些测试代码。第二个断言失败,因为特殊值被反序列化为字符串:
@Test
public void testGson() {
Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
ValuesObject toJson = new ValuesObject(0, new Object[] { 0.0, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN });
String json = gson.toJson(toJson);
assertEquals("{\"type\":0,\"values\":[0.0,Infinity,-Infinity,NaN]}", json);
ValuesObject fromJson = gson.fromJson(json, ValuesObject.class);
assertEquals(toJson, fromJson); // fails
}
private class ValuesObject {
private int type;
private Object[] values;
public ValuesObject(int type, Object[] values) {
this.type = type;
this.values = values;
}
// snip... equals and hashCode
}
我知道答案在于自定义 TypeAdapter 或 Deserializer,但我没有看到最好的方法。我真正想要做的就是覆盖 ObjectTypeAdapter 来处理这三个特殊情况的 String 值,但代码不可扩展。看来需要实现一整套适配器。
基于
class ValuesObjectTypeAdapterFactory implements TypeAdapterFactory {
private ValuesObjectTypeAdapterFactory() { }
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType() != Object[].class) {
return null;
}
// Get the default adapter as delegate
// Cast is safe due to `type` check at method start
@SuppressWarnings("unchecked")
TypeAdapter<Object> delegate = (TypeAdapter<Object>) gson.getDelegateAdapter(this, type);
// Cast is safe because `T` is ValuesObject or subclass (due to `type` check at method start)
@SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new TypeAdapter<>() {
@Override
public void write(JsonWriter out, Object value) throws IOException {
delegate.write(out, value);
}
@Override
public Object read(JsonReader in) {
JsonElement jsonObject = new JsonParser().parse(in);
Object value = delegate.fromJsonTree(jsonObject);
if (value instanceof Object[]) {
Object[] array = (Object[]) value;
for (int i=0; i<array.length; i++) {
if (array[i] instanceof String) {
switch ((String)array[i]) {
case "Infinity":
case "-Infinity":
case "NaN":
array[i] = Double.valueOf((String) array[i]);
}
}
}
}
return value;
}
};
return adapter;
}
}
如果只有那个特定的字段应该以这种方式反序列化,您可以使用 @JsonAdapter
on that field instead of registering the type adapter factory globally with a GsonBuilder
to avoid affecting other unrelated fields. You then need to replace the call to gson.getDelegateAdapter
with gson.getAdapter
due to this Gson issue.
你应该避免在这里使用JsonParser
;它总是在宽松模式下解析 JSON 而不管 JsonReader
设置如何(尽管这里并不重要,因为无论如何您都必须使用宽松模式来解析非有限数字)。不幸的是,目前还没有很好的记录。相反,您可以使用 gson.getAdapter(JsonElement.class)
并使用返回的适配器来解析 JSON。但是,对于您的用例,甚至不需要将 JSON 解析为 JsonElement
,您可以直接调用 delegate.read(in)
.
以下显示了建议的更改。它使用“diff-like”格式,其中应删除以 -
开头的每一行,并应添加以 +
开头的每一行:
+/**
+ * Should only be used with Gson's {@link JsonAdapter}; otherwise infinite recursion can occur.
+ */
class ValuesObjectTypeAdapterFactory implements TypeAdapterFactory {
- private ValuesObjectTypeAdapterFactory() { }
+ // Default constructor for Gson's @JsonAdapter
+ public ValuesObjectTypeAdapterFactory() { }
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType() != Object[].class) {
return null;
}
// Get the default adapter as delegate
// Cast is safe due to `type` check at method start
@SuppressWarnings("unchecked")
- TypeAdapter<Object> delegate = (TypeAdapter<Object>) gson.getDelegateAdapter(this, type);
+ // Uses `getAdapter` as workaround for https://github.com/google/gson/issues/1028
+ TypeAdapter<Object> delegate = (TypeAdapter<Object>) gson.getAdapter(type);
@SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new TypeAdapter<>() {
@Override
public void write(JsonWriter out, Object value) throws IOException {
delegate.write(out, value);
}
@Override
public Object read(JsonReader in) throws IOException {
- JsonElement jsonObject = new JsonParser().parse(in);
-
- Object value = delegate.fromJsonTree(jsonObject);
+ Object value = delegate.read(in);
if (value instanceof Object[]) {
Object[] array = (Object[]) value;
for (int i=0; i<array.length; i++) {
if (array[i] instanceof String) {
switch ((String)array[i]) {
case "Infinity":
case "-Infinity":
case "NaN":
array[i] = Double.valueOf((String) array[i]);
}
}
}
}
return value;
}
};
return adapter;
}
}
private class ValuesObject {
private final int type;
+ @JsonAdapter(ValuesObjectTypeAdapterFactory.class)
private final Object[] values;
public ValuesObject(int type, Object[] values) {
this.type = type;
this.values = values;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.deepHashCode(values);
result = prime * result + Objects.hash(type);
return result;
}
// snip... equals and hashCode
}