New issue 231 by limpbizkit: First class support for polymorphism
(subclasses!)
http://code.google.com/p/google-gson/issues/detail?id=231
GSON always operates on the static type of an object. It has no mechanism
to operate on the object's runtime type.
It would be handy if GsonBuilder permitted a way to specify the known
subclasses of a type. Then at serialization/deserialization these fields
could be included:
public void testPolymorphism() {
Rectangle r = new Rectangle();
r.width = 5;
r.height = 7;
Circle c = new Circle();
c.radius = 3;
List<Shape> shapes = new ArrayList<Shape>();
shapes.add(r);
shapes.add(c);
Gson gson = new GsonBuilder()
.create();
String json = gson.toJson(shapes, new TypeToken<List<Shape>>()
{}.getType());
assertEquals("[{\"width\":5,\"height\":7},{\"radius\":3}]", json);
}
static class Shape {}
static class Rectangle extends Shape {
int width;
int height;
}
static class Circle extends Shape {
int radius;
}
It would be extra awesome if it could use the set of fields in a stream to
infer the type to instantiate. In the example above, it could use that
the 'radius' field as evidence that the runtime type should be a Circle. Or
perhaps this could be user-configured too, such as this:
new GsonBuilder()
.runtimeType(Shape.class, Circle.class, "radius")
.runtimeType(Shape.class, Rectangle.class, "width", "height")
.create();
Issue 170 has been merged into this issue.
It would be more robust to support a custom JSON schema that serializes
runtime type information explicitly (akin to xsi:type in XML) rather than
trying to guess the type by field name matching (if two unrelated classes
had the same fields the guess might be wrong). For example, you could have
Gson.setRuntimeTypeProperty(String) and say
gson.setRuntimeTypeProperty("$type");
Thanks,
Adrian Price
Senior Architect
TIBCO Software Inc.
Issue 237 has been merged into this issue.
Issue 238 has been merged into this issue.
Issue 209 has been merged into this issue.
I concur with Adrian, except that it would be best if the JSON property
holding the explicit runtime type were not configurable at all, just for
simplicity's sake. However, you can only do that if the prop name is
something that can't be a Java identifier name, to avoid clashing. I'm
guessing the serializer would have to be configured for whether or not to
output that information, maybe only on particular fields or classes. But
the deserializer would not have to configured; it would simply use the
runtime type if present, and behave the old way if not.
At any rate, I would also like to see this done. Not being able to handle
polymorphism is pretty much a non-starter for a rich data model. Is there a
workaround, even if painful? If so, could you post something showing how it
could be done?
- Ray A. Conner
Issue 129 has been merged into this issue.
Let me give another counter-example (pseudo-code), which I would really
like to turn into json:
class AllPredicate< T > implements Predicate< T > {
List< Predicate< T > > operands;
}
I always avoid specifying a concrete type if an appropriate interface
exists.
I disagree with the suggestion in Comment#6 that the type property name
should be hard-coded. We're talking about a *custom* JSON schema so IMO
such a property needs to be configurable (just as the mechanism for
resolving and deresolving the type name needs to be extensible, to allow
use of other metamodels besides Java, e.g., XSD, UML, EMF). I suppose you
could store the custom type property name in some hard-coded property on
the root JSON object (e.g., gson:type_property) and of course there should
be a sensible default type property name (e.g., gson:type) and a usable
default metamodel (e.g., JavaTypeSystem).
By the way there are other custom JSON schema extensions that are also
required to support *formal modelling* semantics properly, such as the
notion of element identity and the distinction between UML-style
composition aggregation associations versus non-aggregating associations.
As things currently stand a Java object graph in which multiple elements
refer to a single object, the referenced object will be replicated in the
serialized JSON and will deserialize into multiple identical objects rather
than a single instance as in the original. Not at all what is required of a
non-aggregating association!
Adrian Price
I'll admit it's a bad idea to hard-code that. My main concern was keeping
the number of things that the programmer has to do to a minimum, convention
over configuration. I've found that the more you make a programmer do,
especially if it has to be consistent in two or more places, the more
likely it will be done incorrectly.
You should try the Hierarchical Type Adapter feature to see if this helps
simplify your problem. Basically, it appears that you will need to push the
sub-classing logic into it.
The official announcement for the release is here:
http://groups.google.com/group/google-gson/browse_thread/thread/6272c9be58676e47
Comment #12 on issue 231 by joel.leitch: First class support for
polymorphism (subclasses!)
http://code.google.com/p/google-gson/issues/detail?id=231
Fixed in r828.
Issue 321 has been merged into this issue.
Comment #14 on issue 231 by limpbiz...@gmail.com: First class support for
polymorphism (subclasses!)
http://code.google.com/p/google-gson/issues/detail?id=231
This wasn't fixed by r828. But I'm going to implement this. Here's my
proposed API:
RuntimeTypeAdapter<BillingInstrument> grta
= new RuntimeTypeAdapter(BillingInstrument.class, "type");
grta.registerSubtype(CreditCard.class, "CC");
grta.registerSubtype(Paypal.class, "PayPal");
grta.registerSubtype(BankTransfer.class); // defaults to the simple
name, "BankTransfer"
This would synthesize a type field in the emitted JSON as a hint during
deserialization:
/*
* "billingInstruments": [
* {
* "type": "CC",
* "cvv": 234
* },
* {
* "type": "PayPal",
* "email", "je...@swank.ca"
* }
* ]
*/
Again, I urgently entreat you *not* to hard code the custom schema in the
way suggested by comment #14. For a start, the namespace for 'meta-type'
attributes like this *must* be something that won't collide with
application metamodels. If you hard code it as 'type' then you won't be
able to serialize any object that has a legitimate 'type' attribute. They
made the same mistake in SDO and it ruined an otherwise lovely
specification. At the very least, provide a mechanism for customizing such
meta-attribute names.
@adrianp agreed. Thats howhy 'new RuntimeTypeAdapter' takes two arguments!
Ahh... I get it now. Sorry, I should have studied the code snippet more
carefully. Looks good! :-)
@comment 13: is there a build available ? I really need polymorphism when
serialising. The 1.7.1 release does not seem to support it correctly.
It isn't done yet! But you can take a look at RuntimeTypeAdapter.java in
svn if you'd like something urgently.
I tried with a build of trunk and it fixes my serialisation issues. So
hopefully there will be a new release coming soon. There were 4 unittest
failing in the trunk.
Yes, comment 13 would be very useful!
Yes, comment 14 would be very useful!
Yes, comment 14 would be very useful! Another way that you can get around
this solution as a coder, if you are using polymorphism in a List: for
your abstract class, keep a counter variable in it, and save
separate "subclasses" in the counter for order. When serializing, create
separate saved files as Json strings to keep track of what subclass you are
using. Then when you deserialize the data to information and load it into
each of your classes, load the data to each of your subclasses in the order
your abstract class detailed the order as.
Would be great to see something like Comment #14 appear :)
What would happen if "type" : "..." was not in the json example? It should
still be possible determine the correct subtype based on the contents of
the json. Could the API outsource this test? The only other alternative is
that multiple RuntimeTypeAdapter's are created... and that seems very
problematic too...
Don't read too deeply into this code, I'm shooting from the hip...
RuntimeTypeAdapter<BillingInstrument> grta = new
RuntimeTypeAdapter(BillingInstrument.class);
grta.registerSubtype(CreditCard.class, new IsSubtype(){
@override
public boolean isSubType(JsonElement jsonElement){
//if the json has a "cvv" then yes, its a CreditCard. But this
could be something far more complicated...
return jsonElement.getAsJsonObject().get("cvv") != null;
}
});
Other notes, would reflection/introspection be of use to produce re-usable
IsSubType's? If so, then the IsSubtype should probably have access to the
class also (and not just the jsonElement alone).
@ahhughes: detecting based on the fields is possible but definitely not
very reliable. If you'd like to do that, you should write your own type
adapter!
Is it possible to divide serialization to jsonTree from deserialization
from jsonTree? Implementation of serialization looking to be very
straightforward (without using marker field), and in some cases it is
enough to just serialize bean and not deserialize.
In my case (website) I'm using strictly typed deserialization to bean (ajax
posts) and dynamic typed serialization (event mechanism - events are sent
back to client and processed via JS).
I notice that the "RuntimeTypeAdapterFactory" class is at the moment
in "extras", will it be part of the 2.0 release anyway?
I notice that the "RuntimeTypeAdapterFactory" class is at the moment
in "extras", will it be part of the next release anyway?
RuntimeTypeAdapterFactory won't be in GSON 2.0.
There is a simple solution for this..override the default
CollectionTypeAdapter to not use the defined parameterized type.
for example:
private class RunTimeTypeCollectionAdapter implements
JsonSerializer<Collection> {
@Override
public JsonElement serialize(Collection src, Type typeOfSrc,
JsonSerializationContext context) {
if (src == null) {
return null;
}
JsonArray array = new JsonArray();
for (Object child : src) {
JsonElement element = context.serialize(child);
array.add(element);
}
return array;
}
}
in your gson builder, use this:
new GsonBuilder().registerTypeHierarchyAdapter(Collection.class, new
RunTimeTypeCollectionAdapter())
In the current implementation of RuntimeTypeAdapterFactory, the Shape class
is forbidden to have a field that can be used as type. This is sometimes
desirable when you know the limited number of shapes you are going to use:
enum ShapeType {
RECTANGLE, CIRCLE
}
static class Shape {
private final ShapeType shapeType;
protected Shape(ShapeType shapeType) {
this.shapeType = shapeType;
}
}
Now, if I use:
RuntimeTypeAdapterFactory shapeTypeAdapterFactory =
RuntimeTypeAdapterFactory.of(Shape.class, "shapeType");
shapeTypeAdapterFactory.registerSubType(Rectangle.class);
shapeTypeAdapterFactory.registerSubType(Circle.class);
new
GsonBuilder().registerTypeAdapaterFactory(shapeTypeAdapterFactory).create();
This will result in a runtime error:
com.google.gson.JsonParseException: cannot serialize Rectangle because it
already defines a field named shapeType
at
com.trymph.definition.gson.RuntimeTypeAdapterFactory$1.write(RuntimeTypeAdapterFactory.java:228)
at
com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
at
com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.write(MapTypeAdapterFactory.java:240)
at
com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.write(MapTypeAdapterFactory.java:1)
at
com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
at
com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89)
at
com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195)
at com.google.gson.Gson.toJson(Gson.java:582)
at com.google.gson.Gson.toJson(Gson.java:561)
at com.google.gson.Gson.toJson(Gson.java:516)
Suggestions on how to revise RuntimeTypeAdapterFactory which will take care
of this situation better?
What about pre-pending a character that is illegal in a java field so there
won't be collisions?