使用TypeAdapter对对象中的一个变量(多个变量)进行Gson自定义Seralizer


96

我已经看到了许多使用自定义TypeAdapter的简单示例。最有用的是Class TypeAdapter<T>。但这还没有回答我的问题。

我想自定义对象中单个字段的序列化,并让默认的Gson机制负责其余的工作。

出于讨论目的,我们可以使用此类定义作为我要序列化的对象的类。我想让Gson序列化基类的前两个类成员以及所有公开的成员,并且我想为下面显示的第3个和最后一个类成员进行自定义序列化。

public class MyClass extends SomeClass {

@Expose private HashMap<String, MyObject1> lists;
@Expose private HashMap<String, MyObject2> sources;
private LinkedHashMap<String, SomeClass> customSerializeThis;
    [snip]
}

Answers:


131

这是一个很好的问题,因为它隔离了一些应该很容易但实际上需要大量代码的东西。

首先,编写一个摘要TypeAdapterFactory,为您提供钩子以修改传出数据。本示例在Gson 2.2中使用了一个新的API,该API getDelegateAdapter()允许您查找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),然后调用hook方法beforeWrite()以允许子类自定义该树。与相同的反序列化afterRead()

接下来,我们将其子类化为特定MyClass示例。为了说明这一点,我将在序列化时向地图添加一个名为“ size”的综合属性。为了对称起见,我会在反序列化时将其删除。实际上,这可以是任何定制。

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的新TypeAdapterTypeAdapterFactory类型非常强大,但它们也很抽象,需要练习才能有效使用。希望您发现此示例有用!


@Jesse谢谢!如果没有您的帮助,我将永远无法解决!
MountainX

我无法new MyClassTypeAdapterFactory()用私有实例化实例化
MountainX

啊,对此感到抱歉。我把所有这些都放在一个文件中。
杰西·威尔逊

7
该机制(beforeWrite和afterRead)应该是GSon核心的一部分。谢谢!
梅勒妮

2
我正在使用TypeAdapter以避免由于相互引用而导致的无限循环..这是一个很棒的机制,谢谢@Jesse,尽管我想问一下您是否有使用此机制实现相同效果的想法。我想听听您的意见..谢谢!
Mohammed R. El-Khoudary 2014年

16

还有另一种方法。正如杰西·威尔逊(Jesse Wilson)所说,这应该很容易。猜猜看,这容易!

如果您实现JsonSerializerJsonDeserializer针对您的类型,则可以用很少的代码处理所需的部分并将其委托给Gson进行其他所有操作。为了方便起见,我在下面的另一个问题中引用@Perception的答案,请参见该答案以获取更多详细信息:

在这种情况下,最好使用a JsonSerializer而不是a 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的方法看起来会更好。选择易用性与灵活性之间的权衡。


1
这无法将所有其他字段委托value给gson
卫斯理

10

我的同事还提到了@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;
   }
 }

1
这对于我的用例来说效果很好。非常感谢。
Neoklosch

1
这里有一个WebArchive链接,因为原来现在死了:web.archive.org/web/20180119143212/https://google.github.io/...
浮动翻车鱼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.