Open TaylanUB opened 4 years ago
I think Gson should only resolve type variables for raw types. For every other case I can think of the user is expecting behavior which Gson does not provide and an exception should be thrown (though this is currently not the case). Please let me know if you can think of any other legit use cases for resolving type variables.
Your PencilBox
example shows one case where a type variable is misused. The use of the type variable PencilType
suggests that Gson is aware of the type argument for that variable, which is however not the case due to Java's type erasure. Even your proposed fix does not solve this.
Consider this example: The user of your class creates a new PencilBox<RedPencil>(json)
, however the JSON data actually represents a PencilBox<BluePencil>
. This would deserialize fine but cause runtime exceptions. The use of the type variable creates the illusion of type-safety here. A more correct implementation would be to have a method static PencilBox<?> deserialize(Reader reader)
instead.
And Gson could not even support all type variable usages for raw types:
TypeToken
cannot represent them. Only using the first bound is not an option because the deserialized type has to satisfy both bounds. E.g. T extends Number & CharSequence
could not be deserialized as Number
because the created object might not be a CharSequence
.
Note that there are some cases for which multiple bounds could be supported but they are likely rather rare which does not justify the required effort:
X extends String & CharSequence
could be simplified as X extends String
since String implements CharSequence. This becomes more complex if the other bounds are parameterized types.X extends Y & CharSequence
where Y
has the type argument String
. In this case the type could be resolved as String since it implements CharSequence.TypeToken
cannot represent them. For example X extends Comparable<X>
or <X extends Y, Y extends Comparable<X>>
cannot be represented using TypeToken. Both can be satisfied if a type is comparable with itself, e.g. Integer implements Comparable<Integer>
. However, TypeToken could only represent it as Comparable<Object>
respectively Comparable<Comparable<Object>>
(or similar) which would allow types which are not comparable with objects of the same type only.Note that I am not a member of this project.
GSON Version: 2.8.6
When
$Gson$Types.getRawType(Type)
is passed aTypeVariable
instance, it simply gives up and returnsObject.class
:That's not very good, and giving up just because there may be several bounds seems unnecessary. Since the behavior causes issues with my custom type adapter, I've had to resort to the following hack. (The
TypedField
class is a component of my custom type adapter. If you want to see the full code, here's the whole shebang.)As you see, the way I handle type variables is to traverse their upper bounds and return the first type that's an actual class, i.e. subtype of
Object
. Incidentally, type variables can only have one upper bound that's an actual class, so there can be no confusion here. Only if no upper bound is an actual class, then the fallback is once againObject.class
. (Even better might be to return the one upper bound if there is only one, and only fall back toObject.class
in case of ambiguity, but I personally don't need that.)I think GSON might want to adopt this behavior, but I first wanted some feedback before I make a patch. I'm also not sure whether I understand the relevant parts of GSON's internals correctly.
Another issue I encountered today because of GSON's current behavior, which I'm not able to fix with my custom type adapter, is the following:
Let's say I have a class
PencilBox<PencilType extends Pencil>
with a fieldList<PencilType> myPencils
:And let's say I have a custom type adapter for the class hierarchy starting from
Pencil
.When
new PencilBox<ColoringPencil>(reader)
is called, I expect that GSON will use my custom type adapter which I've registered for thePencil
type hierarchy. But no such thing happens, because GSON gives up on trying to figure out the upper bound of thePencilType
type variable. I get a list ofLinkedTreeMap
instances which then causes aClassCastException
as I'm trying to stuff them in aList
that's meant to hold objects of typePencil
.I guess the current solution here would be to use
TypeToken<List<Pencil>>
. I'm not sure if that might cause any headaches during refactoring, but it certainly breaks intuition. GSON should realize thatList<PencilType>
is effectivelyList<Pencil>
in that situation.