gson - 如何在序列化任何类型的对象时包含类名属性 [英] gson - How to include class name property when serializing object of any type

查看:1761
本文介绍了gson - 如何在序列化任何类型的对象时包含类名属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的应用程序中序列化对象时,意识到我需要将类名称作为属性。如果我为序列化的任何非原始对象添加类名称属性,可能会最好。



我看到这是Genson中的一个内置功能,它与 useClassMetadata 方法。但是我已经在我的项目中使用了gson,所以如果我可以坚持使用它将是有益的。



这是我目前的尝试:

  package com.mycompany.javatest; 

import com.google.gson。*;
import java.lang.reflect。*;

public class JavaTest {

public static class GenericSerializer实现了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());
}
返回retValue;

$ b @Override
public Object deserialize(JsonElement json,Type typeOfT,
JsonDeserializationContext context)throws JsonParseException {

类actualClass;
if(json.isJsonObject()){
JsonObject jsonObject = json.getAsJsonObject();
String className = jsonObject.get(CLASS_PROPERTY_NAME).getAsString();

尝试{
actualClass = Class.forName(className);
}
catch(ClassNotFoundException e){
e.printStackTrace();
抛出新的JsonParseException(e.getMessage());
}
}
else {
actualClass = typeOfT.getClass();
}
return context.deserialize(json,actualClass);



public static class MyClass {

private final String name =SpongePants SquareBob;


$ b 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));


$ b $ / code $ / pre

打印

  {name:SpongePants SquareBob} 

我希望它打印

  {name:SpongePants SquareBob,class: com.mycompany.javatest $ MyClass} 

编辑:另一种尝试(这次使用GsonFire)

  package com.mycompany.javatest; 

import com.google.gson。*;
导入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){
//忽略
}

@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()){
返回Class.forName(je.getAsJsonObject().get(CLASS_PROPERTY_NAME).getAsString());
}
catch(ClassNotFoundException ex){
ex.printStackTrace();
}
}

返回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); //< - 给我一个ClassCastException

}

}


解决方案

还有另外一个答案。花了一点时间。



方面评论:如果您将递归地使用反射来计算出类中的字段,那么上述解决方案将起作用。然后用特殊的序列化程序将这些序列化,同时为父对象使用单独的序列化程序。这将避免计算器。



话虽如此 - 我是一个懒惰的开发者,所以我喜欢懒惰。我正在为您调整Google解决方案。



注意:请测试并根据您的需求进行调整。这是一个原型,我没有清除不必要的代码或检查可能的问题>



代码的原始来源:



https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java



因此,这是基于 RuntimeTypeAdapterFactory 。该工厂由谷歌提供,其目的是支持分层反序列化。要做到这一点,你需要注册一个基类和ALL子类,并且要添加一个属性作为标识符。



这显然为我们提供了我们想要的东西:为可以处理这些类的类类型递归注册不同的适配器,同时不是在圆圈中运行并导致一个计算器。有一个重要问题:您必须注册所有子类。这显然是不合适的(尽管有人可能会争辩说,你可以使用类路径解决方案,并简单地在启动时添加所有类,以便能够在任何地方使用此类)。所以我查看了源代码并更改了代码以动态执行此操作。 请注意,谷歌警告不要这样做 - 按照自己的条件使用它:)



这是我的工厂:

  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;

