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

Fab*_*ndl 3 java json final gson deserialization

我使用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;
}
Run Code Online (Sandbox Code Playgroud)

我正在使用WidgetDefinition的自定义反序列化器对屏幕进行反序列化,如下所示.屏幕中的'name'设置正确,AWidget中的'name'保持为null.

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:我想知道它是否必须对此做些什么:

Gson 1.7不会序列化集合元素中的子类字段.2.0添加了这些额外信息.

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

Jan*_*iak 12

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

JLS 17.5.3最终字段的后续修改

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

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

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


Pro*_*uce 6

解决原始问题中的确切问题有点困难,因为没有提供完整的最小代码和示例.也许以下理解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);
  }
}
Run Code Online (Sandbox Code Playgroud)

另一方面,例如在反序列化期间创建,因为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);
  }
}
Run Code Online (Sandbox Code Playgroud)

总之,在vanilla反序列化期间,不可能使用传入JSON中的任何数据重新分配最终字段赋值,但如果最终字段分配在用户定义的构造函数中发生,则可能看起来可能.**

**我不确定JVM规范是否允许一些实现更宽松,以便在不同的JVM上运行时可能会观察到与上述行为不同的行为.


mkj*_*sen 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; }
}
Run Code Online (Sandbox Code Playgroud)

这种行为的各种不一致性足以让我决定定义自定义类型适配器并省略默认构造函数.自从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;
    }
}
Run Code Online (Sandbox Code Playgroud)

文档中示例的略微调整的变体

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

产生所需的结果.