Gson反序列化:设置final字段

mmvthczy  于 2022-11-06  发布在  其他
关注(0)|答案(4)|浏览(241)

我使用gson反序列化了一个小部件层次结构,但是在反序列化final字段时遇到了问题。
示例:

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,如下所示。Screen中的'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;
        }
    }
}

**编辑:**我想知道它是否与此有关:

Gson1.7不会序列化集合元素中的子类字段。2.0添加了这个额外的信息。
(网址:)

eoxn13cs

eoxn13cs1#

Gson使用反射来设置final字段(通过.setAccessible(true)),因此您描述的问题(以及其他相关问题)可能来自Java处理final的方式......
JLS 17.5.3最终字段的后续修改
在某些情况下,例如反序列化,系统将需要在构造后更改对象的final字段。final字段可以通过反射和其他依赖于实现的方式进行更改。唯一具有合理语义的模式是构造对象,然后更新对象的final字段。不应使对象对其他线程可见,也不应该读取final字段,直到对对象的final字段的所有更新都完成。final字段的冻结既发生在设置final字段的构造函数的末尾,也发生在每次通过反射或其他特殊机制修改final字段之后。
即便如此,也会有一些复杂的情况:如果final字段在字段声明中初始化为编译时的常量表达式(参见15.28节),那么final字段的变化可能不会被观察到,因为final字段的使用在编译时被常量表达式的值所取代。
另一个问题是规范允许对final字段进行积极的优化。在一个线程中,允许对final字段的读取进行重新排序,这些修改不是在构造函数中发生的。

jvidinwx

jvidinwx2#

由于没有提供完整的最小代码和示例,所以要解决原始问题中的确切问题有点困难。也许下面对Gson如何在反序列化过程中示例化目标对象的理解会有所帮助。
Gson 1.7.1和2.0在vanilla反序列化操作期间都遵守final字段赋值,并且初始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=-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上运行时,可能会观察到与上面描述的不同的行为。

fykwrbwg

fykwrbwg3#

这个问题的具体性与一般的标题不太匹配,但我也有同样的问题,这个问题对我来说是谷歌的顶级结果,所以我会在这里回答,尽管这个问题的年龄。
总结一下这里其他答案的发现,似乎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);
  }
}

产生所需的结果。

hec6srdp

hec6srdp4#

我尝试了一个关于final字段的实验--首先用初始化器设置它们,例如:

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

GSON无法正确地反序列化它。
然后在构造函数中...

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

这是有效的。也许这是一个Java编译器优化,其中final基元或字符串类型的变量与初始化器被视为编译时常量,没有一个实际的变量被创建,而如果初始化是在构造器中完成的,一个变量被创建?

相关问题