Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First class support for polymorphism (subclasses!) #231

Open
GoogleCodeExporter opened this issue Mar 19, 2015 · 51 comments
Open

First class support for polymorphism (subclasses!) #231

GoogleCodeExporter opened this issue Mar 19, 2015 · 51 comments

Comments

@GoogleCodeExporter
Copy link

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();

Original issue reported on code.google.com by limpbizkit on 28 Aug 2010 at 6:00

@GoogleCodeExporter
Copy link
Author

Issue 170 has been merged into this issue.

Original comment by limpbizkit on 28 Aug 2010 at 6:03

@GoogleCodeExporter
Copy link
Author

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.

Original comment by adrianp....@gtempaccount.com on 1 Oct 2010 at 1:10

@GoogleCodeExporter
Copy link
Author

Issue 237 has been merged into this issue.

Original comment by limpbizkit on 6 Oct 2010 at 5:49

@GoogleCodeExporter
Copy link
Author

Issue 238 has been merged into this issue.

Original comment by limpbizkit on 6 Oct 2010 at 5:57

@GoogleCodeExporter
Copy link
Author

Issue 209 has been merged into this issue.

Original comment by limpbizkit on 6 Oct 2010 at 6:35

@GoogleCodeExporter
Copy link
Author

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

Original comment by ray.a.co...@gmail.com on 27 Oct 2010 at 9:25

@GoogleCodeExporter
Copy link
Author

Issue 129 has been merged into this issue.

Original comment by limpbizkit on 4 Nov 2010 at 10:44

@GoogleCodeExporter
Copy link
Author

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.

Original comment by ray.a.co...@gmail.com on 11 Nov 2010 at 12:10

@GoogleCodeExporter
Copy link
Author

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

Original comment by adrianp....@gtempaccount.com on 11 Nov 2010 at 2:12

@GoogleCodeExporter
Copy link
Author

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.

Original comment by ray.a.co...@gmail.com on 11 Nov 2010 at 2:46

@GoogleCodeExporter
Copy link
Author

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

Original comment by joel.leitch@gmail.com on 13 Apr 2011 at 4:22

@GoogleCodeExporter
Copy link
Author

Fixed in r828.

Original comment by joel.leitch@gmail.com on 20 Apr 2011 at 10:35

  • Changed state: Fixed

@GoogleCodeExporter
Copy link
Author

Issue 321 has been merged into this issue.

Original comment by limpbizkit on 4 May 2011 at 9:31

@GoogleCodeExporter
Copy link
Author

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", "jesse@swank.ca"
     *       }
     *    ]
     */

Original comment by limpbizkit on 5 May 2011 at 7:00

  • Changed state: Started

@GoogleCodeExporter
Copy link
Author

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.

Original comment by adrianp....@gtempaccount.com on 7 May 2011 at 12:51

@GoogleCodeExporter
Copy link
Author

@adrianp agreed. Thats howhy 'new RuntimeTypeAdapter' takes two arguments!

Original comment by limpbizkit on 7 May 2011 at 3:54

@GoogleCodeExporter
Copy link
Author

Ahh... I get it now. Sorry, I should have studied the code snippet more 
carefully. Looks good! :-)

Original comment by adrianp....@gtempaccount.com on 7 May 2011 at 5:42

@GoogleCodeExporter
Copy link
Author

@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.

Original comment by david.nouls on 1 Jun 2011 at 2:12

@GoogleCodeExporter
Copy link
Author

It isn't done yet! But you can take a look at RuntimeTypeAdapter.java in svn if 
you'd like something urgently.

Original comment by jessewil...@google.com on 1 Jun 2011 at 8:27

@GoogleCodeExporter
Copy link
Author

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.

Original comment by david.nouls on 6 Jun 2011 at 8:41

@GoogleCodeExporter
Copy link
Author

[deleted comment]

1 similar comment
@GoogleCodeExporter
Copy link
Author

[deleted comment]

@GoogleCodeExporter
Copy link
Author

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.

Original comment by jshlcl...@gmail.com on 28 Jun 2011 at 8:29

@GoogleCodeExporter
Copy link
Author

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).

Original comment by ahhug...@gmail.com on 29 Jun 2011 at 12:33

@GoogleCodeExporter
Copy link
Author

@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!

Original comment by jessewil...@google.com on 29 Jun 2011 at 4:16

@GoogleCodeExporter
Copy link
Author

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).

Original comment by oxygene.core on 31 Aug 2011 at 12:44

@GoogleCodeExporter
Copy link
Author

[deleted comment]

@GoogleCodeExporter
Copy link
Author

RuntimeTypeAdapterFactory won't be in GSON 2.0.

Original comment by limpbizkit on 19 Oct 2011 at 5:54

@GoogleCodeExporter
Copy link
Author

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())

Original comment by tommytcc...@gmail.com on 9 Nov 2011 at 11:46

@GoogleCodeExporter
Copy link
Author

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?


