Gson反序列化:设置最终字段

时间:2011-12-02 14:02:59

标签: java json deserialization gson final

我使用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的自定义反序列化器对屏幕进行反序列化,如下所示。屏幕中的'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)

4 个答案:

答案 0 :(得分:12)

Gson使用反射来设置最终字段(通过.setAccessible(true)),所以你描述的问题(和其他相关的)可能来自Java处理决赛的方式......

  

JLS 17.5.3 Subsequent Modification of final Fields

     

在某些情况下,例如反序列化,系统需要在构造后更改对象的最终字段。最终字段可以通过反射和其他依赖于实现的方式进行更改。它具有合理语义的唯一模式是构造对象然后更新对象的最终字段的模式。对象不应该对其他线程可见,也不应该读取最终字段,直到完成对象的最终字段的所有更新。最终字段的冻结既发生在设置了最终字段的构造函数的末尾,也发生在通过反射或其他特殊机制对每个最终字段进行修改之后。

     

即使这样,也有许多并发症。如果在字段声明中将final字段初始化为编译时常量表达式(第15.28节),则可能无法观察到对final字段的更改,因为在编译时将该final字段的使用替换为常量表达式的值

     

另一个问题是规范允许对最终字段进行积极优化。在一个线程中,允许使用构造函数中不发生的最终字段的那些修改来重新排序最终字段的读取。

答案 1 :(得分:5)

解决原始问题中的确切问题有点困难,因为没有提供完整的最小代码和示例。也许以下对在反序列化过程中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 - 而不是用户定义的构造函数 - 不遵守在任何构造函数中显式定义的最终字段的赋值。例如:

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上运行时可能会观察到与上述行为不同的行为。

答案 2 :(得分:5)

问题的特殊性与通用标题不完全匹配,但我遇到了同样的问题,问题是对我来说评分最高的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);
  }
}

产生所需的结果。

答案 3 :(得分:1)

我尝试使用最终字段进行实验 - 首先使用初始化设置它们,例如:

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

GSON不会对此进行适当的反序列化。

然后在构造函数中......

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

这很有用。也许它是Java编译器优化,其中带有初始化器的最终原语或String类型变量被视为conmpile-time常量,而没有创建实际变量,而如果在构造函数中完成初始化,则创建变量?