当类型为 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
     
 }