Original comment by inder123 on 14 Apr 2012 at 7:21

@GoogleCodeExporter
Copy link
Author

What about pre-pending a character that is illegal in a java field so there 
won't be collisions? 

Original comment by l...@ghue.net on 15 Apr 2012 at 3:37

@GoogleCodeExporter
Copy link
Author

Hi,

I need to know how we can deserialize an object which contains a list of type 
abstract class as an attribute using gson

code eg:

class A{
List<B> list;
}

public abstract class B{
}
..

Original comment by chinju18...@gmail.com on 24 May 2012 at 8:28

@GoogleCodeExporter
Copy link
Author

Any movement on this? Comment #14 seems like an option. Not sure if #30 made it 
in either. I thought gson would have had this down easy due to its popularity.

Original comment by m...@foursquare.com on 30 Jun 2012 at 5:18

@GoogleCodeExporter
Copy link
Author

Implemented by:

http://code.google.com/p/google-gson/source/browse/trunk/extras/src/main/java/co
m/google/gson/typeadapters/RuntimeTypeAdapterFactory.java

We aren't (yet) including this in the core API so your best bet is to include 
the source in your app.

Original comment by limpbizkit on 5 Jul 2012 at 7:37

@GoogleCodeExporter
Copy link
Author

The current javadoc is incorrect. One should read the following:

    RuntimeTypeAdapterFactory<Shape> shapeAdapter = RuntimeTypeAdapterFactory.of(Shape.class)
                        .registerSubtype(Rectangle.class)
                        .registerSubtype(Circle.class)
                .registerSubtype(Diamond.class);

    Gson gson = new GsonBuilder().registerTypeAdapterFactory(shapeAdapter).create();

    Shape rectangle = new Rectangle(..);
    jsonString = gson.toJson(rectangle, Shape.class);

Original comment by pjpi...@gmail.com on 27 Jul 2012 at 12:02

@GoogleCodeExporter
Copy link
Author

Dear all,

This RuntimeTypeAdapterFactory is the solution for registering the Type and 
subType.

As per our requirement : if custom object comes from server then i will define 
only one top level type which is pass as in fromJson() api's second parameter,

Runtime it will manage all suptypes.
Does it possible to manage this without type and subtype registration ?

Lets take one example.

Class A{
 // fields 
}

Class B extends A{
  // fields 
}

Class C extends A{
  // fields 
}

Class D extends A{
  // fields 
}


Class Test extends A{
  public List<A> contents = new ArrayList<A>(); // it may contain A, B, C etc.
  private boolean isHeader = false;
  private String myTestString = "test";
}

Is there any way to write without registering type and subtype?

as here define runtimeTypeAdapter in TypeAdapterRuntimeTypeWrapper,
is there any other solution put logic here or other place?

is in read logic as per "type" can we create runtime adapter as per "type" 
coming?
it coming in ReflectiveTypeAdapterFactory in read() of Adapter<T> 










Original comment by pareshpv...@gmail.com on 11 Sep 2012 at 2:33

@GoogleCodeExporter
Copy link
Author

If I understand this Issue (231) correctly (which i may not), are we not 
talking about the limitation of not being able to deserialize arbitary 
collections of objects? If that is the case, a reasonable solution would seem 
to be easy enough: just have a config option to (optionally) serialize an 
object's java class along with its other fields. The field could be named 
something like com_google_gson_java_class and simply be a string with the class 
name. Easy to serialize. Easy to deserialize.

Original comment by rhd...@gmail.com on 26 Apr 2013 at 4:25

@GoogleCodeExporter
Copy link
Author

Re. #38, we're not talking specifically about collections per se, the principle 
applies as much to single-valued as multi-valued properties. It's more about 
preserving runtime type information in the JSON so that the original object 
class can be re-instantiated by custom type adapters when deserializing the 
JSON. As I mentioned in #9, the type scheme needs to be extensible so that it 
can be made to work with XSD, UML, EMF, not specifically the Java type system. 
For example, I'd like to be able to serialize dynamic EMF EObjects - these are 
all instances of org.eclipse.emf.ecore.impl.DynamicEObjectImpl but their eClass 
is different and it is their qualified EClass name (e.g., as 
package-ns-prefix:type) that one would wish to serialize as the runtime type 
name.

Original comment by adrianp....@gtempaccount.com on 27 Apr 2013 at 6:13

@GoogleCodeExporter
Copy link
Author

Thanks for the RuntimeTypeAdapterFactory guys, been using it in some production 
code and it is working quickly and as expected for handling a collection of 
JSON objects which can be one of many types.

Original comment by DavidTP...@gmail.com on 24 Sep 2013 at 1:11

@GoogleCodeExporter
Copy link
Author

Hey guys.
A simple case I try to solve with RuntimeTypeAdapterFactory of yours.
JSON field can be either an JSONObject or String.
How would I parse it?

