我有以下代码:
public static class A
{
public A() {}
private List<B> bs = new ArrayList<B>();
public List<B> getBs() {
return bs;
}
public void setBs(List<B> bs) {
this.bs = bs;
}
}
public static class B
{
B(String foo){this.foo=foo;}
private String foo;
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
}
public static void main(String[] args) throws Exception {
Gson gson = new Gson();
A a = new A();
a.getBs().add(new B("bar"));
System.out.println(gson.toJson(a));
}
并且正如预期的那样输出是:
{"bs":[{"foo":"bar"}]}
但是,如果我将A设为HashMap的子类:
public static class A extends HashMap
我收到一个空集:{}
我甚至尝试过:
System.out.println(gson.toJson(a, new TypeToken<A>(){}.getType()));
和
System.out.println(gson.toJson(a, new TypeToken<HashMap>(){}.getType()));
有人可以告诉我是否/如何使用GSON序列化这个HashMap子类?
答案 0 :(得分:2)
Gson使用(默认和自定义)TypeAdapterFactory
个实例以及他们创建的TypeAdapter
个对象来序列化/反序列化对象。
它遍历已注册的TypeAdapterFactory
个对象列表,并选择第一个可以为您提供的对象类型创建适当TypeAdapter
的对象。其中一个TypeAdapterFactory
对象是类型MapTypeAdapterFactory
之一,它创建TypeAdapter
(类型为MapTypeAdapterFactory$Adapter
),基于java.util.Map
接口序列化/反序列化(键/值)。它对您的自定义子类型的字段没有任何作用。
如果您希望Gson将您的类型序列化为Map
和自定义类型,则需要直接注册自定义TypeAdapter
或创建{TypeAdapterFactory
自定义TypeAdapter
1}}对象。
答案 1 :(得分:0)
这是自定义的TypeAdapterFactory。
测试:
public static void main(String[] args) throws Exception{
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new RetainFieldMapFactory())
.create();
Foo f = gson.fromJson("{'key1':'value1','key2':'value2'}", Foo.class);
System.out.println("in map:\t" + f.toString());
System.out.println("f.key1:\t"+f.key1);
System.out.println("toJson:\t"+gson.toJson(f));
}
public static class Foo extends HashMap<String, String> {
private String key1;
}
输出:
in map: {key2=value2}
f.key1: value1
toJson: {"key2":"value2","key1":"value1"}
RetainFieldMapFactory.java:
/**
* Created by linfaxin on 2015/4/9 009.
* Email: linlinfaxin@163.com
*/
public class RetainFieldMapFactory implements TypeAdapterFactory {
FieldNamingPolicy fieldNamingPolicy = FieldNamingPolicy.IDENTITY;
ConstructorConstructor constructorConstructor = new ConstructorConstructor(Collections.<Type, InstanceCreator<?>>emptyMap());
MapTypeAdapterFactory defaultMapFactory = new MapTypeAdapterFactory(constructorConstructor, false);
ReflectiveFilterMapFieldFactory defaultObjectFactory = new ReflectiveFilterMapFieldFactory(constructorConstructor,
fieldNamingPolicy, Excluder.DEFAULT);
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> mapAdapter = defaultMapFactory.create(gson, type);
if(mapAdapter!=null){
return (TypeAdapter<T>) new RetainFieldMapAdapter(mapAdapter, defaultObjectFactory.create(gson, type));
}
return mapAdapter;
}
class RetainFieldMapAdapter extends TypeAdapter<Map<String, Object>>{
TypeAdapter<Map<String, Object>> mapAdapter;
ReflectiveTypeAdapterFactory.Adapter<Map<String, Object>> objectAdapter;
RetainFieldMapAdapter(TypeAdapter mapAdapter, ReflectiveTypeAdapterFactory.Adapter objectAdapter) {
this.mapAdapter = mapAdapter;
this.objectAdapter = objectAdapter;
}
@Override
public void write(final JsonWriter out, Map<String, Object> value) throws IOException {
//1.write object
StringWriter sw = new StringWriter();
objectAdapter.write(new JsonWriter(sw), value);
//2.convert object to a map
Map<String, Object> objectMap = mapAdapter.fromJson(sw.toString());
//3.overwrite fields in object to a copy map
value = new LinkedHashMap<String, Object>(value);
value.putAll(objectMap);
//4.write the copy map
mapAdapter.write(out, value);
}
@Override
public Map<String, Object> read(JsonReader in) throws IOException {
//1.create map, all key-value retain in map
Map<String, Object> map = mapAdapter.read(in);
//2.create object from created map
Map<String, Object> object = objectAdapter.fromJsonTree(mapAdapter.toJsonTree(map));
//3.remove fields in object from map
for(String field : objectAdapter.boundFields.keySet()){
map.remove(field);
}
//4.put map to object
object.putAll(map);
return object;
}
}
/**
* If class is extends from some custom map,
* class should implement this to avoid serialize custom map's fields
*/
public interface RetainFieldFlag {}
static class ReflectiveFilterMapFieldFactory extends ReflectiveTypeAdapterFactory{
public ReflectiveFilterMapFieldFactory(ConstructorConstructor constructorConstructor, FieldNamingStrategy fieldNamingPolicy, Excluder excluder) {
super(constructorConstructor, fieldNamingPolicy, excluder);
}
@Override
protected boolean shouldFindFieldInClass(Class willFindClass, Class<?> originalRaw) {
if(RetainFieldFlag.class.isAssignableFrom(originalRaw)){
return RetainFieldFlag.class.isAssignableFrom(willFindClass);
}else{
Class[] endClasses = new Class[]{Object.class, HashMap.class, LinkedHashMap.class,
LinkedTreeMap.class, Hashtable.class, TreeMap.class, ConcurrentHashMap.class,
IdentityHashMap.class, WeakHashMap.class, EnumMap.class};
for(Class c : endClasses){
if(willFindClass == c) return false;
}
}
return super.shouldFindFieldInClass(willFindClass, originalRaw);
}
}
/**
* below code copy from {@link com.google.gson.internal.bind.ReflectiveTypeAdapterFactory}
* (little modify, in source this class is final)
* Type adapter that reflects over the fields and methods of a class.
*/
static class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
private final ConstructorConstructor constructorConstructor;
private final FieldNamingStrategy fieldNamingPolicy;
private final Excluder excluder;
public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
FieldNamingStrategy fieldNamingPolicy, Excluder excluder) {
this.constructorConstructor = constructorConstructor;
this.fieldNamingPolicy = fieldNamingPolicy;
this.excluder = excluder;
}
public boolean excludeField(Field f, boolean serialize) {
return !excluder.excludeClass(f.getType(), serialize) && !excluder.excludeField(f, serialize);
}
private String getFieldName(Field f) {
SerializedName serializedName = f.getAnnotation(SerializedName.class);
return serializedName == null ? fieldNamingPolicy.translateName(f) : serializedName.value();
}
public <T> Adapter<T> create(Gson gson, final TypeToken<T> type) {
Class<? super T> raw = type.getRawType();
if (!Object.class.isAssignableFrom(raw)) {
return null; // it's a primitive!
}
ObjectConstructor<T> constructor = constructorConstructor.get(type);
return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
}
private ReflectiveTypeAdapterFactory.BoundField createBoundField(
final Gson context, final Field field, final String name,
final TypeToken<?> fieldType, boolean serialize, boolean deserialize) {
final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
// special casing primitives here saves ~5% on Android...
return new ReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) {
final TypeAdapter<?> typeAdapter = context.getAdapter(fieldType);
@SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree
@Override void write(JsonWriter writer, Object value)
throws IOException, IllegalAccessException {
Object fieldValue = field.get(value);
TypeAdapter t = new TypeAdapterRuntimeTypeWrapper(context, this.typeAdapter, fieldType.getType());
t.write(writer, fieldValue);
}
@Override void read(JsonReader reader, Object value)
throws IOException, IllegalAccessException {
Object fieldValue = typeAdapter.read(reader);
if (fieldValue != null || !isPrimitive) {
field.set(value, fieldValue);
}
}
};
}
private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {
Map<String, BoundField> result = new LinkedHashMap<String, BoundField>();
if (raw.isInterface()) {
return result;
}
Type declaredType = type.getType();
Class<?> originalRaw = type.getRawType();
while (shouldFindFieldInClass(raw, originalRaw)) {
Field[] fields = raw.getDeclaredFields();
for (Field field : fields) {
boolean serialize = excludeField(field, true);
boolean deserialize = excludeField(field, false);
if (!serialize && !deserialize) {
continue;
}
field.setAccessible(true);
Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
BoundField boundField = createBoundField(context, field, getFieldName(field),
TypeToken.get(fieldType), serialize, deserialize);
BoundField previous = result.put(boundField.name, boundField);
if (previous != null) {
throw new IllegalArgumentException(declaredType
+ " declares multiple JSON fields named " + previous.name);
}
}
type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
raw = type.getRawType();
}
return result;
}
protected boolean shouldFindFieldInClass(Class willFindClass, Class<?> originalRaw){
return willFindClass != Object.class;
}
static abstract class BoundField {
final String name;
final boolean serialized;
final boolean deserialized;
protected BoundField(String name, boolean serialized, boolean deserialized) {
this.name = name;
this.serialized = serialized;
this.deserialized = deserialized;
}
abstract void write(JsonWriter writer, Object value) throws IOException, IllegalAccessException;
abstract void read(JsonReader reader, Object value) throws IOException, IllegalAccessException;
}
public static final class Adapter<T> extends TypeAdapter<T> {
private final ObjectConstructor<T> constructor;
private final Map<String, BoundField> boundFields;
private Adapter(ObjectConstructor<T> constructor, Map<String, BoundField> boundFields) {
this.constructor = constructor;
this.boundFields = boundFields;
}
@Override public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
T instance = constructor.construct();
try {
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
BoundField field = boundFields.get(name);
if (field == null || !field.deserialized) {
in.skipValue();
} else {
field.read(in, instance);
}
}
} catch (IllegalStateException e) {
throw new JsonSyntaxException(e);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
in.endObject();
return instance;
}
@Override public void write(JsonWriter out, T value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
out.beginObject();
try {
for (BoundField boundField : boundFields.values()) {
if (boundField.serialized) {
out.name(boundField.name);
boundField.write(out, value);
}
}
} catch (IllegalAccessException e) {
throw new AssertionError();
}
out.endObject();
}
}
}
static class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
private final Gson context;
private final TypeAdapter<T> delegate;
private final Type type;
TypeAdapterRuntimeTypeWrapper(Gson context, TypeAdapter<T> delegate, Type type) {
this.context = context;
this.delegate = delegate;
this.type = type;
}
@Override
public T read(JsonReader in) throws IOException {
return delegate.read(in);
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public void write(JsonWriter out, T value) throws IOException {
// Order of preference for choosing type adapters
// First preference: a type adapter registered for the runtime type
// Second preference: a type adapter registered for the declared type
// Third preference: reflective type adapter for the runtime type (if it is a sub class of the declared type)
// Fourth preference: reflective type adapter for the declared type
TypeAdapter chosen = delegate;
Type runtimeType = getRuntimeTypeIfMoreSpecific(type, value);
if (runtimeType != type) {
TypeAdapter runtimeTypeAdapter = context.getAdapter(TypeToken.get(runtimeType));
if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {
// The user registered a type adapter for the runtime type, so we will use that
chosen = runtimeTypeAdapter;
} else if (!(delegate instanceof ReflectiveTypeAdapterFactory.Adapter)) {
// The user registered a type adapter for Base class, so we prefer it over the
// reflective type adapter for the runtime type
chosen = delegate;
} else {
// Use the type adapter for runtime type
chosen = runtimeTypeAdapter;
}
}
chosen.write(out, value);
}
/**
* Finds a compatible runtime type if it is more specific
*/
private Type getRuntimeTypeIfMoreSpecific(Type type, Object value) {
if (value != null
&& (type == Object.class || type instanceof TypeVariable<?> || type instanceof Class<?>)) {
type = value.getClass();
}
return type;
}
}
}