使用 TypeAdapter 的对象中一个(多个)变量的 Gson 自定义序列化程序
Posted
技术标签:
【中文标题】使用 TypeAdapter 的对象中一个(多个)变量的 Gson 自定义序列化程序【英文标题】:Gson custom seralizer for one variable (of many) in an object using TypeAdapter 【发布时间】:2012-07-01 13:13:40 【问题描述】:我见过很多使用自定义 TypeAdapter 的简单示例。最有帮助的是Class TypeAdapter<T>
。但这还没有回答我的问题。
我想自定义对象中单个字段的序列化,并让默认的 Gson 机制处理其余部分。
出于讨论的目的,我们可以使用这个类定义作为我希望序列化的对象的类。我想让 Gson 序列化前两个类成员以及所有暴露的基类成员,并且我想对下面显示的第三个和最后一个类成员进行自定义序列化。
public class MyClass extends SomeClass
@Expose private HashMap<String, MyObject1> lists;
@Expose private HashMap<String, MyObject2> sources;
private LinkedHashMap<String, SomeClass> customSerializeThis;
[snip]
【问题讨论】:
【参考方案1】:这是一个很好的问题,因为它隔离了一些应该很容易但实际上需要大量代码的东西。
首先,编写一个摘要TypeAdapterFactory
,它为您提供了修改传出数据的钩子。此示例使用 Gson 2.2 中名为 getDelegateAdapter()
的新 API,允许您查找 Gson 默认使用的适配器。如果您只想调整标准行为,委托适配器非常方便。与完整的自定义类型适配器不同,它们会在您添加和删除字段时自动保持最新状态。
public abstract class CustomizedTypeAdapterFactory<C>
implements TypeAdapterFactory
private final Class<C> customizedClass;
public CustomizedTypeAdapterFactory(Class<C> customizedClass)
this.customizedClass = customizedClass;
@SuppressWarnings("unchecked") // we use a runtime check to guarantee that 'C' and 'T' are equal
public final <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type)
return type.getRawType() == customizedClass
? (TypeAdapter<T>) customizeMyClassAdapter(gson, (TypeToken<C>) type)
: null;
private TypeAdapter<C> customizeMyClassAdapter(Gson gson, TypeToken<C> type)
final TypeAdapter<C> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<C>()
@Override public void write(JsonWriter out, C value) throws IOException
JsonElement tree = delegate.toJsonTree(value);
beforeWrite(value, tree);
elementAdapter.write(out, tree);
@Override public C read(JsonReader in) throws IOException
JsonElement tree = elementAdapter.read(in);
afterRead(tree);
return delegate.fromJsonTree(tree);
;
/**
* Override this to muck with @code toSerialize before it is written to
* the outgoing JSON stream.
*/
protected void beforeWrite(C source, JsonElement toSerialize)
/**
* Override this to muck with @code deserialized before it parsed into
* the application type.
*/
protected void afterRead(JsonElement deserialized)
上面的类使用默认的序列化得到一棵JSON树(用JsonElement
表示),然后调用钩子方法beforeWrite()
让子类自定义那棵树。使用 afterRead()
进行反序列化也是如此。
接下来,我们将其子类化为特定的 MyClass
示例。为了说明,我将在序列化时向地图添加一个名为“大小”的合成属性。为了对称,我会在反序列化时将其删除。实际上,这可以是任何自定义。
private class MyClassTypeAdapterFactory extends CustomizedTypeAdapterFactory<MyClass>
private MyClassTypeAdapterFactory()
super(MyClass.class);
@Override protected void beforeWrite(MyClass source, JsonElement toSerialize)
JsonObject custom = toSerialize.getAsJsonObject().get("custom").getAsJsonObject();
custom.add("size", new JsonPrimitive(custom.entrySet().size()));
@Override protected void afterRead(JsonElement deserialized)
JsonObject custom = deserialized.getAsJsonObject().get("custom").getAsJsonObject();
custom.remove("size");
最后通过创建一个使用新类型适配器的自定义Gson
实例将它们组合在一起:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new MyClassTypeAdapterFactory())
.create();
Gson 的新 TypeAdapter 和 TypeAdapterFactory 类型非常强大,但它们也是抽象的,需要练习才能有效使用。希望这个示例对您有用!
【讨论】:
@Jesse 谢谢!如果没有你的帮助,我永远也不会明白这一点! 我无法使用私有 ctor 实例化new MyClassTypeAdapterFactory()
...
啊,对不起。我在一个文件中完成了所有这些操作。
该机制(beforeWrite 和 afterRead)应该是 GSon 核心的一部分。谢谢!
我正在使用 TypeAdapter 来避免由于相互引用而导致的无限循环。这是一个很好的机制,谢谢@Jesse,尽管我想问你是否有想法用这种机制实现相同的效果..我有想法,但我想听听您的意见..谢谢!【参考方案2】:
还有另一种方法。正如 Jesse Wilson 所说,这应该很容易。猜猜看,这很容易!
如果您为您的类型实现JsonSerializer
和JsonDeserializer
,您可以处理您想要的部分并委派给Gson 处理其他所有部分,只需要很少的代码。为方便起见,我从下面的@Perception's answer on another question 引用,有关详细信息,请参阅该答案:
在这种情况下,最好使用
JsonSerializer
而不是TypeAdapter
,原因很简单,序列化程序可以访问其序列化上下文。public class PairSerializer implements JsonSerializer<Pair> @Override public JsonElement serialize(final Pair value, final Type type, final JsonSerializationContext context) final JsonObject jsonObj = new JsonObject(); jsonObj.add("first", context.serialize(value.getFirst())); jsonObj.add("second", context.serialize(value.getSecond())); return jsonObj;
这样做的主要优点(除了避免复杂的变通方法)是您仍然可以利用可能已在主上下文中注册的其他类型适配器和自定义序列化程序。请注意,序列化程序和适配器的注册使用完全相同的代码。
但是,我承认如果您要经常修改 Java 对象中的字段,Jesse 的方法看起来会更好。这是易用性与灵活性之间的权衡,任您选择。
【讨论】:
这无法将value
上的所有其他字段委托给 gson【参考方案3】:
我的同事也提到了@JsonAdapter
注解的使用
https://google.github.io/gson/apidocs/com/google/gson/annotations/JsonAdapter.html
页面已移至此处:https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/annotations/JsonAdapter.html
例子:
private static final class Gadget
@JsonAdapter(UserJsonAdapter2.class)
final User user;
Gadget(User user)
this.user = user;
【讨论】:
这非常适合我的用例。非常感谢。 这是一个 WebArchive 链接,因为原版现已失效:web.archive.org/web/20180119143212/https://google.github.io/…以上是关于使用 TypeAdapter 的对象中一个(多个)变量的 Gson 自定义序列化程序的主要内容,如果未能解决你的问题,请参考以下文章
有没有办法使用 TypeAdapter 来序列化父类和子类实例
Dart Hive TypeAdapter 对 write() 方法的合理性