【问题标题】:Deserializing polymorphic JSON with transient variables using Gson-extras使用 Gson-extras 反序列化具有瞬态变量的多态 JSON
【发布时间】:2020-05-05 16:28:47
【问题描述】:

我有一些我正在序列化/反序列化的多态 Java 类。我正在使用 Gson-extras 中的 RuntimeTypeAdapterFactory 来确保类正确序列化和反序列化,并将“类型”字段设置为派生类的名称。它工作正常。

我有一些其他的类,我正在序列化/反序列化,它们有一些应用了瞬态修饰符的变量。我正在使用同样来自 Gson-extras 的 PostConstructAdapterFactory 将 PostConstruct 方法添加到我的类中,以便可以在反序列化完成后和调用类中的任何函数之前重新分配瞬态变量的值。这也很好用。

我面临的挑战是我有另一组多态类,也有瞬态变量,我希望在反序列化后执行 PostConstruct 方法。我所有同时使用 RuntimeTypeAdapterFactory 和 PostConstructAdapterFactory 的尝试都失败了。我可以注册两者,但是当我这样做时, PostConstructAdapterFactory 行为似乎覆盖了 RuntimeTypeAdapterFactory 行为,导致我的类不再存储多态类所需的“类型”字段。

似乎我可以选择其中之一,但不能同时拥有。我什至考虑编写一个具有两种适配器工厂功能的混合适配器工厂类,但也没有成功。

我觉得我可能在这里遗漏了一些关于这些设计方式的明显内容。当然可以让我的课程同时使用这两个功能吗?下面是一个简单的例子来说明我在说什么。

public class BaseClass {
    private static final RuntimeTypeAdapterFactory<BaseClass> ADAPTER = RuntimeTypeAdapterFactory.of(BaseClass.class);
    private static final HashSet<Class<?>> REGISTERED_CLASSES= new HashSet<Class<?>>();
    static { GsonUtils.registerType(ADAPTER); }

    private synchronized void registerClass() {
        if (!REGISTERED_CLASSES.contains(this.getClass())) {
            REGISTERED_CLASSES.add(this.getClass());
            ADAPTER.registerSubtype(this.getClass());
        }
    }

    public BaseClass() {
        registerClass();
    }
}

public class DerivedClass extends BaseClass {
   public DerivedClass(Integer number) {
       super();
       this.number = number;
       Init();
   }

   protected Integer number;
   protected transient Integer doubled;
   protected transient Integer tripled;
   protected transient Integer halved;
   protected transient Integer squared;
   protected transient Integer cubed;

   public void Init() {
       halved = number / 2;
       doubled = number * 2;
       tripled = number * 3;
       squared = number * number;
       cubed = number * number * number;
   }

   @PostConstruct
   private void postConstruct() {
       Init();
   }

}

public class GsonUtils {
    private static final GsonBuilder gsonBuilder = new GsonBuilder().registerTypeAdapterFactory(new PostConstructAdapterFactory());

    public static void registerType(RuntimeTypeAdapterFactory<?> adapter) {
        gsonBuilder.registerTypeAdapterFactory(adapter);
    }

    public static Gson getGson() {
        return gsonBuilder.create();
    }    
}

Gson gson = GsonUtils.getGson(); 
String serialized = gson.toJson(new DerivedClass(6), BaseClass.class);
DerivedClass dc = gson.fromJson(serialized, DerivedClass.class);

更新:感谢@michał-ziober 的回答。照你说的做确实有效。它确实回答了这个问题,但是它仍然不能完全解决我的问题。让我稍微修改一下这个问题。

我的代码的简化版本可能有点太简单了。当我尝试将 DerivedClass 序列化/反序列化为另一个类的属性时,我的原始问题被突出显示。如下:

public class WrapperClass {
    protected BaseClass dc = new DerivedClass(6);
}

Gson gson = GsonUtils.getGson(); 
String serialized = gson.toJson(new WrapperClass());
WrapperClass wc = gson.fromJson(serialized, WrapperClass.class);

在这种情况下,只要我的 DerivedClass 没有定义 PostConstruct 方法,DerivedClass 的序列化/反序列化就可以正常工作。但如果是这样,“类型”属性不会写入文件。

重申一下,如果我直接序列化/反序列化 DerivedClass,一切都很好,但如果它在 WrapperClass 中并且我序列化/反序列化 WrapperClass 并且如果我定义了 PostConstruct 方法,则它不起作用。

