簡體   English   中英

Gson 反序列化:設置最終字段

[英]Gson deserialization: set final field

我使用 gson 反序列化小部件層次結構,但在反序列化最終字段時遇到問題。

例子:

public final class Screen{

    @Expose
    private final List<WidgetDefinition> children       = null;
    @Expose
    private final String name           = null;
}

public final class AWidget implements WidgetDefinition {
    @Expose
    private final String name           = null;
}

我正在使用 WidgetDefinition 的自定義反序列化器反序列化屏幕,如下所示。 Screen 中的 'name' 設置正確,AWidget 中的 'name' 保持為空。

final class Deserializer implements JsonDeserializer<WidgetDefinition> {

    public WidgetDefinition deserialize(final JsonElement json, final Type type,
                                        final JsonDeserializationContext context) {

        JsonObject jsonObject = json.getAsJsonObject();

        String typeName = jsonObject.get("type").getAsString();
        if (typeName.equals("awidget")) {
            return context.deserialize(json, AWidget.class);
        } else {
            return null;
        }
    }
}

編輯:我想知道它是否與此有關:

Gson 1.7 不會序列化集合元素中的子類字段。 2.0 添加了這個額外的信息。

(https://sites.google.com/site/gson/gson-roadmap)

Gson 使用反射來設置 final 字段(通過.setAccessible(true) ),所以你描述的問題(和其他相關的)可能來自 Java 處理 finals 的方式......

JLS 17.5.3 final 字段的后續修改

在某些情況下,例如反序列化,系統將需要在構造后更改對象的最終字段。 final 字段可以通過反射和其他依賴於實現的方式來改變。 具有合理語義的唯一模式是構造對象然后更新對象的最終字段。 在對象的最終字段的所有更新完成之前,不應使對象對其他線程可見,也不應讀取最終字段。 final 字段的凍結發生在設置 final 字段的構造函數的末尾,以及在通過反射或其他特殊機制每次修改 final 字段之后立即發生。

即便如此,仍有許多並發症。 如果在字段聲明中將 final 字段初始化為編譯時常量表達式(第 15.28 節),則可能不會觀察到對 final 字段的更改,因為該 final 字段的使用在編譯時被替換為常量表達式的值.

另一個問題是規范允許對最終字段進行積極優化。 在一個線程中,允許對 final 字段的讀取重新排序,並使用那些未在構造函數中發生的對 final 字段的修改。

由於沒有提供完整的最少代碼和示例,因此對原始問題中的確切問題進行故障排除有點困難。 也許以下對 Gson 在反序列化過程中如何實例化目標對象的理解有所幫助。

Gson 1.7.1 和 2.0 在 vanilla 反序列化操作期間都尊重最終字段分配,並且初始最終字段分配沒有改變。 例如:

import com.google.gson.Gson;

public class GsonFoo
{
  public static void main(String[] args)
  {
    // {"name":"Fred","id":42}
    String json1 = "{\"name\":\"Fred\",\"id\":42}";
    System.out.println(new Gson().fromJson(json1, Bar1.class));
    // output:
    // Bar1: name=Fred, id=-1
  }
}

class Bar1
{
  String name = "BLANK";
  final int id = -1;

  @Override
  public String toString()
  {
    return String.format("Bar1: name=%s, id=%d", name, id);
  }
}

另一方面,例如在反序列化期間創建,因為 Gson 使用sun.misc.Unsafe —— 而不是用戶定義的構造函數 —— 在任何構造函數中明確定義的 final 字段的分配不受尊重。 例如:

import com.google.gson.Gson;

public class GsonFoo
{
  public static void main(String[] args)
  {
    // {"name":"Fred","id":42}
    String json1 = "{\"name\":\"Fred\",\"id\":42}";
    System.out.println(new Gson().fromJson(json1, Bar1.class));
    // output:
    // Bar1: name=Fred, id=42
  }
}

class Bar1
{
  String name = "BLANK";
  final int id;

  Bar1()
  {
    id = -1;
  }

  @Override
  public String toString()
  {
    return String.format("Bar1: name=%s, id=%d", name, id);
  }
}

總之,在 vanilla 反序列化期間,無法使用傳入 JSON 中的任何數據重新分配最終字段分配,但如果最終字段分配發生在用戶定義的構造函數中,則似乎是可能的。**

**我不確定 JVM 規范是否允許一些實現寬松,以便在不同的 JVM 上運行時可能會觀察到與上述行為不同的行為。

問題的特殊性與通用標題不太匹配,但我遇到了同樣的問題,而且這個問題對我來說是 Google 評分最高的結果,所以盡管問題的年齡很大,我還是會在這里回答。

總結這里其他答案的發現,似乎 Gson 的過去和當前版本反序列化對象,但不會反序列化基元(如果它們已被字段初始化)。

public final class A {
    // Gson will properly deserialise foo, regardless of initialisation.
    public final String foo = "bar";
}
public final class B {
    // i will always be 42.
    public final int i = 42;
}
public final class C {
    // Gson will properly deserialise j
    public final int j;
    public C() { j = 37; }
}

這種行為中的各種不一致足以讓我決定定義自定義類型適配器並省略默認構造函數。 從 Gson 2.1 開始, TypeAdapter使這變得非常簡單。 缺點是 Gson 現在無法自動處理。

給定一個類型Point定義為:

public final class Point {
    public final int x;
    public final int y;
    public Point(final int x, final int y) {
        this.x = x;
        this.y = y;
    }
}

文檔中示例的稍微調整的變體

public class PointAdapter extends TypeAdapter<Point> {
  public Point read(JsonReader reader) throws IOException {
    if (reader.peek() == JsonToken.NULL) {
      reader.nextNull();
      return null;
    }
    String xy = reader.nextString();
    String[] parts = xy.split(",");
    int x = Integer.parseInt(parts[0]);
    int y = Integer.parseInt(parts[1]);
    return new Point(x, y);
  }
  public void write(JsonWriter writer, Point point) throws IOException {
    if (point == null) {
      writer.nullValue();
      return;
    }
    String xy = point.x + "," + point.y;
    writer.value(xy);
  }
}

產生想要的結果。

我嘗試了一個最終字段的實驗——首先用初始化器設置它們,例如:

class Test {
    final int x = 1;
    private Test() {}
}

GSON 不會正確地反序列化它。

然后在構造函數中......

class Test {
    final int x;
    private Test() {
    x = 1;
    }
}

這奏效了。 也許這是一個 Java 編譯器優化,其中帶有初始化程序的最終原始或字符串類型變量被視為編譯時常量,而沒有創建實際變量,而如果在構造函數中完成初始化,則會創建一個變量?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM