【问题标题】:Gson Polymorphic SerializationGson 多态序列化
【发布时间】:2012-10-26 00:52:10
【问题描述】:

使用 Gson 2.2.2 我正在尝试序列化 POJO(行为)的数组列表。

我有一个几乎是我在网上看到的副本的适配器:

public class BehaviorAdapter implements JsonSerializer<Behavior> {

    private static final String CLASSNAME = "CLASSNAME";
    private static final String INSTANCE = "INSTANCE";

    @Override
    public JsonElement serialize(Behavior src, Type typeOfSrc,
            JsonSerializationContext context) {

        JsonObject retValue = new JsonObject();
        String className = src.getClass().getCanonicalName();
        retValue.addProperty(CLASSNAME, className);
        JsonElement elem = context.serialize(src);
        retValue.add(INSTANCE, elem);
        return retValue;
    }
}

我是这样注册的:

GsonBuilder builder = new GsonBuilder();        
builder.registerTypeHierarchyAdapter(Behavior.class, new BehaviorAdapter());
gson = builder.create();

然后当我尝试序列化我的 ArrayList 时:

String json2 = gson.toJson(behaviors);

我得到一个堆栈溢出。

好像在线:

JsonElement elem = context.serialize(src);

它开始一个递归循环,一次又一次地通过我的序列化程序。那么我如何注册它以防止这种情况发生?我需要序列化列表并保持多态性。

【问题讨论】:

标签: java gson


【解决方案1】:

看起来你找到了无限循环the JsonSerializer docs warn about

但是,您永远不应该在 src 对象本身上调用它,因为这会导致无限循环(Gson 将再次调用您的回调方法)。

我能想到的最简单的方法是创建一个没有安装处理程序的新 Gson 实例,并通过它运行您的实例。

最后,您可以只序列化List&lt;Behavior&gt;

public class BehaviorListAdapter implements JsonSerializer<List<Behavior>> {

    private static final String CLASSNAME = "CLASSNAME";
    private static final String INSTANCE = "INSTANCE";

    @Override
    public JsonElement serialize(List<Behavior> src, Type typeOfSrc,
            JsonSerializationContext context) {
        JsonArray array = new JsonArray();
        for (Behavior behavior : src) {
            JsonObject behaviorJson = new JsonObject();
            String className = behavior.getClass().getCanonicalName();
            behaviorJson.addProperty(CLASSNAME, className);
            JsonElement elem = context.serialize(behavior);
            behaviorJson.add(INSTANCE, elem);
            array.add(behaviorJson);
        }
        return array;
    }
}

GsonBuilder builder = new GsonBuilder();
// use a TypeToken to make a Type instance for a parameterized type
builder.registerTypeAdapter(
    (new TypeToken<List<Behavior>>() {}).getType(),
    new BehaviorListAdapter());
gson = builder.create();

【讨论】:

  • 感谢您的链接!我看了那里,但没有在序列化函数中。
  • registerTypeHierarchyAdapter 好像想上课,是不是漏了什么?
  • 不,对不起,那是我。我的意思是registerTypeAdapter
  • 我还想提一下,因为我为此浪费了很多时间,所以当你序列化时,你需要指定 typetoken。 Type typetoken = new TypeToken&lt;ArrayList&lt;Behavior&gt;&gt;() {}.getType(); gson.toJson(sprite.behaviors, typetoken);
【解决方案2】:

看看RuntimeTypeAdapterFactory。该类的test 有一个示例:

RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of(
    BillingInstrument.class)
    .registerSubtype(CreditCard.class);
Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(rta)
    .create();

CreditCard original = new CreditCard("Jesse", 234);
assertEquals("{\"type\":\"CreditCard\",\"cvv\":234,\"ownerName\":\"Jesse\"}",
    gson.toJson(original, BillingInstrument.class));
BillingInstrument deserialized = gson.fromJson(
    "{type:'CreditCard',cvv:234,ownerName:'Jesse'}", BillingInstrument.class);
assertEquals("Jesse", deserialized.ownerName);
assertTrue(deserialized instanceof CreditCard);

这个类不在核心Gson中;您需要将其复制到您的项目中才能使用它。

【讨论】:

  • 这似乎是个好主意,除了我有很多子类。
  • 为了让 RuntypeAdapterFactory 在编写时工作,我必须将“if (type.getRawType() != baseType) { return null; }” 更改为 if (!baseType.isAssignableFrom(type.getRawType( ))) { 返回空值; } 在创建方法中。
  • @Melanie:这是序列化工作 100% 所必需的。干杯。
【解决方案3】:

我明白你在这里想要做什么,我也遇到了同样的问题。

我写完了一个简单的抽象类

public abstract class TypedJsonizable extends Jsonizable {}