【问题讨论】:

    标签: java serialization polymorphism gson postconstruct


    【解决方案1】:

    使用类型适配器一切都很好,您可以同时使用它们。在您的情况下,问题在于执行代码的顺序。

    1. GsonUtils.getGson() - 使用适配器创建 Gson 对象。
    2. gson.toJson(new DerivedClass(6), BaseClass.class) - 您创建 DerivedClass 实例,该实例从 BaseClass 执行超级构造函数,该构造函数在适配器中注册给定类(原文如此!)。

    试试这个代码:

    DerivedClass src = new DerivedClass(6);
    Gson gson1 = GsonUtils.getGson();
    String serialized = gson1.toJson(src, BaseClass.class);
    System.out.println(serialized);
    DerivedClass dc = gson1.fromJson(serialized, DerivedClass.class);
    

    它将按预期工作。创建DerivedClass 有一个副作用——这是一个很好的例子,为什么类应该相互分离。 BaseClass 中不应有任何 Gson 特定类。

    在最好的世界里BaseClass 应该只包含通用属性和一些基本逻辑:

    class BaseClass {
        // getters, setters, business logic.
    }
    

    所有Gson的配置都应该放在GsonUtils类中:

    class GsonUtils {
    
        public static Gson getGson() {
            return new GsonBuilder()
                    .registerTypeAdapterFactory(RuntimeTypeAdapterFactory.of(BaseClass.class)
                            .registerSubtype(DerivedClass.class))
                    .registerTypeAdapterFactory(new PostConstructAdapterFactory())
                    .create();
        }
    }
    

    如果您不想手动指定所有类,则需要在运行时扫描环境。看看:At runtime, find all classes in a Java application that extend a base class.

    问题更新后编辑

    看起来TypeAdapterFactory 链接并不像我预期的那么简单。这取决于给定的TypeAdapterFactory 实现是返回null 还是返回带有一些委托给其他工厂的新对象。

    我找到了一种解决方法,但在实际项目中可能并不容易使用:

    1. 我创建了两个Gson 对象:一个用于序列化,一个用于反序列化。仅用于反序列化过程,我们注册PostConstructAdapterFactory
    2. 我们向BaseClass添加一个带有@PostConstruct注解的方法。

    例子:

    import com.google.gson.Gson;
    import com.google.gson.GsonBuilder;
    import com.google.gson.typeadapters.PostConstructAdapterFactory;
    import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;
    
    import javax.annotation.PostConstruct;
    
    public class GsonApp {
    
        public static void main(String[] args) {
            RuntimeTypeAdapterFactory<BaseClass> typeFactory = RuntimeTypeAdapterFactory.of(BaseClass.class)
                    .registerSubtype(DerivedClass.class);
    
            Gson serializer = new GsonBuilder()
                    .registerTypeAdapterFactory(typeFactory)
                    .create();
    
            Gson deserializer = new GsonBuilder()
                    .registerTypeAdapterFactory(typeFactory)
                    .registerTypeAdapterFactory(new PostConstructAdapterFactory())
                    .create();
    
            WrapperClass wrapper = new WrapperClass();
            wrapper.setDc(new DerivedClass(8));
    
            String json = serializer.toJson(wrapper);
            System.out.println(json);
            System.out.println(deserializer.fromJson(json, WrapperClass.class));
        }
    
    }
    
    class WrapperClass {
        protected BaseClass dc;
    
        public BaseClass getDc() {
            return dc;
        }
    
        public void setDc(BaseClass dc) {
            this.dc = dc;
        }
    
        @Override
        public String toString() {
            return "WrapperClass{" +
                    "dc=" + dc +
                    '}';
        }
    }
    
    class BaseClass {
    
        @PostConstruct
        protected void postConstruct() {
        }
    }
    
    class DerivedClass extends BaseClass {
        public DerivedClass(Integer number) {
            super();
            this.number = number;
            Init();
        }
    
        protected Integer number;
        protected transient Integer doubled;
        protected transient Integer tripled;
        protected transient Integer halved;
        protected transient Integer squared;
        protected transient Integer cubed;
    
        public void Init() {
            halved = number / 2;
            doubled = number * 2;
            tripled = number * 3;
            squared = number * number;
            cubed = number * number * number;
        }
    
        @PostConstruct
        protected void postConstruct() {
            Init();
        }
    
        @Override
        public String toString() {
            return "DerivedClass{" +
                    "number=" + number +
                    ", doubled=" + doubled +
                    ", tripled=" + tripled +
                    ", halved=" + halved +
                    ", squared=" + squared +
                    ", cubed=" + cubed +
                    "} ";
        }
    }
    

    上面的代码打印:

    {"dc":{"type":"DerivedClass","number":8}}
    WrapperClass{dc=DerivedClass{number=8, doubled=16, tripled=24, halved=4, squared=64, cubed=512} }
    

    【讨论】:

    • 嗨@michał-ziober。谢谢你的回答。我修改了所有内容以按照您的方式工作,并且它确实适用于简单的示例。然后我尝试了我的旧方法,它也适用于简单的示例,我猜这有点太简单了。虽然回到你的方式,它可以工作,但是,如果我将 DerivedClass 包装到另一个类中并序列化它,它就不起作用。我已经修改了问题以反映这一点。有什么想法吗?
    • @johndoe - 我更新了问题,但在您的情况下找不到任何简单的解决方案。
    • 太好了,谢谢。您的解决方法完全适合我的问题。拥有一个单独的序列化器和反序列化器一点也不方便。在您看来,这是 gson 中的错误吗?我觉得是这样,因为它对单个对象按预期工作,但在嵌套在另一个类中时则不然。
    • @johndoe,可能不是错误。这就是RuntimeTypeAdapterFactoryPostConstructAdapterFactory 工厂的内部实现如何协同工作。方法create,它们都实现了某些类型的返回委托,而null 则用于其他类型,这就是链接被破坏的地方。我认为POJO 是它们之间的传递,但实际上并非如此。有关更多信息,我必须调试此代码并测试所有情况,但如果解决方法在那里有效,则无需这样做。
    猜你喜欢
    • 2015-10-18
    • 2022-01-15
    • 1970-01-01
    • 1970-01-01
    • 2017-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多