/ **
*调整运行时类型可能与其声明类型不同的值。当字段的类型与GSON在反序列化该字段时应创建
*的类型不同时,此
*是必需的。例如,考虑这些类型:
*< pre> {@code
* abstract class Shape {
* int x;
* int y;
*}
* class Circle延伸Shape {
* int radius;
*}
* class Rectangle extends Shape {
* int width;
* int height;
*}
* class Diamond extends Shape {
* int width;
* int height;
*}
* class绘图{
* Shape bottomShape;
* Shape topShape;
*}
*}< / pre>
*< p>没有附加的类型信息,序列化的JSON是不明确的。
*是本图中的底部形状是长方形还是菱形? <预> {@code
* {
*bottomShape:{
*width:10,
*height:5,
*x: 0,
*y:0
*},
*topShape:{
*radius:2,
*x:4 ,
*y:1
*}
*}}< / pre>
*该类通过向
*序列化的JSON添加类型信息并在JSON为
*反序列化时遵守该类型信息来解决此问题:< pre> {@code
* {
*bottomShape:{
*type:Diamond,
*width:10,
*height :5,
*x:0,
*y:0
*},
*topShape:{
*type :Circle,
*radius:2,
*x:4,
*y:1
*}
*}} < /预>
*类型字段名称({@codetype})和类型标签({@code
*Rectangle})都是可配置的。
*
*< h3>注册类型< / h3>
*通过将基本类型和类型字段
*名称传递给工厂方法{@link #of}来创建一个{RuntimeTypeAdapterFactory}。如果您不提供显式类型
*字段名称,则将使用{@codetype}。 <预> {@code
* RuntimeTypeAdapterFactory< Shape> shapeAdapterFactory
* = RuntimeTypeAdapterFactory.of(Shape.class,type);
*}< / pre>
*接下来注册所有的子类型。每个子类型必须明确
*注册。这可以保护您的应用程序免受注入攻击。如果
*不提供明确的类型标签,则将使用该类型的简单名称。
*< pre> {@code
* shapeAdapter.registerSubtype(Rectangle.class,Rectangle);
* shapeAdapter.registerSubtype(Circle.class,Circle);
* shapeAdapter.registerSubtype(Diamond.class,Diamond);
*}< / pre>
*最后,在应用程序的GSON构建器中注册类型适配器工厂:
*< pre> {@code
* Gson gson = new GsonBuilder()
* .registerTypeAdapterFactory(shapeAdapterFactory)
* .create();
*}< / pre>
*与{@code GsonBuilder}类似,此API支持链接:< pre> {@code
* RuntimeTypeAdapterFactory< Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
.registerSubtype(Rectangle.class)
* .registerSubtype(Circle.class)
* .registerSubtype(Diamond.class);
*}< / pre>
* /
public final class RuntimeClassNameTypeAdapterFactory< T>实现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;
}

/ **
*使用{@code baseType}创建一个新的运行时类型适配器,使用{@code
* typeFieldName}作为类型字段名称。类型字段名称区分大小写。
* /
public static< T> RuntimeClassNameTypeAdapterFactory< T> (Class< T> baseType,String typeFieldName){
return new RuntimeClassNameTypeAdapterFactory< T>(baseType,typeFieldName);
}

/ **
*为{bcode baseType}创建一个新的运行时类型适配器,使用{type}作为
* type字段名称。
* /
public static< T> RuntimeClassNameTypeAdapterFactory< T> (Class< T> baseType){
return new RuntimeClassNameTypeAdapterFactory< T>(baseType,class);
}

/ **
*由{@code label}标识的注册{code type}。标签大小写
*敏感。
*
*如果已在此类型适配器上注册了{@code type}或{@code label}
*,则引发IllegalArgumentException。
* /
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);
返回此;
}

/ **
*由其{@link Class#getSimpleName simple
* name}标识的注册类型{}。标签区分大小写。
*
*如果在此类型适配器上已经注册了{@code type}或其简单名称
*,则会抛出IllegalArgumentException。
* /
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); (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);


