For example, there are two classes
class A {
public int key;
public B b;
}
class B {
private final int key;
public int x;
public int y;
public B(int key) {
this.key = key;
}
}
So field key of inner B instance must be exactly as field key of outer A instance.
B's instance creator is:
public class B_InstanceCreator implements InstanceCreator<B> {
private final int key;
public B_InstanceCreator(int key) {
this.key = key;
}
@Override
public B createInstance(Type type) {
return new B(key);
}
}
How can I implement type adapter for A, which creates (and then uses to deserialize inner B) B_InstanceCreator just after extracting key?
Gson seems to be extremely stateless, and it hurts sometimes. The same story is relevant to
InstanceCreatorwhere the context is passed via a constructor or setters of an object implementing this interface like you menitoned above. Obtaining thekeyis only possible when deserializing theAclass since the classes of the child nodes are not aware of its parent object context. So you have to implement a deserializer for theAclass first (unfortunately losing annotations like@SerializedName,@Expose,@JsonAdapter...):As you can see above, its another performance-weak place is
createInnerGsonthat instantiates a Gson builder, an instance creator, and finally the inner Gson instance behind the scenes (don't know how much it costs). But it seems to be the only way to pass thekeycontext to the instance creator. (It would be nice if Gson could implement afromJsonmethod to overwrite the state of aBinstance (say, something likegson.merge(someJson, someBInstance)) -- but Gson can only produce new deserialized values.)The JSON deserializer can be used as follows (assuming
AandBhave pretty-print implemented in their respectivetoStringmethods):Output:
V2
You know, I have an idea...
JsonDeserializer<A>probably was not that a good idea requiring semi-manualAdeserializing. However, an alternatve solution is nesting a real deserializer into a sort of a "shadow" generic deserializer, thus delegating the whole deserialiation job to Gson. The only thing here is that you have to remember that "shadow root" Gson instances should be used for the objects you mentioned as top-most (the root), otherwise it won't work as you might expect or might affect performance (see how nested Gson instances are obtained below).GsonFactoryJsonDeserializer.java
Let's first take a quick look on this class. It does not make deserialization itself and just asks for another Gson instance to perform deserialization. It has two factory methods: one for direct mapping between
JsonElementtoGson, and one for having two operations like extracting a key fromJsonElementand delegating the key to theGsonfactory (remember the specifics ofInstanceCreatordiscussed above?). This deserializer actually does nothing more than just obtaining the root JSON element.Demo
The demo below encapsulates a
GsonBuilderfactory method in order to create a newGsonBuilderinstance on demand because Gson builders are stateful and using the same instance may leed to pretty unexpected behavior.gsonFactoryJsonDeserializeris using the second overload: to separate key extraction and registering theBclass instantiator. If the keys in outer and inner objects does not match (a.keymust equala.b.key), the application should throw an assertion error that should never happen in production.Output:
Note that I didn't make any performance testing, and unfortunately creating new Gson instances seems to be the only way of doing what you were asking for.