import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonPrimitive; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; import com.google.gson.internal.Streams; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; /** * Adapts values whose runtime type may differ from their declaration type. This * is necessary when a field's type is not the same type that GSON should create * when deserializing that field. For example, consider these types: *
{@code * abstract class Shape { * int x; * int y; * } * class Circle extends Shape { * int radius; * } * class Rectangle extends Shape { * int width; * int height; * } * class Diamond extends Shape { * int width; * int height; * } * class Drawing { * Shape bottomShape; * Shape topShape; * } * }
*
Without additional type information, the serialized JSON is ambiguous. Is * the bottom shape in this drawing a rectangle or a diamond?
* This class addresses this problem by adding type information to the * serialized JSON and honoring that type information when the JSON is * deserialized:
* Both the type field name ({@code "type"}) and the type labels ({@code * "Rectangle"}) are configurable. * *
Registering Types
* Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field * name to the {@link #of} factory method. If you don't supply an explicit type * field name, {@code "type"} will be used.
* Next register all of your subtypes. Every subtype must be explicitly * registered. This protects your application from injection attacks. If you * don't supply an explicit type label, the type's simple name will be used. *
*/ public final class RuntimeClassNameTypeAdapterFactory implements TypeAdapterFactory { private final Class> baseType; private final String typeFieldName; private final Map> labelToSubtype = new LinkedHashMap>(); private final Map, String> subtypeToLabel = new LinkedHashMap, String>(); private RuntimeClassNameTypeAdapterFactory(Class> baseType, String typeFieldName) { if (typeFieldName == null || baseType == null) { throw new NullPointerException(); } this.baseType = baseType; this.typeFieldName = typeFieldName; } /** * Creates a new runtime type adapter using for {@code baseType} using {@code * typeFieldName} as the type field name. Type field names are case sensitive. */ public static RuntimeClassNameTypeAdapterFactory of(Class baseType, String typeFieldName) { return new RuntimeClassNameTypeAdapterFactory(baseType, typeFieldName); } /** * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as * the type field name. */ public static RuntimeClassNameTypeAdapterFactory of(Class baseType) { return new RuntimeClassNameTypeAdapterFactory(baseType, "class"); } /** * Registers {@code type} identified by {@code label}. Labels are case * sensitive. * * @throws IllegalArgumentException if either {@code type} or {@code label} * have already been registered on this type adapter. */ public RuntimeClassNameTypeAdapterFactory registerSubtype(Class extends T> type, String label) { if (type == null || label == null) { throw new NullPointerException(); } if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { throw new IllegalArgumentException("types and labels must be unique"); } labelToSubtype.put(label, type); subtypeToLabel.put(type, label); return this; } /** * Registers {@code type} identified by its {@link Class#getSimpleName simple * name}. Labels are case sensitive. * * @throws IllegalArgumentException if either {@code type} or its simple name * have already been registered on this type adapter. */ public RuntimeClassNameTypeAdapterFactory registerSubtype(Class extends T> type) { return registerSubtype(type, type.getSimpleName()); } public TypeAdapter create(Gson gson, TypeToken type) { final Map> labelToDelegate = new LinkedHashMap>(); final Map, TypeAdapter>> subtypeToDelegate = new LinkedHashMap, TypeAdapter>>(); // && !String.class.isAssignableFrom(type.getRawType()) if(Object.class.isAssignableFrom(type.getRawType()) ) { TypeAdapter> delegate = gson.getDelegateAdapter(this, type); labelToDelegate.put("class", delegate); subtypeToDelegate.put(type.getRawType(), delegate); } // for (Map.Entry> entry : labelToSubtype.entrySet()) { // TypeAdapter> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); // labelToDelegate.put(entry.getKey(), delegate); // subtypeToDelegate.put(entry.getValue(), delegate); // } return new TypeAdapter() { @Override public R read(JsonReader in) throws IOException { JsonElement jsonElement = Streams.parse(in); JsonElement labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); if (labelJsonElement == null) { throw new JsonParseException("cannot deserialize " + baseType + " because it does not define a field named " + typeFieldName); } String label = labelJsonElement.getAsString(); @SuppressWarnings("unchecked") // registration requires that subtype extends T TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label); if (delegate == null) { throw new JsonParseException("cannot deserialize " + baseType + " subtype named " + label + "; did you forget to register a subtype?"); } return delegate.fromJsonTree(jsonElement); } @Override public void write(JsonWriter out, R value) throws IOException { Class> srcType = value.getClass(); String label = srcType.getName(); @SuppressWarnings("unchecked") // registration requires that subtype extends T TypeAdapter delegate = (TypeAdapter) subtypeToDelegate.get(srcType); if (delegate == null) { throw new JsonParseException("cannot serialize " + srcType.getName() + "; did you forget to register a subtype?"); } JsonElement jsonTree = delegate.toJsonTree(value); if(jsonTree.isJsonPrimitive()) { Streams.write(jsonTree, out); } else { JsonObject jsonObject = jsonTree.getAsJsonObject(); if (jsonObject.has(typeFieldName)) { throw new JsonParseException("cannot serialize " + srcType.getName() + " because it already defines a field named " + typeFieldName); } JsonObject clone = new JsonObject(); clone.add(typeFieldName, new JsonPrimitive(label)); for (Map.Entry e : jsonObject.entrySet()) { clone.add(e.getKey(), e.getValue()); } Streams.write(clone, out); } } }.nullSafe(); } }
JsonObject clone = new JsonObject(); clone.add(typeFieldName, new JsonPrimitive(label)); for (Map.Entry e : jsonObject.entrySet()) { clone.add(e.getKey(), e.getValue()); } Streams.write(clone, out);
public class GsonClassNameTest { static Gson create = new GsonBuilder().registerTypeAdapterFactory(RuntimeClassNameTypeAdapterFactory.of(Object.class)).create(); public static void main(String[] args) { String json = create.toJson(new X()); System.out.println(json); } public static class X { public String test = "asd"; public int xyz = 23; public Y y_class = new Y(); } public static class Y { String yTest = "asd2"; Z zTest = new Z(); } public static class Z { long longVal = 25; double doubleTest = 2.4; } }
package com.mycompany.javatest; import com.google.gson.*; import java.lang.reflect.*; import org.junit.*; public class JavaTest { public static class GenericSerializer implements JsonSerializer
我自己尝试了这个似乎工作:
public class GsonClassNameTest { public static void main(String[] args) { Gson create = new GsonBuilder().registerTypeHierarchyAdapter(Object.class, new ODeserialiser()).create(); String json = create.toJson(new X()); System.out.println(json); } public static class ODeserialiser implements JsonSerializer { @Override public JsonElement serialize(Object src, Type typeOfSrc, JsonSerializationContext context) { Gson gson = new Gson(); JsonElement serialize = gson.toJsonTree(src); JsonObject o = (JsonObject) serialize; o.addProperty("class", src.getClass().getName()); return serialize; } } public static class X { public String test = "asd"; } }
* Disclaimer: taken from here https://stackoverflow.com/a/40133286/285091 with some modifications *
* * Adapts values whose runtime type may differ from their declaration type. This * is necessary when a field's type is not the same type that GSON should create * when deserializing that field. For example, consider these types: *
{@code * abstract class Shape { * int x; * int y; * } * class Circle extends Shape { * int radius; * } * class Rectangle extends Shape { * int width; * int height; * } * class Diamond extends Shape { * int width; * int height; * } * class Drawing { * Shape bottomShape; * Shape topShape; * } * }
*
Without additional type information, the serialized JSON is ambiguous. Is * the bottom shape in this drawing a rectangle or a diamond?
* This class addresses this problem by adding type information to the * serialized JSON and honoring that type information when the JSON is * deserialized:
* Both the type field name ({@code "type"}) and the type labels ({@code * "Rectangle"}) are configurable. *
*
Registering Types
* Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field * name to the {@link #of} factory method. If you don't supply an explicit type * field name, {@code "type"} will be used.
* Next register all of your subtypes. Every subtype must be explicitly * registered. This protects your application from injection attacks. If you * don't supply an explicit type label, the type's simple name will be used. *
*/ public final class RuntimeClassNameTypeAdapterFactory implements TypeAdapterFactory { private final Class> baseType; private final String typeFieldName; private final Map> labelToSubtype = new LinkedHashMap>(); private final Map, String> subtypeToLabel = new LinkedHashMap, String>(); private RuntimeClassNameTypeAdapterFactory(Class> baseType, String typeFieldName) { if (typeFieldName == null || baseType == null) { throw new NullPointerException(); } this.baseType = baseType; this.typeFieldName = typeFieldName; } /** * Creates a new runtime type adapter using for {@code baseType} using {@code * typeFieldName} as the type field name. Type field names are case sensitive. */ public static RuntimeClassNameTypeAdapterFactory of(Class baseType, String typeFieldName) { return new RuntimeClassNameTypeAdapterFactory(baseType, typeFieldName); } /** * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as * the type field name. */ public static RuntimeClassNameTypeAdapterFactory of(Class baseType) { return new RuntimeClassNameTypeAdapterFactory(baseType, "class"); } /** * Registers {@code type} identified by {@code label}. Labels are case * sensitive. * * @throws IllegalArgumentException if either {@code type} or {@code label} * have already been registered on this type adapter. */ public RuntimeClassNameTypeAdapterFactory registerSubtype(Class extends T> type, String label) { if (type == null || label == null) { throw new NullPointerException(); } if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { throw new IllegalArgumentException("types and labels must be unique"); } labelToSubtype.put(label, type); subtypeToLabel.put(type, label); return this; } /** * Registers {@code type} identified by its {@link Class#getSimpleName simple * name}. Labels are case sensitive. * * @throws IllegalArgumentException if either {@code type} or its simple name * have already been registered on this type adapter. */ public RuntimeClassNameTypeAdapterFactory registerSubtype(Class extends T> type) { return registerSubtype(type, type.getSimpleName()); } public TypeAdapter create(Gson gson, TypeToken type) { final Map> labelToDelegate = new LinkedHashMap>(); final Map, TypeAdapter>> subtypeToDelegate = new LinkedHashMap, TypeAdapter>>(); // && !String.class.isAssignableFrom(type.getRawType()) if (Object.class.isAssignableFrom(type.getRawType())) { TypeAdapter> delegate = gson.getDelegateAdapter(this, type); labelToDelegate.put(type.getRawType().getName(), delegate); subtypeToDelegate.put(type.getRawType(), delegate); } // for (Map.Entry> entry : labelToSubtype.entrySet()) { // TypeAdapter> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); // labelToDelegate.put(entry.getKey(), delegate); // subtypeToDelegate.put(entry.getValue(), delegate); // } return new TypeAdapter() { @SuppressWarnings("unchecked") @Override public R read(JsonReader in) throws IOException { JsonElement jsonElement = Streams.parse(in); if (jsonElement.isJsonObject()) { JsonElement labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); if (labelJsonElement == null) { throw new JsonParseException("cannot deserialize " + baseType + " because it does not define a field named " + typeFieldName); } String label = labelJsonElement.getAsString(); TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label); if (delegate == null) { Class aClass; try { aClass = (Class) Class.forName(label); } catch (ClassNotFoundException e) { throw new JsonParseException("Cannot find class " + label, e); } TypeToken subClass = TypeToken.get(aClass); delegate = gson.getDelegateAdapter(RuntimeClassNameTypeAdapterFactory.this, subClass); if (delegate == null) { throw new JsonParseException("cannot deserialize " + baseType + " subtype named " + label + "; did you forget to register a subtype?"); } } return delegate.fromJsonTree(jsonElement); } else if (jsonElement.isJsonNull()) { return null; } else { TypeAdapter delegate = gson.getDelegateAdapter(RuntimeClassNameTypeAdapterFactory.this, type); if (delegate == null) { throw new JsonParseException("cannot deserialize " + baseType + "; did you forget to register a subtype?"); } return delegate.fromJsonTree(jsonElement); } } @Override public void write(JsonWriter out, R value) throws IOException { Class> srcType = value.getClass(); String label = srcType.getName(); TypeAdapter delegate = getDelegate(srcType); if (delegate == null) { throw new JsonParseException("cannot serialize " + srcType.getName() + "; did you forget to register a subtype?"); } JsonElement jsonTree = delegate.toJsonTree(value); if (!jsonTree.isJsonObject()) { Streams.write(jsonTree, out); } else { JsonObject jsonObject = jsonTree.getAsJsonObject(); if (jsonObject.has(typeFieldName)) { throw new JsonParseException("cannot serialize " + srcType.getName() + " because it already defines a field named " + typeFieldName); } JsonObject clone = new JsonObject(); clone.add(typeFieldName, new JsonPrimitive(label)); for (Map.Entry e : jsonObject.entrySet()) { clone.add(e.getKey(), e.getValue()); } Streams.write(clone, out); } } @SuppressWarnings("unchecked") private TypeAdapter getDelegate(Class> srcType) { TypeAdapter> typeAdapter = subtypeToDelegate.get(srcType); if (typeAdapter != null) { return (TypeAdapter) typeAdapter; } for (Map.Entry, TypeAdapter>> classTypeAdapterEntry : subtypeToDelegate.entrySet()) { if (classTypeAdapterEntry.getKey().isAssignableFrom(srcType)) { return (TypeAdapter) classTypeAdapterEntry.getValue(); } } return null; } }.nullSafe(); } }