Another answer. It took a little longer.
Lateral comment: the above solution will work if you recursively use reflection to develop fields in your class. Then serialize the series with a special serializer using one separate for the parent. This will avoid stackoverflow.
Having said that, I'm a lazy developer, so I like to do something lazy. I am adapting the Google solution for you.
NOTE: PLEASE CHECK THIS AND ADAPT THIS TO YOUR NEEDS. THIS IS A PROTOTYPE AND I DID NOT LOSE THE NECESSARY CODE OR CHECKED FOR POSSIBLE QUESTIONS>
Source code source:
https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java
So this is based on RuntimeTypeAdapterFactory . This factory is provided by google and its goal is to support hierarchical deserialization. To do this, you must register the base class and ALL subclasses, with the property that you would like to add as an identifier. If you read javadocs, it will become much clearer.
This, obviously, offers us what we want: recursively register different adapters for class types that can handle them, and not work in circles and call stackoverflow. With one important problem: you need to register ALL subclasses. This is obviously not suitable (although it can be argued that you can solve the problem with the classpath and simply add all your classes at startup once to be able to use it everywhere). So I looked at the source and changed the code to do this dynamically. Please note that Google warns about this - use it on your own terms :)
Here is my Factory:
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; public final class RuntimeClassNameTypeAdapterFactory<T> implements TypeAdapterFactory { private final Class<?> baseType; private final String typeFieldName; private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>(); private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>(); private RuntimeClassNameTypeAdapterFactory(Class<?> baseType, String typeFieldName) { if (typeFieldName == null || baseType == null) { throw new NullPointerException(); } this.baseType = baseType; this.typeFieldName = typeFieldName; } public static <T> RuntimeClassNameTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) { return new RuntimeClassNameTypeAdapterFactory<T>(baseType, typeFieldName); } public static <T> RuntimeClassNameTypeAdapterFactory<T> of(Class<T> baseType) { return new RuntimeClassNameTypeAdapterFactory<T>(baseType, "class"); } public RuntimeClassNameTypeAdapterFactory<T> 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; } public RuntimeClassNameTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) { return registerSubtype(type, type.getSimpleName()); } public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) { final Map<String, TypeAdapter<?>> labelToDelegate = new LinkedHashMap<String, TypeAdapter<?>>(); final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate = new LinkedHashMap<Class<?>, TypeAdapter<?>>();
I have added ALL imports for you. This is not (really) published in the maven central center, although you can find it here: https://mvnrepository.com/artifact/org.danilopianini/gson-extras/0.1.0
No matter what you need to adapt to make you work for you, I made a copy. The copy is fully compiled, and you can simply paste it into your code and save additional dependency.
The important bits of this code are the following: (and I intentionally left them, but commented out so you can tell)
in create(Gson gson, TypeToken<R> type)
Check if raw can be assigned from String. You want this to apply to every object in the class, so this will take care of that. Pay attention to the code before it looks, if the type is registered in the class, it is no longer needed (accordingly, variables are not needed, you must clear the code)
in @Override public void write(JsonWriter out, R value) throws IOException { :
First, we get rid of the label. Our label is and always will be the name of the source type. This is done in:
String label = srcType.getName();
Secondly, we must distinguish between primitive and object types. Primitive types are strings, integers, etc. In the world of Gson. This means that our check above (adding an adapter) is not captured by the fact that these types of objects are primitive types of grandfathers. So:
if(jsonTree.isJsonPrimitive()) { Streams.write(jsonTree, out);
This will take care of it. If it is primitive, just write the tree to the stream. If this is not the case, we write all the other AND fields in the class field.
JsonObject clone = new JsonObject(); clone.add(typeFieldName, new JsonPrimitive(label)); for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) { clone.add(e.getKey(), e.getValue()); } Streams.write(clone, out);
Fewww - finally, it will take care of it. And here is an example to prove that my code does what (I believe) you need to do;)
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; } }
Now you output this json for you:
{ "class":"google.GsonClassNameTest$X", "test":"asd", "xyz":23, "y_class":{ "class":"google.GsonClassNameTest$Y", "yTest":"asd2", "zTest":{ "class":"google.GsonClassNameTest$Z", "longVal":25, "doubleTest":2.4 } } }
As you can see, strings, long, integers are correctly created. Each recursivley class object also received a class name.
This is a general approach and should work with everything you create. However, if you decide to accept this, do me a favor and write some unit tests;) As I mentioned earlier, I prototyped this implementation.
Hope I get a tick :)
Hi,
Arthur