5

使用 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);

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

4

4 回答 4

11

看起来您发现了JsonSerializer 文档警告的无限循环:

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

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

作为最终运行,您可以只序列化List<Behavior>

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();
于 2012-11-06T07:24:17.527 回答
7

看看RuntimeTypeAdapterFactory。该类的测试有一个例子:

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 中;您需要将其复制到您的项目中才能使用它。

于 2012-11-06T07:11:15.323 回答
1

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

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

public abstract class TypedJsonizable extends Jsonizable {}

并将 TypeHierarchyAdapter 注册到我的 Gson 实例

    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));
    }
}

}

于 2017-06-16T11:19:57.153 回答
1

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

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

一些基地

public class SomeBase{

... }

某后裔

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);
}

}

于 2018-11-26T10:26:36.770 回答