简体   繁体   中英

Serialize class with generic type using gson?

I have the following class

private static class ClassWithGenericType<T> {
    Set<T> values;
}

If I initialize now the class with a Set of Enum -values, serialize and deserialize the object by using gson, the Set of the deserialized object does not contain the Enum -values, but the values as String .

I think this is because the generic type is thrown away through the serialization. I saw, that I could use new TypeToken<...>(){}.getType(); , but the problem is, that the class above is part of a bigger object, so I cannot call gson.fromJson(classWithGenericType, typeToken) directly.

Is there a smart way of solving this problem? I thought of a TypeAdapter , which does not serialize only the values of the Set , but also it's type.

I found now a solution and created a TypeAdapter .

public class SetTypeAdapterFactory implements TypeAdapterFactory {
    @Override
    public <T> TypeAdapter<T> create(Gson gson, @NonNull TypeToken<T> type) {
        if (!Set.class.isAssignableFrom(type.getRawType())) {
            return null;
        }
        return (TypeAdapter<T>) new SetTypeAdapter(gson);
    }
}

public class SetTypeAdapter extends TypeAdapter<Set<?>> {
    public static final String TYPE = "@type";
    public static final String DATA = "@data";
    private final Gson gson;

    public SetTypeAdapter(@NonNull Gson gson) {
        this.gson = gson;
    }

    @Override
    public void write(final JsonWriter out, final Set<?> set
    ) throws IOException {
        out.beginArray();
        for (Object item : set) {
            out.beginObject();
            out.name(TYPE).value(item.getClass().getName());
            out.name(DATA).jsonValue(gson.toJson(item));
            out.endObject();
        }
        out.endArray();
    }

    @Override
    public Set<?> read(final JsonReader in) throws IOException {
        final Set<Object> set = Sets.newHashSet();
        in.beginArray();
        while (in.hasNext()) {
            in.beginObject();
            set.add(readNextObject(in));
            in.endObject();
        }
        in.endArray();
        return set;
    }

    private Object readNextObject(JsonReader in) throws IOException {
        try {
            checkNextName(in, TYPE);
            Class<?> cls = Class.forName(in.nextString());
            checkNextName(in, DATA);
            return gson.fromJson(in, cls);
        } catch (ClassNotFoundException exception) {
            throw new IOException(exception);
        }
    }

    private void checkNextName(JsonReader in, String name) throws IOException {
        if (!in.nextName().equals(name)) {
            throw new IOException("Name was not: " + name);
        }
    }
}

We can add the factory to the GsonBuilder and afterwards we are capable of serializing a Set with generic types.

var gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapterFactory(new SetTypeAdapterFactory());
var gson = gsonBuilder.create();

The serialized Set has then the following structure:

[
  {
    "@type":<class_name_first_element>,
    "@data":<first_element_as_json>
  }, 
  ...
]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM