Android: Attempted to serialize … Forgot to regist

2020-08-22 17:08发布

问题:

How to convert HashMap to JSON using Gson.

class ClassData {
    public String jsonString;
    public Class classType;
}
HashMap<String, ClassData> map = new HashMap<>();

void convert(){
    new Gson().toJson(map); // throws
}

I am getting the next exception

Attempted to serialize java.lang.Class: java.lang.String. Forgot to register a type adapter?

回答1:

You need to implement your own custom serializer for ClassData, and then register it to your Gson Builder.

public class ClassDataSerializerExample {
    static class ClassData {
        public String jsonString;
        public Class classType;

        public ClassData(String jsonString, Class classType) {
            this.jsonString = jsonString;
            this.classType = classType;
        }
    }

    static class ClassDataSerializer implements JsonSerializer<ClassData> {
        @Override
        public JsonElement serialize(ClassData src, Type typeOfSrc, JsonSerializationContext context) {
            return new JsonPrimitive(src.jsonString);
        }
    }

    public static void main(String[] args) {
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(ClassData.class, new ClassDataSerializer());

        HashMap<String, ClassData> map = new HashMap<>();
        map.put("key", new ClassData("key", String.class));

        Gson gson = gsonBuilder.create();
        String json = gson.toJson(map);

        System.out.println(json);
    }
}

Output: {"key":"key"}

Read more: https://sites.google.com/site/gson/gson-user-guide#TOC-Writing-a-Serializer



回答2:

My approach works for Gson 2.3.1 with Java 1.7, can't guarantee it will work with Android.

The problem lies within the variable classType, since Gson can't process the type Class as it is. So we have to create a TypeAdapter for Class.

public class ClassTypeAdapter extends TypeAdapter<Class<?>> {
    @Override
    public void write(JsonWriter jsonWriter, Class<?> clazz) throws IOException {
        if(clazz == null){
            jsonWriter.nullValue();
            return;
        }
        jsonWriter.value(clazz.getName());
    }

    @Override
    public Class<?> read(JsonReader jsonReader) throws IOException {
        if (jsonReader.peek() == JsonToken.NULL) {
            jsonReader.nextNull();
            return null;
        }
        Class<?> clazz = null;
        try {
            clazz = Class.forName(jsonReader.nextString());
        } catch (ClassNotFoundException exception) {
            throw new IOException(exception);
        }
        return clazz;
    }
}

To make it work you'll need a TypeAdapterFactory.

public class ClassTypeAdapterFactory implements TypeAdapterFactory {
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
        if(!Class.class.isAssignableFrom(typeToken.getRawType())) {
            return null;
        }
        return (TypeAdapter<T>) new ClassTypeAdapter();
    }
}

And register the TypeAdapterFactory to your Gson Builder.

gsonBuilder.registerTypeAdapterFactory(new ClassTypeAdapterFactory());

AFAIK you have to use a TypeAdapterFactory. A directly registered TypeAdapter

gsonBuilder.registerTypeAdapter(Class.class, new ClassTypeAdapter());

seems to be ignored when an object of type Class is encountered.

Gson transforms both the HashMap and Java class instances to JSON objects. Example:

{
    "hash_key_1": {
        "jsonString": "json_string_instance_1",
        "classType": "my.class.Name1"
    },
    "hash_key_2": {
        "jsonString": "json_string_instance_2",
        "classType": "my.class.Name2"
    }
}