返回新的TypeAdapter< R>(){
@Override public R read(JsonReader in)throws IOException {
JsonElement jsonElement = Streams.parse (在);
JsonElement labelJsonElement = jsonElement.getAsJsonObject()。remove(typeFieldName);
if(labelJsonElement == null){
throw new JsonParseException(can not deserialize+ baseType
+,因为它没有定义名为+ typeFieldName的字段;
}
String label = labelJsonElement.getAsString();
@SuppressWarnings(unchecked)//注册要求该子类型扩展T
TypeAdapter< R> delegate =(TypeAdapter< R>)labelToDelegate.get(label);
if(delegate == null){
throw new JsonParseException(can not deserialize+ baseType +subtype named
+ label +;你忘记注册一个子类型吗?) ;
}
返回delegate.fromJsonTree(jsonElement);
}

@Override public void write(JsonWriter out,R value)throws IOException {
Class<?> srcType = value.getClass();
String label = srcType.getName();
@SuppressWarnings(unchecked)//注册要求该子类型扩展T
TypeAdapter< R> delegate =(TypeAdapter< R>)subtypeToDelegate.get(srcType);
if(delegate == null){
throw new JsonParseException(can not serialize+ srcType.getName()
+;你忘了注册一个子类型吗?
}
JsonElement jsonTree = delegate.toJsonTree(value);
if(jsonTree.isJsonPrimitive()){
Streams.write(jsonTree,out);
} else {
JsonObject jsonObject = jsonTree.getAsJsonObject();
if(jsonObject.has(typeFieldName)){
throw new JsonParseException(can not serialize+ srcType.getName()
+,因为它已经定义了一个名为+ typeFieldName的字段;
}
JsonObject clone = new JsonObject();
clone.add(typeFieldName,new JsonPrimitive(label)); (Map.Entry< String,JsonElement> e:jsonObject.entrySet()){
clone.add(e.getKey(),e.​​getValue());

}
Streams.write(clone,out);
}
}
} .nullSafe();
}
}

我为您添加了所有导入。这不是(真的)发布在maven central,尽管你可以在这里找到它: https://mvnrepository.com/artifact/org.danilopianini/gson-extras/0.1.0



无论你需要进行适应性获取这为你工作,所以我做了一个副本。该副本完全编译,你可以简单地将它粘贴到你的代码中,并为自己节省额外的依赖。



这段代码的重要部分如下:(我有意留下他们但注释掉,所以你可以告诉)
$ b

create(Gson gson,TypeToken< R> type)



检查原始类型是否可以从String类中分配。您希望将其应用于每个类对象,因此这需要处理该问题。注意,之前的代码会查找类型是否已注册到类中 - 不再需要(因此不需要变量;您应该清理代码)

in @Override public void write(JsonWriter out,R value)throws IOException {



首先,我们摆脱标签。我们的标签始终是源类型的名称。这是在:



String label = srcType.getName();



其次,我们必须区分原始类型和对象类型。 Gson世界中的原始类型是Strings,Integers等。这意味着我们上面的检查(添加一个适配器)并不能捕捉到这些对象类型都是基本类型的事实。所以我们这样做:

$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ Streams.write(jsonTree,out);

这可以解决这个问题。如果它是原始的,只需将树写入流。如果不是,我们将所有其他字段写入类字段。

  JsonObject clone = new JsonObject(); 
clone.add(typeFieldName,new JsonPrimitive(label)); (Map.Entry< String,JsonElement> e:jsonObject.entrySet()){
clone.add(e.getKey(),e.​​getValue());

}
Streams.write(clone,out);

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;


现在为您输出这个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
}
正如你所看到的,正确地创建了字符串,长整数,整数。正如你所看到的,字符串,长整数,正确的创建。每个类对象recursivley都有它的类名。

这是一种通用的方法,应该可以处理您创建的所有内容。但是,如果你决定采取这种做法,请帮我一个忙,并写一些单元测试;)就像我之前提到的那样,我为这个实现建立了原型。



打勾:)



问候,




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.

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)

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

    }

}

解决方案

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:

https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java

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.

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 ALL subclasses. 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

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)

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)

in @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();

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:

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 AND the 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 ;)

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:

{  
   "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.

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

这篇关于gson - 如何在序列化任何类型的对象时包含类名属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