并向我的 Gson 实例注册一个 TypeHierarchyAdapter

    protected static Gson gson = new GsonBuilder()
    .registerTypeHierarchyAdapter
    (TypedJsonizable.class,new TypedJsonizableSerializer());

这个 TypeAdapter 的关键是不要调用 context.serialize 和 context.deserialize 因为这会导致一个无限循环,正如Jeff Bowman 在他的回答中所说的那样,这个 TypeAdapter 使用反射来避免这种情况。

import com.google.gson.*;
import org.apache.log4j.Logger;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;

public class TypedJsonizableSerializer implements JsonSerializer<TypedJsonizable>, JsonDeserializer<TypedJsonizable> {
static final String CLASSNAME_FIELD = "_className";
Logger logger = Logger.getLogger(TypedJsonizable.class);

@Override
public JsonElement serialize(TypedJsonizable src, Type typeOfSrc, JsonSerializationContext context) {
    JsonObject contentObj = new JsonObject();
    contentObj.addProperty(CLASSNAME_FIELD,src.getClass().getCanonicalName());

    for (Field field : src.getClass().getDeclaredFields()) {
        field.setAccessible(true);
        try {
            if (field.get(src)!=null)
                contentObj.add(field.getName(),context.serialize(field.get(src)));
        } catch (IllegalAccessException e) {
            logger.error(e.getMessage(),e);
        }
    }
    return contentObj;
}

@Override
public TypedJsonizable deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
    JsonObject jsonObject = json.getAsJsonObject();
    String className = jsonObject.get(CLASSNAME_FIELD).getAsString();
    if (className == null || className.isEmpty())
        throw new JsonParseException("Cannot find _className field. Probably this instance has not been serialized using Jsonizable jsonizer");
    try {
        Class<?> clazz = Class.forName(className);
        Class<?> realClazz = (Class<?>) typeOfT;
        if (!realClazz.equals(clazz))
            throw new JsonParseException(String.format("Cannot serialize object of class %s to %s", clazz.getCanonicalName(),realClazz.getCanonicalName()));
        Object o  = clazz.getConstructor().newInstance();
        for (Field field : o.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            if (jsonObject.has(field.getName())) {
                field.set(o,context.deserialize(jsonObject.get(field.getName()) , field.getGenericType()));
            }
        }
        return (TypedJsonizable) o;
    } catch (ClassNotFoundException e) {
        throw new JsonParseException(String.format("Cannot find class with name %s . Maybe the class has been refactored or sender and receiver are not using the same jars",className));
    } catch (IllegalAccessException e){
        throw new JsonParseException(String.format("Cannot deserialize, got illegalAccessException %s ",e.getMessage()));
    } catch (NoSuchMethodException | InstantiationException | InvocationTargetException e) {
        throw new JsonParseException(String.format("Cannot deserialize object of class %s, unable to create a new instance invoking empty constructor",className));
    }
}

}

【讨论】:

    【解决方案4】:

    我为这个问题找到了另一种解决方案(解决方法):不要在类层次结构中序列化基类,而是使用后代。例如:

    ...
    protected static Gson gson;
    ...
    GsonBuilder gsb = new GsonBuilder();
    gsb.registerTypeAdapter(SomeBase.class, new MQPolymorphicSerializer<SomeBase>());
    gson = gsb.create();
    

    SomeBase

    public class SomeBase{
    

    ... }

    SomeDescendant

    public class SomeDescendant extends SomeBase {
    ...
    }
    

    堆栈溢出异常情况:

    gson.toJson(new SomeBase());
    

    解决方案:

    gson.toJson(new SomeDescendant());
    

    ...最后 - 序列化程序示例:

    public class MQPolymorphicSerializer<T> implements JsonSerializer<T>, JsonDeserializer<T> {
    private static final String CLASSNAME = "CLASSNAME";
    private static final String INSTANCE = "INSTANCE";
    
    @Override
    public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) {
        JsonObject retValue = new JsonObject();
        String className = src.getClass().getName();
        retValue.addProperty(CLASSNAME, className);
        JsonElement elem = context.serialize(src);
        retValue.add(INSTANCE, elem);
        return retValue;
    }
    
    @Override
    public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        JsonObject jsonObject = json.getAsJsonObject();
        JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
        String className = prim.getAsString();
        Class<?> klass = null;
        try {
            klass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new JsonParseException(e.getMessage());
        }
        return context.deserialize(jsonObject.get(INSTANCE), klass);
    }
    

    }

    【讨论】:

      猜你喜欢
      • 2023-04-09
      • 1970-01-01
      • 2015-10-18
      • 2023-03-03
      • 1970-01-01
      • 1970-01-01
      • 2017-02-07
      相关资源
      最近更新 更多