java gson - 如何在序列化任何类型的对象时包含类名属性
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/39999278/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
gson - How to include class name property when serializing object of any type
提问by gromit190
Came to realize I need to include the class name as a property when serializing an object in my application. It would probably be best if I added the class name property for any non-primitive object that is serialized.
意识到在我的应用程序中序列化对象时,我需要将类名作为属性包含在内。如果我为任何序列化的非原始对象添加类名属性,那可能是最好的。
I saw that this is a built-in feature in Genson with the useClassMetadata
method. But I'm already using gson in my project, so it would be beneficial if I could stick with it.
我用这个useClassMetadata
方法看到这是 Genson 的一个内置功能。但是我已经在我的项目中使用了 gson,所以如果我能坚持下去会很有好处。
This is my current attempt:
这是我目前的尝试:
package com.mycompany.javatest;
import com.google.gson.*;
import java.lang.reflect.*;
public class JavaTest {
public static class GenericSerializer implements JsonSerializer<Object>, JsonDeserializer<Object> {
private static final String CLASS_PROPERTY_NAME = "class";
@Override
public JsonElement serialize(Object src, Type typeOfSrc,
JsonSerializationContext context) {
JsonElement retValue = context.serialize(src);
if (retValue.isJsonObject()) {
retValue.getAsJsonObject().addProperty(CLASS_PROPERTY_NAME, src.getClass().getName());
}
return retValue;
}
@Override
public Object deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
Class actualClass;
if (json.isJsonObject()) {
JsonObject jsonObject = json.getAsJsonObject();
String className = jsonObject.get(CLASS_PROPERTY_NAME).getAsString();
try {
actualClass = Class.forName(className);
}
catch (ClassNotFoundException e) {
e.printStackTrace();
throw new JsonParseException(e.getMessage());
}
}
else {
actualClass = typeOfT.getClass();
}
return context.deserialize(json, actualClass);
}
}
public static class MyClass {
private final String name = "SpongePants SquareBob";
}
public static void main(String[] args) {
MyClass obj = new MyClass();
GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapter(Object.class, new GenericSerializer());
Gson gson = gb.create();
System.out.println(gson.toJson(obj, Object.class));
}
}
Prints
印刷
{"name":"SpongePants SquareBob"}
I want it to print
我想要它打印
{"name":"SpongePants SquareBob","class":"com.mycompany.javatest$MyClass"}
EDIT:Another attempt (this time using GsonFire)
编辑:另一次尝试(这次使用 GsonFire)
package com.mycompany.javatest;
import com.google.gson.*;
import io.gsonfire.*;
public class JavaTest {
public static class DummyData {
private final String someData = "1337";
}
private static final String CLASS_PROPERTY_NAME = "class";
public static void main(String[] args) {
GsonFireBuilder gfb = new GsonFireBuilder();
gfb.registerPostProcessor(Object.class, new PostProcessor<Object>() {
@Override
public void postDeserialize(Object t, JsonElement je, Gson gson) {
// Ignore
}
@Override
public void postSerialize(JsonElement je, Object t, Gson gson) {
if (je.isJsonObject()) {
je.getAsJsonObject().add(CLASS_PROPERTY_NAME, new JsonPrimitive(t.getClass().getTypeName()));
}
}
});
gfb.registerTypeSelector(Object.class, (JsonElement je) -> {
System.out.println(je);
if (je.isJsonObject()) {
try {
return Class.forName(je.getAsJsonObject().get(CLASS_PROPERTY_NAME).getAsString());
}
catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
}
return null;
});
Gson gson = gfb.createGson();
DummyData dd = new DummyData();
String json = gson.toJson(dd);
System.out.println(json);
DummyData dd2 = (DummyData) gson.fromJson(json, Object.class); // <-- gives me a ClassCastException
}
}
采纳答案by pandaadb
Yet another answer. It took a bit longer.
还有一个答案。花了更长的时间。
Side comment: The above solution would work if you would recursively use reflection to work out the fields in your class. Then serialise those with the special serialiser, while using a separate one for the parent object. This would avoid the stackoverflow.
旁注:如果您递归地使用反射来计算类中的字段,则上述解决方案将起作用。然后使用特殊的序列化器序列化那些,同时为父对象使用一个单独的序列化器。这将避免计算器溢出。
Having said that - i am a lazy developer, so I like to do things lazy. I am adapting a google solution for you.
话虽如此 - 我是一个懒惰的开发人员,所以我喜欢做懒惰的事情。我正在为您调整谷歌解决方案。
NOTE: PLEASE TEST THIS AND ADAPT IT TO YOUR NEEDS. THIS IS A PROTOTYPE AND I HAVE NOT CLEANED UP UNNECESSARY CODE OR CHECKED FOR POSSIBLE ISSUES>
注意:请对此进行测试并根据您的需要进行调整。这是一个原型,我没有清理不必要的代码或检查可能的问题>
The original source of the code:
代码的原始来源:
So, this is based on the RuntimeTypeAdapterFactory
. This factory is provided by google and its purpose is to support hierarchical deserialisation. To do this, you would register a base class and ALL subclasses, with a property that you would like to add as an identifier. If you read the javadocs, this would get much clearer.
因此,这是基于RuntimeTypeAdapterFactory
. 该工厂由 google 提供,其目的是支持分层反序列化。为此,您需要注册一个基类和所有子类,并使用您希望添加为标识符的属性。如果您阅读 javadocs,这将变得更加清晰。
This obviously offers us the thing that we want: recursively register different adapters for class types that can handle these, while NOT running in circles and causing a stackoverflow. With one important issue: you have to register ALLsubclasses. This is obviously not suitable (though one might argue you could you classpath resolution and simply add all your classes at startup once to be able to use this everywhere). So I looked into the source and changed the code to do this dynamically. Note that google warns against doing it - use it on your own terms :)
这显然为我们提供了我们想要的东西:为可以处理这些的类类型递归注册不同的适配器,同时不会循环运行并导致堆栈溢出。有一个重要问题:您必须注册所有子类。这显然不合适(尽管有人可能会争辩说您可以解析类路径并在启动时简单地添加所有类以便能够在任何地方使用它)。所以我查看了源代码并更改了代码以动态执行此操作。请注意,谷歌警告不要这样做 - 根据您自己的条件使用它:)
Here is my Factory:
这是我的工厂:
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.Streams;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
/**
* Adapts values whose runtime type may differ from their declaration type. This
* is necessary when a field's type is not the same type that GSON should create
* when deserializing that field. For example, consider these types:
* <pre> {@code
* abstract class Shape {
* int x;
* int y;
* }
* class Circle extends Shape {
* int radius;
* }
* class Rectangle extends Shape {
* int width;
* int height;
* }
* class Diamond extends Shape {
* int width;
* int height;
* }
* class Drawing {
* Shape bottomShape;
* Shape topShape;
* }
* }</pre>
* <p>Without additional type information, the serialized JSON is ambiguous. Is
* the bottom shape in this drawing a rectangle or a diamond? <pre> {@code
* {
* "bottomShape": {
* "width": 10,
* "height": 5,
* "x": 0,
* "y": 0
* },
* "topShape": {
* "radius": 2,
* "x": 4,
* "y": 1
* }
* }}</pre>
* This class addresses this problem by adding type information to the
* serialized JSON and honoring that type information when the JSON is
* deserialized: <pre> {@code
* {
* "bottomShape": {
* "type": "Diamond",
* "width": 10,
* "height": 5,
* "x": 0,
* "y": 0
* },
* "topShape": {
* "type": "Circle",
* "radius": 2,
* "x": 4,
* "y": 1
* }
* }}</pre>
* Both the type field name ({@code "type"}) and the type labels ({@code
* "Rectangle"}) are configurable.
*
* <h3>Registering Types</h3>
* Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field
* name to the {@link #of} factory method. If you don't supply an explicit type
* field name, {@code "type"} will be used. <pre> {@code
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory
* = RuntimeTypeAdapterFactory.of(Shape.class, "type");
* }</pre>
* Next register all of your subtypes. Every subtype must be explicitly
* registered. This protects your application from injection attacks. If you
* don't supply an explicit type label, the type's simple name will be used.
* <pre> {@code
* shapeAdapter.registerSubtype(Rectangle.class, "Rectangle");
* shapeAdapter.registerSubtype(Circle.class, "Circle");
* shapeAdapter.registerSubtype(Diamond.class, "Diamond");
* }</pre>
* Finally, register the type adapter factory in your application's GSON builder:
* <pre> {@code
* Gson gson = new GsonBuilder()
* .registerTypeAdapterFactory(shapeAdapterFactory)
* .create();
* }</pre>
* Like {@code GsonBuilder}, this API supports chaining: <pre> {@code
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
* .registerSubtype(Rectangle.class)
* .registerSubtype(Circle.class)
* .registerSubtype(Diamond.class);
* }</pre>
*/
public final class RuntimeClassNameTypeAdapterFactory<T> implements TypeAdapterFactory {
private final Class<?> baseType;
private final String typeFieldName;
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>();
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>();
private RuntimeClassNameTypeAdapterFactory(Class<?> baseType, String typeFieldName) {
if (typeFieldName == null || baseType == null) {
throw new NullPointerException();
}
this.baseType = baseType;
this.typeFieldName = typeFieldName;
}
/**
* Creates a new runtime type adapter using for {@code baseType} using {@code
* typeFieldName} as the type field name. Type field names are case sensitive.
*/
public static <T> RuntimeClassNameTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
return new RuntimeClassNameTypeAdapterFactory<T>(baseType, typeFieldName);
}
/**
* Creates a new runtime type adapter for {@code baseType} using {@code "type"} as
* the type field name.
*/
public static <T> RuntimeClassNameTypeAdapterFactory<T> of(Class<T> baseType) {
return new RuntimeClassNameTypeAdapterFactory<T>(baseType, "class");
}
/**
* Registers {@code type} identified by {@code label}. Labels are case
* sensitive.
*
* @throws IllegalArgumentException if either {@code type} or {@code label}
* have already been registered on this type adapter.
*/
public RuntimeClassNameTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
if (type == null || label == null) {
throw new NullPointerException();
}
if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
throw new IllegalArgumentException("types and labels must be unique");
}
labelToSubtype.put(label, type);
subtypeToLabel.put(type, label);
return this;
}
/**
* Registers {@code type} identified by its {@link Class#getSimpleName simple
* name}. Labels are case sensitive.
*
* @throws IllegalArgumentException if either {@code type} or its simple name
* have already been registered on this type adapter.
*/
public RuntimeClassNameTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
return registerSubtype(type, type.getSimpleName());
}
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
final Map<String, TypeAdapter<?>> labelToDelegate
= new LinkedHashMap<String, TypeAdapter<?>>();
final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate
= new LinkedHashMap<Class<?>, TypeAdapter<?>>();
// && !String.class.isAssignableFrom(type.getRawType())
if(Object.class.isAssignableFrom(type.getRawType()) ) {
TypeAdapter<?> delegate = gson.getDelegateAdapter(this, type);
labelToDelegate.put("class", delegate);
subtypeToDelegate.put(type.getRawType(), delegate);
}
// for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
// TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
// labelToDelegate.put(entry.getKey(), delegate);
// subtypeToDelegate.put(entry.getValue(), delegate);
// }
return new TypeAdapter<R>() {
@Override public R read(JsonReader in) throws IOException {
JsonElement jsonElement = Streams.parse(in);
JsonElement labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
if (labelJsonElement == null) {
throw new JsonParseException("cannot deserialize " + baseType
+ " because it does not define a field named " + typeFieldName);
}
String label = labelJsonElement.getAsString();
@SuppressWarnings("unchecked") // registration requires that subtype extends T
TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
if (delegate == null) {
throw new JsonParseException("cannot deserialize " + baseType + " subtype named "
+ label + "; did you forget to register a subtype?");
}
return delegate.fromJsonTree(jsonElement);
}
@Override public void write(JsonWriter out, R value) throws IOException {
Class<?> srcType = value.getClass();
String label = srcType.getName();
@SuppressWarnings("unchecked") // registration requires that subtype extends T
TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
if (delegate == null) {
throw new JsonParseException("cannot serialize " + srcType.getName()
+ "; did you forget to register a subtype?");
}
JsonElement jsonTree = delegate.toJsonTree(value);
if(jsonTree.isJsonPrimitive()) {
Streams.write(jsonTree, out);
} else {
JsonObject jsonObject = jsonTree.getAsJsonObject();
if (jsonObject.has(typeFieldName)) {
throw new JsonParseException("cannot serialize " + srcType.getName()
+ " because it already defines a field named " + typeFieldName);
}
JsonObject clone = new JsonObject();
clone.add(typeFieldName, new JsonPrimitive(label));
for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
clone.add(e.getKey(), e.getValue());
}
Streams.write(clone, out);
}
}
}.nullSafe();
}
}
I have added ALL imports for you. This is not (really) published in maven central, though you could find it here: https://mvnrepository.com/artifact/org.danilopianini/gson-extras/0.1.0
我已经为你添加了所有进口。这不是(真的)发布在 Maven 中心,尽管你可以在这里找到它:https: //mvnrepository.com/artifact/org.danilopianini/gson-extras/0.1.0
Regardless you would have to make adaptions to get this working for you, so I made a copy. The copy fully compiles and you can simply paste it into your code and save yourself the extra dependency.
无论如何,您必须进行调整才能使其适合您,所以我制作了一份副本。该副本完全编译,您可以简单地将其粘贴到您的代码中,并为自己节省额外的依赖项。
The important bits of this code are as follows: (and I have purposely left them in but commented out so you can tell)
这段代码的重要部分如下:(我故意把它们留在里面,但注释掉了,这样你就知道了)
in create(Gson gson, TypeToken<R> type)
在 create(Gson gson, TypeToken<R> type)
Check if the raw type is assignable from the String class. You want this to be applied to every class object, so this takes care of that. Note the code before that would look up if the type is registered with the class - not needed anymore (accordingly the variables wouldn't be needed; you should clean up the code)
检查原始类型是否可从 String 类分配。您希望将其应用于每个类对象,因此这会解决这个问题。请注意之前的代码会查找该类型是否已在类中注册 - 不再需要(因此不需要变量;您应该清理代码)
in @Override public void write(JsonWriter out, R value) throws IOException {
:
在@Override public void write(JsonWriter out, R value) throws IOException {
:
First, we get rid of the label. Our label is and will always be the name of the source type. This is done in:
首先,我们去掉标签。我们的标签是并将永远是源类型的名称。这是在:
String label = srcType.getName();
String label = srcType.getName();
Second, we have to make a distinction between primitive and object types. Primitive types are Strings, Integers etc in the Gson world. This means that our check above (adding an adapter) does not catch the fact that these Object types are in deed primitive types. So we do:
其次,我们必须区分原始类型和对象类型。Gson 世界中的原始类型是字符串、整数等。这意味着我们上面的检查(添加适配器)没有发现这些 Object 类型实际上是原始类型的事实。所以我们这样做:
if(jsonTree.isJsonPrimitive()) {
Streams.write(jsonTree, out);
This takes care of that. If it is primitive, just write the tree into the stream. If it is not, we then write all other fields ANDthe class field into it.
这会照顾到这一点。如果它是原始的,只需将树写入流中。如果不是,我们然后将所有其他字段和类字段写入其中。
JsonObject clone = new JsonObject();
clone.add(typeFieldName, new JsonPrimitive(label));
for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
clone.add(e.getKey(), e.getValue());
}
Streams.write(clone, out);
Fewww - finally this now takes care of that. And here is the example to prove my code does what (I believe) you want it to do ;)
Fewww - 现在终于解决了这个问题。这是证明我的代码执行(我相信)您希望它执行的操作的示例;)
public class GsonClassNameTest {
static Gson create = new GsonBuilder().registerTypeAdapterFactory(RuntimeClassNameTypeAdapterFactory.of(Object.class)).create();
public static void main(String[] args) {
String json = create.toJson(new X());
System.out.println(json);
}
public static class X {
public String test = "asd";
public int xyz = 23;
public Y y_class = new Y();
}
public static class Y {
String yTest = "asd2";
Z zTest = new Z();
}
public static class Z {
long longVal = 25;
double doubleTest = 2.4;
}
}
This now outputs this json for you:
现在为您输出这个json:
{
"class":"google.GsonClassNameTest$X",
"test":"asd",
"xyz":23,
"y_class":{
"class":"google.GsonClassNameTest$Y",
"yTest":"asd2",
"zTest":{
"class":"google.GsonClassNameTest$Z",
"longVal":25,
"doubleTest":2.4
}
}
}
As you can see, Strings, Longs, integers are correctly created. Each class object recursivley got it's classname as well.
如您所见,正确创建了字符串、长整型和整数。每个类对象 recursivley 也得到了它的类名。
This is a generic approach and should work with everything you create. However, if you decide to take this, do me a favour and write a few unit tests ;) Like I mentioned before, I prototyped this implementation.
这是一种通用方法,应该适用于您创建的所有内容。但是,如果您决定接受这个,请帮我一个忙并编写一些单元测试 ;) 就像我之前提到的,我为这个实现做了原型。
Hope that gets me a tick :)
希望能让我打勾:)
Regards,
问候,
Artur
阿图尔
回答by pandaadb
Just tried this myself and this seems to work:
我自己试过这个,这似乎有效:
public class GsonClassNameTest {
public static void main(String[] args) {
Gson create = new GsonBuilder().registerTypeHierarchyAdapter(Object.class, new ODeserialiser()).create();
String json = create.toJson(new X());
System.out.println(json);
}
public static class ODeserialiser implements JsonSerializer<Object> {
@Override
public JsonElement serialize(Object src, Type typeOfSrc, JsonSerializationContext context) {
Gson gson = new Gson();
JsonElement serialize = gson.toJsonTree(src);
JsonObject o = (JsonObject) serialize;
o.addProperty("class", src.getClass().getName());
return serialize;
}
}
public static class X {
public String test = "asd";
}
}
This prints:
这打印:
{"test":"asd","class":"google.GsonClassNameTest$X"}
details:
细节:
You have to register a Hierarchy adapter, so that if you register it with the Object class, it will be called for any type you pass into it.
你必须注册一个 Hierarchy 适配器,这样如果你用 Object 类注册它,它就会被你传递给它的任何类型调用。
You also have to use a different Gson instance within the custom Serializer, otherwise you just keep running in circles and get a Stackoverflow.
您还必须在自定义序列化程序中使用不同的 Gson 实例,否则您只会继续循环运行并获得 Stackoverflow。
Other than that, pretty straight forward :)
除此之外,非常直接:)
Note: I have rather little experience with gson, so there may be a cooler solution to this.
注意:我对 gson 的经验很少,所以可能有一个更酷的解决方案。
Regards,
问候,
Artur
阿图尔
回答by gromit190
Accepted @padaadb's answer but just wanted to paste the code I'm using. It takes care of serializing with type and de-serializing into the proper subtybe:
接受@padaadb 的回答,但只是想粘贴我正在使用的代码。它负责使用类型序列化并反序列化为适当的子类型:
package com.mycompany.javatest;
import com.google.gson.*;
import java.lang.reflect.*;
import org.junit.*;
public class JavaTest {
public static class GenericSerializer implements JsonSerializer<Object>, JsonDeserializer<Object> {
private static final String CLASS_PROPERTY_NAME = "class";
private final Gson gson;
public GenericSerializer() {
gson = new Gson();
}
public GenericSerializer(Gson gson) {
this.gson = gson;
}
@Override
public Object deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
Class actualClass;
if (json.isJsonObject()) {
JsonObject jsonObject = json.getAsJsonObject();
String className = jsonObject.get(CLASS_PROPERTY_NAME).getAsString();
try {
actualClass = Class.forName(className);
}
catch (ClassNotFoundException e) {
e.printStackTrace();
throw new JsonParseException(e.getMessage());
}
}
else {
actualClass = typeOfT.getClass();
}
return gson.fromJson(json, actualClass);
}
@Override
public JsonElement serialize(Object src, Type typeOfSrc,
JsonSerializationContext context) {
JsonElement retValue = gson.toJsonTree(src);
if (retValue.isJsonObject()) {
retValue.getAsJsonObject().addProperty(CLASS_PROPERTY_NAME, src.getClass().getName());
}
return retValue;
}
}
public static void main(String[] args) {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeHierarchyAdapter(Object.class, new GenericSerializer());
Gson gson = builder.create();
SomeSuperClass x = new SomeSubClass();
String json = gson.toJson(x);
SomeSuperClass y = gson.fromJson(json, SomeSuperClass.class); // Usually, y would now be of type SomeSuperClass
Assert.assertEquals(x.getClass(), y.getClass()); // y is actually of type SomeSubClass (!)
System.out.println("y.getClass()= " + y.getClass());
}
public static class SomeSuperClass {
}
public static class SomeSubClass extends SomeSuperClass {
private final String someMember = "12345";
}
}
回答by Alix
pandaadb's amazing answer wasn't completely working for me since it does not handle arrays/lists and there was a problem with deserialization, so I made a couple of changes:
pandaadb的惊人答案并不完全适合我,因为它不处理数组/列表并且反序列化存在问题,因此我进行了一些更改:
package org.ctbto.osi.fieldapp.util.gson;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.Streams;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* <p>
* Disclaimer: taken from here https://stackoverflow.com/a/40133286/285091 with some modifications
* </p>
*
* Adapts values whose runtime type may differ from their declaration type. This
* is necessary when a field's type is not the same type that GSON should create
* when deserializing that field. For example, consider these types:
* <pre> {@code
* abstract class Shape {
* int x;
* int y;
* }
* class Circle extends Shape {
* int radius;
* }
* class Rectangle extends Shape {
* int width;
* int height;
* }
* class Diamond extends Shape {
* int width;
* int height;
* }
* class Drawing {
* Shape bottomShape;
* Shape topShape;
* }
* }</pre>
* <p>Without additional type information, the serialized JSON is ambiguous. Is
* the bottom shape in this drawing a rectangle or a diamond? <pre> {@code
* {
* "bottomShape": {
* "width": 10,
* "height": 5,
* "x": 0,
* "y": 0
* },
* "topShape": {
* "radius": 2,
* "x": 4,
* "y": 1
* }
* }}</pre>
* This class addresses this problem by adding type information to the
* serialized JSON and honoring that type information when the JSON is
* deserialized: <pre> {@code
* {
* "bottomShape": {
* "type": "Diamond",
* "width": 10,
* "height": 5,
* "x": 0,
* "y": 0
* },
* "topShape": {
* "type": "Circle",
* "radius": 2,
* "x": 4,
* "y": 1
* }
* }}</pre>
* Both the type field name ({@code "type"}) and the type labels ({@code
* "Rectangle"}) are configurable.
* <p>
* <h3>Registering Types</h3>
* Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field
* name to the {@link #of} factory method. If you don't supply an explicit type
* field name, {@code "type"} will be used. <pre> {@code
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory
* = RuntimeTypeAdapterFactory.of(Shape.class, "type");
* }</pre>
* Next register all of your subtypes. Every subtype must be explicitly
* registered. This protects your application from injection attacks. If you
* don't supply an explicit type label, the type's simple name will be used.
* <pre> {@code
* shapeAdapter.registerSubtype(Rectangle.class, "Rectangle");
* shapeAdapter.registerSubtype(Circle.class, "Circle");
* shapeAdapter.registerSubtype(Diamond.class, "Diamond");
* }</pre>
* Finally, register the type adapter factory in your application's GSON builder:
* <pre> {@code
* Gson gson = new GsonBuilder()
* .registerTypeAdapterFactory(shapeAdapterFactory)
* .create();
* }</pre>
* Like {@code GsonBuilder}, this API supports chaining: <pre> {@code
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
* .registerSubtype(Rectangle.class)
* .registerSubtype(Circle.class)
* .registerSubtype(Diamond.class);
* }</pre>
*/
public final class RuntimeClassNameTypeAdapterFactory<T> implements TypeAdapterFactory {
private final Class<?> baseType;
private final String typeFieldName;
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>();
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>();
private RuntimeClassNameTypeAdapterFactory(Class<?> baseType, String typeFieldName) {
if (typeFieldName == null || baseType == null) {
throw new NullPointerException();
}
this.baseType = baseType;
this.typeFieldName = typeFieldName;
}
/**
* Creates a new runtime type adapter using for {@code baseType} using {@code
* typeFieldName} as the type field name. Type field names are case sensitive.
*/
public static <T> RuntimeClassNameTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
return new RuntimeClassNameTypeAdapterFactory<T>(baseType, typeFieldName);
}
/**
* Creates a new runtime type adapter for {@code baseType} using {@code "type"} as
* the type field name.
*/
public static <T> RuntimeClassNameTypeAdapterFactory<T> of(Class<T> baseType) {
return new RuntimeClassNameTypeAdapterFactory<T>(baseType, "class");
}
/**
* Registers {@code type} identified by {@code label}. Labels are case
* sensitive.
*
* @throws IllegalArgumentException if either {@code type} or {@code label}
* have already been registered on this type adapter.
*/
public RuntimeClassNameTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
if (type == null || label == null) {
throw new NullPointerException();
}
if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
throw new IllegalArgumentException("types and labels must be unique");
}
labelToSubtype.put(label, type);
subtypeToLabel.put(type, label);
return this;
}
/**
* Registers {@code type} identified by its {@link Class#getSimpleName simple
* name}. Labels are case sensitive.
*
* @throws IllegalArgumentException if either {@code type} or its simple name
* have already been registered on this type adapter.
*/
public RuntimeClassNameTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
return registerSubtype(type, type.getSimpleName());
}
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
final Map<String, TypeAdapter<?>> labelToDelegate
= new LinkedHashMap<String, TypeAdapter<?>>();
final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate
= new LinkedHashMap<Class<?>, TypeAdapter<?>>();
// && !String.class.isAssignableFrom(type.getRawType())
if (Object.class.isAssignableFrom(type.getRawType())) {
TypeAdapter<?> delegate = gson.getDelegateAdapter(this, type);
labelToDelegate.put(type.getRawType().getName(), delegate);
subtypeToDelegate.put(type.getRawType(), delegate);
}
// for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
// TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
// labelToDelegate.put(entry.getKey(), delegate);
// subtypeToDelegate.put(entry.getValue(), delegate);
// }
return new TypeAdapter<R>() {
@SuppressWarnings("unchecked")
@Override
public R read(JsonReader in) throws IOException {
JsonElement jsonElement = Streams.parse(in);
if (jsonElement.isJsonObject()) {
JsonElement labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
if (labelJsonElement == null) {
throw new JsonParseException("cannot deserialize " + baseType
+ " because it does not define a field named " + typeFieldName);
}
String label = labelJsonElement.getAsString();
TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
if (delegate == null) {
Class<R> aClass;
try {
aClass = (Class<R>) Class.forName(label);
} catch (ClassNotFoundException e) {
throw new JsonParseException("Cannot find class " + label, e);
}
TypeToken<R> subClass = TypeToken.get(aClass);
delegate = gson.getDelegateAdapter(RuntimeClassNameTypeAdapterFactory.this, subClass);
if (delegate == null) {
throw new JsonParseException("cannot deserialize " + baseType + " subtype named "
+ label + "; did you forget to register a subtype?");
}
}
return delegate.fromJsonTree(jsonElement);
} else if (jsonElement.isJsonNull()) {
return null;
} else {
TypeAdapter<R> delegate = gson.getDelegateAdapter(RuntimeClassNameTypeAdapterFactory.this, type);
if (delegate == null) {
throw new JsonParseException("cannot deserialize " + baseType + "; did you forget to register a subtype?");
}
return delegate.fromJsonTree(jsonElement);
}
}
@Override
public void write(JsonWriter out, R value) throws IOException {
Class<?> srcType = value.getClass();
String label = srcType.getName();
TypeAdapter<R> delegate = getDelegate(srcType);
if (delegate == null) {
throw new JsonParseException("cannot serialize " + srcType.getName()
+ "; did you forget to register a subtype?");
}
JsonElement jsonTree = delegate.toJsonTree(value);
if (!jsonTree.isJsonObject()) {
Streams.write(jsonTree, out);
} else {
JsonObject jsonObject = jsonTree.getAsJsonObject();
if (jsonObject.has(typeFieldName)) {
throw new JsonParseException("cannot serialize " + srcType.getName()
+ " because it already defines a field named " + typeFieldName);
}
JsonObject clone = new JsonObject();
clone.add(typeFieldName, new JsonPrimitive(label));
for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
clone.add(e.getKey(), e.getValue());
}
Streams.write(clone, out);
}
}
@SuppressWarnings("unchecked")
private TypeAdapter<R> getDelegate(Class<?> srcType) {
TypeAdapter<?> typeAdapter = subtypeToDelegate.get(srcType);
if (typeAdapter != null) {
return (TypeAdapter<R>) typeAdapter;
}
for (Map.Entry<Class<?>, TypeAdapter<?>> classTypeAdapterEntry : subtypeToDelegate.entrySet()) {
if (classTypeAdapterEntry.getKey().isAssignableFrom(srcType)) {
return (TypeAdapter<R>) classTypeAdapterEntry.getValue();
}
}
return null;
}
}.nullSafe();
}
}
All credit still goes to him/her, though. As s/he says, please test this code before using it!
尽管如此,所有的功劳仍然归功于他/她。正如他/她所说,请在使用此代码之前对其进行测试!