2011-12-02 17 views
3

我使用gson反序列化一个小部件层次结构,但是在反序列化最终字段时出现问题。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自定义串并转换器一个屏幕,如下所示。屏幕中的'名称'设置正确,AWidget中的'名称'保持为空。

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)

+0

为什么要反序列化到最后一个字段?最后的意思是声明这不会改变,所以你真的好像在嘲弄你自己的模型。 – aishwarya

+3

我想要不可变的对象。我不会创建这些对象,除非从gson解析它们,并且之后不需要更改它们。在我的理解中,不变性总是更好。 –

+0

是的,但是你可以让它们变成私人的,并且不提供setter - 这可以达到同样的目的吗? – aishwarya

回答

12

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

JLS 17.5.3 Subsequent Modification of final Fields

在一些例如反序列化的情况下,系统将需要在构建后更改对象的最终字段。最终字段可以通过反射和其他实现相关手段进行更改。其中有合理的语义的唯一模式是在其中构建对象,然后更新对象的最终字段。对象不应该对其他线程可见,也不应该读取最终字段,直到完成对对象最终字段的所有更新。最终字段的冻结发生在最终字段被设置的构造函数的末尾,并且在通过反射或其他特殊机制每次修改最终字段之后立即出现。

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

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

5

这是一个有点难以解决在原来的问题确切的问题,因为完整的最少的代码和例子是不提供。在反序列化过程中,对Gson如何实例化目标对象的下列理解可能有所帮助。

Gson 1.7.1和2.0都考虑了香草反序列化操作中的最终字段赋值,并且初始的最终字段赋值没有改变。例如:

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); 
    } 
} 

总之,香草反序列化过程,这是无法从传入的JSON重新分配与任何数据的最后字段赋值,虽然它可能出现如果最终字段分配在一个发生是可能用户定义的构造函数**

**我不确定JVM规范是否允许某些实现宽大处理,以便在不同的JVM上运行时可能会观察到上述不同的行为。

+0

我发现了更多的行为。如果我有一个字段是最终的,并分配给null(无构造函数),gson将填充该字段。如果它是一个原始类型或分配给其他的东西,比如一个字符串,gson不会碰它。 –

+0

Ack。然后Gson甚至比我猜测的更不一致。 –

1

我尝试过的实验最终场 - 首先设定他们initialisers,例如:

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

GSON将无法正常deserialise这一点。

,然后在构造函数...

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

这个工作。也许这是一个Java编译器优化,其中带有初始化程序的最终原始类型或字符串类型变量被视为conmpile-time常量,没有创建实际变量,而如果在构造函数中完成初始化,则会创建一个变量?

4

问题的具体性与通用标题不太一致,但我遇到同样的问题,问题在于我对Google的评分最高,所以我会尽量回答问题的年龄。

总之这里的其他答案的调查结果,似乎过去和现在GSON 的版本将 deserialise对象,但不会 deserialise元如果他们一直在现场初始化。

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 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); 
    } 
} 

产生所期望的结果

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; 
    } 
} 

稍微调整的例子的变体。

+0

“TypeAdaptor”的链接已损坏 – Ferrybig