9

我想序列化一个自定义 Java 对象,这样我就可以使用SharedPreferences它来存储它并在另一个 Activity 中检索它。我不需要持久存储,SharedPreferences当我的应用程序关闭时我会擦除它们。我目前正在为此使用 GSON,但它似乎不适用于 Android 的 SparseArray 类型。

我的对象:

public class PartProfile {

private int gameId;
// Some more primitives
private SparseArray<Part> installedParts = new SparseArray<Part>();

// ...
}

public class Part {
   private String partName;
   // More primitives
}

序列化:

Type genericType = new TypeToken<PartProfile>() {}.getType();
String serializedProfile = Helpers.serializeWithJSON(installedParts, genericType);
preferences.edit().putString("Parts", serializedProfile).commit();

序列化与JSON():

public static String serializeWithJSON(Object o, Type genericType) {
    Gson gson = new Gson();
    return gson.toJson(o, genericType);
}

反序列化:

Type genericType = new TypeToken<PartProfile>() {}.getType();
PartProfile parts = gson.fromJson(preferences.getString("Parts", "PARTS_ERROR"), genericType);

SparseArray<Part> retreivedParts = parts.getInstalledParts();
int key;
for (int i = 0; i < retreivedParts.size(); i++) {
    key = retreivedParts.keyAt(i);
    // Exception here:
    Part part = retreivedParts.get(key);
    // ...
}

例外:

java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.mypackage.objects.Part

我不明白为什么 Gson 想将 a 投射LinkedTreeMap到我的对象上,我从不在我的整个程序中使用一个。HashMap<Integer,Part>在我切换到 之前,我曾经有一个SparseArray<Part>,并且从来没有遇到过问题。Gson 不支持 SparseArrays,还是我这边有错误?

编辑:似乎 SparseArray 被正确反序列化,但不是里面的对象。而不是LinkedTreeMaps,这些应该是类型Part在此处输入图像描述

4

3 回答 3

13

确实有一种方法可以序列化任何类型的SparseArray,这是一个示例代码:

public class SparseArrayTypeAdapter<T> extends TypeAdapter<SparseArray<T>> {

    private final Gson gson = new Gson();
    private final Class<T> classOfT;
    private final Type typeOfSparseArrayOfT = new TypeToken<SparseArray<T>>() {}.getType();
    private final Type typeOfSparseArrayOfObject = new TypeToken<SparseArray<Object>>() {}.getType();

    public SparseArrayTypeAdapter(Class<T> classOfT) {
        this.classOfT = classOfT;
    }

    @Override
    public void write(JsonWriter jsonWriter, SparseArray<T> tSparseArray) throws IOException {
        if (tSparseArray == null) {
            jsonWriter.nullValue();
            return;
        }
        gson.toJson(gson.toJsonTree(tSparseArray, typeOfSparseArrayOfT), jsonWriter);
    }

    @Override
    public SparseArray<T> read(JsonReader jsonReader) throws IOException {
        if (jsonReader.peek() == JsonToken.NULL) {
            jsonReader.nextNull();
            return null;
        }
        SparseArray<Object> temp = gson.fromJson(jsonReader, typeOfSparseArrayOfObject);
        SparseArray<T> result = new SparseArray<T>(temp.size());
        int key;
        JsonElement tElement;
        for (int i = 0; i < temp.size(); i++) {
            key = temp.keyAt(i);
            tElement = gson.toJsonTree(temp.get(key));
            result.put(key, gson.fromJson(tElement, classOfT));
        }
        return result;
    }

}

要使用它,您需要在Gson对象中注册它,如下所示:

Type sparseArrayType = new TypeToken<SparseArray<MyCustomClass>>() {}.getType();
Gson gson = new GsonBuilder()
    .registerTypeAdapter(sparseArrayType, new SparseArrayTypeAdapter<MyCustomClass>(MyCustomClass.class))
    .create();

你可以在这个 gist中找到这个例子。

PS:我知道它根本没有优化,但这只是一个示例,可以说明如何实现您所需要的。

于 2013-09-05T19:56:08.350 回答
5

似乎 SparseArray 被正确反序列化,但不是里面的对象。这些应该是 Part 类型,而不是 LinkedTreeMaps。

您的观察是正确的,因为SparseArray 包含 Object(不是Part),Gson 不会有任何线索将 Part 作为您的对象类型。因此,它将您的列表映射为其臭名昭著的内部类型LinkedTreeMap

要解决它,我认为您将无法使用SparseArray... 或者您可以尝试retreivedParts.get(key).toString(),然后使用 gson 再次解析对象。但我认为这样做没有效率

于 2013-07-17T08:00:06.947 回答
0

正如其他答案中指出的那样,SparseArray内部实现使用 anObject[]来存储值,因此 Gson 无法正确反序列化它。

这可以通过创建自定义 Gson 来解决TypeAdapterFactory

import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import android.util.SparseArray;

import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

public class SparseArrayTypeAdapterFactory implements TypeAdapterFactory {
    public static final SparseArrayTypeAdapterFactory INSTANCE = new SparseArrayTypeAdapterFactory();
    
    private SparseArrayTypeAdapterFactory() { }
    
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        // This factory only supports (de-)serializing SparseArray
        if (type.getRawType() != SparseArray.class) {
            return null;
        }
        
        // Get the type argument for the element type parameter `<E>`
        // Note: Does not support raw SparseArray type (i.e. without type argument)
        Type elementType = ((ParameterizedType) type.getType()).getActualTypeArguments()[0];
        TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
        
        // This is safe because check at the beginning made sure type is SparseArray
        @SuppressWarnings("unchecked")
        TypeAdapter<T> adapter = (TypeAdapter<T>) new SparseArrayTypeAdapter<>(elementAdapter);
        // call nullSafe() to make adapter automatically handle `null` SparseArrays
        return adapter.nullSafe();
    }
    
    private static class SparseArrayTypeAdapter<E> extends TypeAdapter<SparseArray<E>> {
        private final TypeAdapter<E> elementTypeAdapter;
        
        public SparseArrayTypeAdapter(TypeAdapter<E> elementTypeAdapter) {
            this.elementTypeAdapter = elementTypeAdapter;
        }
        
        @Override
        public void write(JsonWriter out, SparseArray<E> sparseArray) throws IOException {
            out.beginObject();
            
            int size = sparseArray.size();
            for (int i = 0; i < size; i++) {
                out.name(Integer.toString(sparseArray.keyAt(i)));
                elementTypeAdapter.write(out, sparseArray.valueAt(i));
            }
            
            out.endObject();
        }

        @Override
        public SparseArray<E> read(JsonReader in) throws IOException {
            in.beginObject();
            
            SparseArray<E> sparseArray = new SparseArray<>();
            while (in.hasNext()) {
                int key = Integer.parseInt(in.nextName());
                E value = elementTypeAdapter.read(in);
                // Use `append(...)` here because SparseArray is serialized in ascending
                // key order so `key` will be > previously added key
                sparseArray.append(key, value);
            }
            
            in.endObject();
            return sparseArray;
        }
        
    }
}

该工厂将 SparseArrays 序列化为 JSON 对象,其中键作为 JSON 属性名称,使用相应适配器序列化的值作为 JSON 值,例如:

new SparseArray<List<String>>().put(5, Arrays.asList("Hello", "World"))

    ↓ JSON

{"5": ["Hello", "World"]}

GsonBuilder然后,您可以通过使用在其上注册TypeAdapterFactory的 Gson 实例来使用此 TypeAdapterFactory:

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory​(SparseArrayTypeAdapterFactory.INSTANCE)
    .create();
于 2020-08-12T17:11:44.880 回答