Original comment by kamui.ji...@gmail.com on 3 Feb 2014 at 7:19

@GoogleCodeExporter
Copy link
Author

What's the hold up on this - it seems like there were perfectly good 
suggestions made, and implementation created.  Can you share the thinking 
behind why its not yet in the library?

Original comment by nbpr...@gmail.com on 6 Feb 2014 at 3:33

@GoogleCodeExporter
Copy link
Author

Check out https://github.com/julman99/gson-fire, it has this feature and other 
ones that personally I think they are missing from Gson.

Original comment by julio.vi...@gmail.com on 27 Feb 2014 at 5:36

@GoogleCodeExporter
Copy link
Author

Hi!

I've tried using the RuntimeTypeAdapterFactory, and it works fine, except for 
one small caveat: when serializing objects within lists within objects, the 
type field is left out. For example, this is what I expect:

{"$type":"Area","locations":[{"$type":"Location", 
"coordinateX":34.0,"coordinateY":57.0,"wifiMorsels":[...]}

But this is what I get:

{"$type":"Area","locations":[{"coordinateX":34.0,"coordinateY":57.0,"wifiMorsels
":[...]}

Note that the Area object is also within an object (meaning that simple nested 
objects works) and that wifiMorsels is another list where the contained objects 
lack the type field and value.

In my case I'll be using the same code on both sides of the JSON-based 
communication, so I require the $type field to work.

Any help is greatly appreciated!

Original comment by tamino.h...@gmail.com on 17 Mar 2014 at 6:24

@kiptoomm
Copy link

Was this ever released? I'm using Gson 2.3.1 and don't see it..

@Cy4n1d3
Copy link

Cy4n1d3 commented Nov 13, 2015

I don't think so.
Guess I'll either have to write some generic converters or switch back to Jackson, as I rely on loads of Collections filled with abstract classes for a project...

@JonasVautherin
Copy link

GsonFire does something that could help (see "Type Selectors" here)

@Cy4n1d3
Copy link

Cy4n1d3 commented Apr 7, 2016

Didn't know about that, thanks for the heads up!
Might come in handy some day 👍

@subha-sam
Copy link

subha-sam commented Jul 26, 2016

Hey guys ,
I have a similar usecase as this
Class A{
// fields
}

Class B extends A{
// fields
}

Class C extends A{
// fields
}

Class D extends A{
// fields
}

Class Test extends A{
public List contents = new ArrayList(); // it may contain A, B, C etc.
private boolean isHeader = false;
private String myTestString = "test";
}
I am trying to serialize "contents " collection using gson libarary.

can someone tell me whats the solution for this. I found using RuntimeTypeAdaptorFactory would solve it but, I using gson 2..5, in that version I dont see this class.

Struck with this issues for a past three days. writing custom serializations code for my usecase runs for pages.kindly help. whats the work around that the current gson library is having.

@inder123
Copy link
Collaborator

Copy over RuntimeTypeAdapterFactory code. It is meant to be used as copied source-code.

@mgerhardy
Copy link

mgerhardy commented Mar 27, 2017

Add pull request #1055 that shows a few issues regarding Collection. I would be glad about feedback.

My problem is that I need to deserialize Collections at runtime - thus I can't use the new TypeToken<List>() {}.getType() syntax. I only have a Class instance. Do I have to register some special type of Collection adapter or JsonDeserializer? I tried to add a deserializer but this wasn't possible because I lack needed types for fromJson calls.

	private class RunTimeTypeCollectionAdapter implements JsonDeserializer<Collection> {
		@Override
		@SuppressWarnings({ "rawtypes", "unchecked" })
		public Collection deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
				throws JsonParseException {
			if (json == null) {
				return null;
			}
			final TypeToken<?> typeToken = TypeToken.get(typeOfT);
			try {
				Class<?> collectionClass = typeToken.getRawType();
				if (collectionClass == List.class) {
					collectionClass = ArrayList.class;
				} else if (collectionClass == Set.class) {
					collectionClass = HashSet.class;
				} else if (collectionClass == Map.class) {
					collectionClass = HashMap.class;
				}
				final Collection collection = (Collection) Class.forName(collectionClass.getTypeName()).newInstance();
				final Class<?> rawType = typeToken.getRawType();
				if (json.isJsonArray()) {
					final JsonArray array = json.getAsJsonArray();
					for (int i = 0; i < array.size(); ++i) {
						final JsonElement element = array.get(i);
						final Object deserialize = context.deserialize(element, rawType);
						collection.add(deserialize);
					}
				} else {
					// final Object deserialize = context.deserialize(json,
					// rawType);
					// collection.add(deserialize);
				}
				return collection;
			} catch (final Exception e) {
				logger.error(e.getMessage(), e);
			}
			return null;
		}
	}

As you can see in this example, I am getting to a point where the deserializer doesn't get a json array - but MyClass (from Collection - this is a little bit weird for me - because I get those elements one by one, not via array.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants