You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Convert GenericType or ResolvedType to JavaType #69
Some function like Jackson2JsonRedisSerializer requires JavaType as parameter. When I tried to pass generic type parameter via GenericType object, I find that GenericType can only be converted to ResolvedType, and there is no way from ResolvedType to JavaType.
TypeResolverresolver = newTypeResolver();
GenericTypegtype = newGenericType<T>(){}
ResolvedTypertype = resolver.resolve(gtype);
// seems correct but constructType will throw when receiving GenericType!JavaTypejavatype= TypeFactory.defaultInstance().constructType(rtype);
Correct: ResolvedType of classmate is not related to JavaType of Jackson.
Unfortunately I do not think it could be made to, either; Classmate definitely should NOT have a dependency to jackson-databind, but same is true for the opposite: Jackson should not have to add dependency to Classmate.
It would definitely be nice to have some means to convert ResolvedType into JavaType, esp. if it could be done without actual class level dependency. But I do not know how this could be achived.
I am open to ideas for supporting some means of conversion, with the constraint that no dependencies may be added across these 2 libraries (Jackson, ClassMate).
I'd definitely be +1 for improvements, just not sure where and how (plus, if I had time to directly work on it).
But as to JavaType, it depends: it can tell about generic type parameters of type itself, but not of "contained" things (Fields, Methods etc). For that Jackson has BasicBeanIntrospector and some other types; but those are not easily usable from outside of framework itself (like when handling callback to find (de)serializer and so on).
So what specifically do you have in mind?
I won't be so bold as to make an ask. After doing some digging on my own I arrived at what you just told me... no easy path. I love and use both libraries and from a purely aspirational perspective I think it would be nice if Classmate was flexible enough so that Jackson used Classmate. But as the saying goes, I'm sure we all have bigger fish to fry. For me (and probably most programs) doing introspection twice (or even five times) on startup doesn't really impact me. Thanks for your contributions!
Yeah. The origin story of ClassMate is that it came out of Jackson internals, cleaned up.
Unfortunately this also makes the integration rather difficult: Jackson JavaType is a little bit too tightly coupled to what Jackson does, wrt semantics (think MapType, CollectionType).
In some ways one would probably need to have custom TypeFactory (jackson-databind) that could take ResolvedType -- API/call-wise it might already allow passing it as ResolvedType implements (I think?) java.lang.reflect.Type; but of course there is no logic for conversion.
Such thing -- ClassMateTypeFactory? -- would live in a separate package as I wouldn't really want to add classmate dependency on jackson-databind if just adding sort of new ability, but without getting rid of existing JavaType handling.
It would offer some level of integration, still.
I guess it wouldn't be 100% mandatory to sub-class TypeFactory, however; maybe simpler would be to have something that uses TypeFactory to just construct JavaType... this might be better way to go, although be slightly less convenient to use (being additional converter one needs to call explicitly).
The bigger approach of making Jackson just use ClassMate is something that ultimately would probably be good (despite adding one more dependency), except for one thing: since JavaType is exposed via Jackson APIs, it'd be impossible to do this just as internal rewrite (or not impossible; impractical? herculean undertaking?).
Thank you for the ClassMate library. I second the need for a conversion from GenericType/ResolvedType to Jackson's JavaType.
Classmate definitely should NOT have a dependency to jackson-databind, but same is true for the opposite: Jackson should not have to add dependency to Classmate.
True and true, but nothing says a support module published as a separate artifact (e.g. jackson-classmate) can't have a dependency on ClassMate. In fact I think that's an entirely natural and appropriate approach.
In other words, assuming you have a multimodule Maven project, you just add another aggregated subproject with only these conversion utilities, and give the project an artifact ID of jackson-classmate. That subproject would have a dependency on both jackson-databind and classmate. Everything compiles and deploys together (as separate artifacts of course). Then developers can simply add jackson-classmate to their project if they need the conversion utilities.
Actually let me take a step back and address the question itself. I have a GenericType<T> from ClassMate, and I need to call ObjectReader.forType(…). There are several variations of this method, including forType(JavaType valueType) and forType(TypeReference<?> valueTypeRef).
It seems the most direct analog of ClassMate GenericType<?> in Jackson is TypeReference<?>. So I suppose I need to do one of the following:
Convert GenericType<?> to TypeReference<?>.
Convert GenericType<?> to JavaType (equivalent to TypeReference.getType()).
Convert ResolvedType to TypeReference<?>.
Convert ResolvedType (from TypeResolver.resolve(GenericType<?>)) to JavaType (equivalent to TypeReference.getType()).
Which one of these is easier—or even possible?
Since I'm calling ObjectReader.forType(…), maybe there is an easier way, because (at the moment) all I care about is getting the correct type of ObjectReader.
I see that forType(TypeReference<?> valueTypeRef) actually calls:
So (not having tried this), could we construct a JavaType from a GenericType<T> like this?
GenericType<?> genericType = newGenericType<Foo<Bar>>(){}; //just like TypeReferenceTypegenericTypeSubclassSuperClass = genericType.getClass().getGenericSuperclass();
//TODO do sanity check `genericTypeSubclassSuperClass instanceof Class<?>` if desiredTypeunwrappedGenericType = ((ParameterizedType) genericTypeSubclassSuperClass).getActualTypeArguments()[0];
JavaType = _config.getTypeFactory().constructType(unwrappedGenericType);
I'm inferring all of this just from perusing the source code—I haven't tested any of it, and I don't even now if it will compile. but it seems reasonably simple and straightforward
The only mystery at the moment is that I haven't searched enough to find out how to get a _config.getTypeFactory() to use in creating the JavaType, but in the question @jingkaimori indicates that I can use a default TypeFactory to do the same thing. (I wonder if there is a benefit of using the DeserializationConfig of the ObjectReader.) I'll look into this more.
Update: Oh, the DeserializationConfig gets to the ObjectReader from ObjectMapper.getDeserializationConfig(), which is public. So if worst comes to worse, I can use the config from the object mapper. But the point of having an ObjectReader is that I don't even want to have access to the ObjectMapper directly, so let me see if there is something better.
Update: Duh 🤦♂️ — the ObjectReader itself has a public getConfig().
So all indications are that I would be able to create a JavaType from a GenericType<?> using any TypeFactory; and in the case of ObjectReader.forType(…) I would use the TypeFactory from the ObjectReader itself. For a general case, you could use TypeFactory.defaultInstance() as in the question, but I highly recommend using a type factory appropriate for the context for which you need the JavaType—at least the one that comes from an existing ObjectMapper.
I'm going to pause work on this for today, but tomorrow I'll test the conversion code above and write unit tests for it. There seems to be no mystery—I would be willing to bet a bagel (to invent an expression) that it will work just fine.
This method will simply delegate to the method above, passing objectReader.getConfig.getTypeFactory() so that we know we're using the correct type factory, and then delegate to ObjectReader.forType(JavaType valueType).
I haven't yet tried the resulting ObjectReader, but since forType(TypeReference<?> valueTypeRef) creates a JavaType exactly as I do in the unit test, I can't imagine how it couldn't work.
(Note that I'm not sure why GenericType decided to implement Serializable. This means I'll have to suppress a compiler warning about the lack of a serialVersionUID every time I do something like new GenericType<String>(){}, which is like … basically every single time a use it. 😢 I guess I'll open a separate ticket for that. Update: I opened #73 for that issue.)
I had another thought. You'll note that the conversion logic I gave above to convert ClassMate GenericType<?> to Jackson JavaType will actually work with any"Super Type Token", to use Neil Gafter's term. Therefore instead of adding a jackson-classmate library just for conversions, you could actually add a generalized method in Jackson to work with any such type; e.g. in Jackson ObjectReader:
This should work not only for ClassMate's GenericType, but for Spring's ParameterizedTypeReference, Guava's TypeToken, and all the classes from other libraries that follow the same pattern.
Of course you would want to add appropriate guard code to ensure the Object passed conforms to what you'd expect for this pattern.
You may decide for whatever reason you don't want to add this sort of feature to Jackson. I'm just pointing out it's possible.
Agreed on many points: yes, something could definitely depend on ClassMate and Jackson-databind. Similarly, ability to use "generalized" super token pattern seems like an interesting idea.
My main concern really (beyond avoiding wrong kind of dependencies) has to do with API design. And I think that instead of adding super-type token into ObjectMapper or ObjectReader (and presumably ObjectWriter too), more natural place would be TypeFactory, which could resolve these into JavaType, for use anywhere.
But I can then see the challenge of users actually finding this functionality...
Still, even if something else were to be added in other places, I would love to get a PR for adding resolution method (with simple validation as suggested that there's 1-and-only-1 type parameter, and super type is Object) added in TypeFactory.
I think method name should reflect "super type" somewhere given that nominal type, Object, is so... "generic" (no pun intended).
I also wonder if it'd be awkward to even require that class name itself contains "Type". Just as additional sanity check.
And I think that instead of adding super-type token into ObjectMapper or ObjectReader (and presumably ObjectWriter too), more natural place would be TypeFactory, which could resolve these into JavaType, for use anywhere. But I can then see the challenge of users actually finding this functionality...
I'm not so worried about users finding the conversion code in TypeFactory; my bigger worry is the developer using the wrongTypeFactory e.g. trying to create an ObjectReader from an existing one for a specific GenericType<?>.
In other words, I agree that the conversion code should go in TypeFactory (if we decide we want to do a general toJavaType(…) method from an Object super type token), but I also think that another ObjectReader.forSuperTypeToken(…) should be added that will delegate to the TypeFactory, using the correct TypeFactory from the ObjectReader. (The same goes for other locations I'm sure.
I wouldn't mind doing this. I'm in the middle of some intense coding for another project, though, so I won't get to it immediately.
A comment here for whoever gets time to address this: I see this note in Jackson TypeReference:
TypesuperClass = getClass().getGenericSuperclass();
if (superClassinstanceofClass<?>) { // sanity check, should never happenthrownewIllegalArgumentException("Internal error: TypeReference constructed without actual type information");
}
I think the point here is that we expect the generic super type not to be just a plain class, but some parameterized type that contains type information. But I think if checks were added below specifically for that, those would be more direct and include the check here. I'm referring to:
/* 22-Dec-2008, tatu: Not sure if this case is safe -- I suspect * it is possible to make it fail? * But let's deal with specific * case when we know an actual use case, and thereby suitable * workarounds for valid case(s) and/or error to throw * on invalid one(s). */_type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
Thus the improved, pretty code looks like this:
/** * Converts a ClassMate "generic type" to a Jackson "Java type" using the supplied type factory. * @param typeFactory The type factory for creating Jackson types. * @param genericType The ClassMate generic type holder. * @return A Jackson Java type token instance. * @throws IllegalArgumentException if the given type is not a direct subclass of {@link GenericType} providing a single generic type parameter. */publicstaticJavaTypetoJavaType(@NonnullfinalTypeFactorytypeFactory, @NonnullfinalGenericType<?> genericType) {
finalTypegenericTypeSubclassSuperClass = genericType.getClass().getGenericSuperclass();
if(genericTypeSubclassSuperClassinstanceofParameterizedTypeparameterizedType) {
checkArgument(GenericType.class.equals(parameterizedType.getRawType()), "Type token must be immediate subclass of `%s`.",
GenericType.class.getSimpleName());
finalType[] actualTypeArguments = parameterizedType.getActualTypeArguments();
if(actualTypeArguments.length == 1) {
returntypeFactory.constructType(actualTypeArguments[0]);
}
}
thrownewIllegalArgumentException("Type token must provide a single generic type argument.");
}
This is code specific to GenericType, using Java 17 and my own preconditions utility. But it can be modified to work with any "super type token" just by removing the argument check for GenericType.class.equals(parameterizedType.getRawType().
Point about ObjectReader makes sense, although given that method should not be static at all (for this reason), maybe it's not quite as likely users use wrong instance (but what do I know).
I would still start with TypeFactory adding member method, fwtw.
I would still start with TypeFactory adding member method, fwtw.
The other issue in my mind is naming; if we have a method that takes a general "super type token" (such as GenericType<?> or ParameterizedType<?>), what do we call it? Above I had been using "super type token", e.g. ObjectReader.forSuperTypeToken(…), using Neil Gafter's term, but that term is sort of awkward and confusing. Upon reading his article again, I realize that "super type token" is really a specialized technique for the general "type token" technique we have been using for eons with Class<?> as the token. This technique is a type token technique using super types holding the token.
So one approach for the naming would be to simply call it a "type token", and have code that detects whether we are passing a Class<?> or a type token "wrapper" such as GenericType<?>. (In fact Guava even calls theirs a TypeToken<?>.) Thus we would have a method like ObjectReader.forTypeToken(…) and there would be a couple of lines saying if(typeToken instanceof Class<?>) … else //unwrap the super type token. The naming becomes clearer and the method becomes even more general.
It would look like this:
/** * Returns a type represented by a type token: either a {@link Class}; or a direct subclass of some parameterized type (a <dfn>super type token</dfn>), where * the super class parameterized type represents the type. * @param typeToken The type token: either a {@link Class} or a super type token such as <code>com.fasterxml.classmate.GenericType<T></code>, Spring * <code>org.springframework.core.ParameterizedTypeReference<T>, or Guava <code>com.google.common.reflect.TypeToken<T></code>. * @return The type represented by the type token. * @see <a href="https://gafter.blogspot.com/2006/12/super-type-tokens.html">Super Type Tokens</a> * @throws IllegalArgumentException if the given object is not a {@link Class}; or a direct subclass of a class providing a single generic type parameter. */publicstaticTypetypeTokenToType(@NonnullfinalObjecttypeToken) {
if(typeTokeninstanceofClass<?> clazz) { //a class as a type token is already the typereturnclazz;
}
//super type tokenfinalTypesuperTypeTokenSuperClass = typeToken.getClass().getGenericSuperclass();
if(superTypeTokenSuperClassinstanceofParameterizedTypeparameterizedType) {
finalType[] actualTypeArguments = parameterizedType.getActualTypeArguments();
if(actualTypeArguments.length == 1) {
returnactualTypeArguments[0];
}
}
thrownewIllegalArgumentException("Type token must be an instance of `Class`, or have a super class providing a single generic type argument.");
}
That's the generalized "convert type token to Type" method. (The typeToken instanceof Class<?> clazz can easily be split into two lines for lower Java versions, of course.)
I'll likely add a method like this to my PLOOP library, which we discussed years ago and which will soon come back to life.
For Jackson a new TypeFactory method might look like this:
… (all those other libraries using similar super type token types)
(I realize that at some point we'll need to open a ticket in Jackson to add this. I can add it when I get time; I'm trying to juggle several things at once as I work on my own project which is using this.)
Let me come full circle back to the original question. I've already fully explained how to convert from a ClassMate GenericType<?> to a Jackson JavaType by extracting the Type from the GenericType<?> and using Jackson TypeFactory.constructType(Type) on the extracted Type.
But what about converting from ResolvedType as @jingkaimori originally asked? In my case, I might decide I want to convert all types to ClassMate ResolvedType to pass around, and only occasionally call Jackson. ResolvedType seems more appropriate to use for general passing across several methods; GenericType<?> is only a (necessary) hack to get the ball rolling. The problem, as Jackson knows nothing about ResolvedType, is that ResolvedType seems to have thrown away the original Type!
Let me explain it a different way. If I find a Method, I can find the Type (maybe a ParameterizedType for List<String> for example) of its first parameter using method.getGenericParameterTypes()[0]. I assume then that I can construct a Jackson JavaType for the first parameter using typeFactory.constructType(method.getGenericParameterTypes()[0]). But if instead of saving the Type for the first method parameter, what if I immediately resolve it using new TypeResolver.resolve(method.getGenericParameterTypes()[0])? This gives me a ResolvedType—but what happened to the original Type representing the method parameter type? As far as I can tell it has been discarded, so that I can no longer call typeFactory.constructType(resolvedType), as @jingkaimori pointed out.
If only there were a ResolvedType.getType() or something that would give me back the original type—or some (reasonably short) algorithm to reconstruct it.
If there is not, after I pull out my PLOOP library and dust it off, I may improve its TypeInfo<T> class to store, not only the ClassMate ResolvedType, but also the original Type that was used as input to TypeResolver. This will give me a convenient TypeInfo<T> type holder to pass around, providing me acess to ResolvedType but also giving me the original Type in case I need to use it with Jackson.
I agree that converting from ResolvedType to JavaType would be much more useful than "super type token" (for which names that I can think of would include "type reference", "super-type reference", "type wrapper").
But I am not sure integration for ResolvedType -> JavaType would nicely fit into either library as per earlier discussion.
However, it could definitely make a reasonable stand-alone library at first.
And maybe, just maybe, one could make it some kind of extension of TypeFactory... either with existing extension point (TypeModifier? maybe not), or, more likely, adding new one. I doubt I'll have time to focus on that, but -- as always! -- happy to help others as sidekick if they want to give it a try.
Does this make sense? Going with PLOOP probably makes sense then.
Does this make sense? Going with PLOOP probably makes sense then.
Sure, sounds fine. The biggest thing I was wanting to confirm here (besides thinking out loud) is that there wasn't a ResolvedType -> Type I was missing somewhere. And I think it's clear now there isn't. Thanks for the response.
In the meantime, if you get a chance to work on any ClassMate things, I would say #74 and #75 should get the highest priority. That way it would be easier for me and/or others to jump in and help with other tickets if needed.
The biggest thing I was wanting to confirm here (besides thinking out loud) is that there wasn't a ResolvedType -> Type I was missing somewhere.
Correct: there isn't. I am not a fan of java.lang.reflect.Type. Although TBH I have occasionally wondered about possibly trying to use it for conversions -- it might (but might not) work. At least that type and all subtypes are pure interfaces so with enough work... maybe?
I'll see if I find time to tackle issues mentioned.
Hello @cowtowncoder ! I was wondering if there are any plans to create this conversion from ResolvedType -> java.lang.reflect.Type somewhere?
Would you be open if I (try to) create a PR with added functionality + tests and docs?
I stumbled upon this issue when looking at this discussion at the hibernate validator project, where losing type information can be solved by adding this.
@Luc-Veldhuis Yes, I would be willing to help if you (or anyone else) wanted to tackle this. Especially given you have an actual use case to test it against.
I do not have time to focus on trying to implement it myself but always try to find time to help others.
Hey! I have a draft MR. However, the only working solution I could come up with required me to create custom implementations of java Types, so I am not sure if that is the way to go.
Maybe if somebody has a better idea, glad to hear it!
Activity
cowtowncoder commentedon Mar 14, 2022
Correct:
ResolvedTypeof classmate is not related toJavaTypeof Jackson.Unfortunately I do not think it could be made to, either; Classmate definitely should NOT have a dependency to
jackson-databind, but same is true for the opposite: Jackson should not have to add dependency to Classmate.It would definitely be nice to have some means to convert
ResolvedTypeintoJavaType, esp. if it could be done without actual class level dependency. But I do not know how this could be achived.I am open to ideas for supporting some means of conversion, with the constraint that no dependencies may be added across these 2 libraries (Jackson, ClassMate).
rfox12 commentedon Mar 17, 2022
+1 for interoperability between Jackson and Classmate (two solid dependencies for me).
In my case lots of good introspection is performed by Jackson... then repeated by Classmate.
Unless... Is there a way to have Jackson's JavaType tell me about that type's (generic) fields?
cowtowncoder commentedon Mar 21, 2022
I'd definitely be +1 for improvements, just not sure where and how (plus, if I had time to directly work on it).
But as to
JavaType, it depends: it can tell about generic type parameters of type itself, but not of "contained" things (Fields, Methods etc). For that Jackson hasBasicBeanIntrospectorand some other types; but those are not easily usable from outside of framework itself (like when handling callback to find (de)serializer and so on).So what specifically do you have in mind?
rfox12 commentedon Mar 22, 2022
I won't be so bold as to make an ask. After doing some digging on my own I arrived at what you just told me... no easy path. I love and use both libraries and from a purely aspirational perspective I think it would be nice if Classmate was flexible enough so that Jackson used Classmate. But as the saying goes, I'm sure we all have bigger fish to fry. For me (and probably most programs) doing introspection twice (or even five times) on startup doesn't really impact me. Thanks for your contributions!
cowtowncoder commentedon Mar 22, 2022
Yeah. The origin story of ClassMate is that it came out of Jackson internals, cleaned up.
Unfortunately this also makes the integration rather difficult: Jackson
JavaTypeis a little bit too tightly coupled to what Jackson does, wrt semantics (thinkMapType,CollectionType).In some ways one would probably need to have custom
TypeFactory(jackson-databind) that could takeResolvedType-- API/call-wise it might already allow passing it asResolvedTypeimplements (I think?)java.lang.reflect.Type; but of course there is no logic for conversion.Such thing --
ClassMateTypeFactory? -- would live in a separate package as I wouldn't really want to add classmate dependency on jackson-databind if just adding sort of new ability, but without getting rid of existingJavaTypehandling.It would offer some level of integration, still.
I guess it wouldn't be 100% mandatory to sub-class
TypeFactory, however; maybe simpler would be to have something that usesTypeFactoryto just constructJavaType... this might be better way to go, although be slightly less convenient to use (being additional converter one needs to call explicitly).The bigger approach of making Jackson just use ClassMate is something that ultimately would probably be good (despite adding one more dependency), except for one thing: since
JavaTypeis exposed via Jackson APIs, it'd be impossible to do this just as internal rewrite (or not impossible; impractical? herculean undertaking?).garretwilson commentedon Oct 4, 2023
Thank you for the ClassMate library. I second the need for a conversion from
GenericType/ResolvedTypeto Jackson'sJavaType.True and true, but nothing says a support module published as a separate artifact (e.g.
jackson-classmate) can't have a dependency on ClassMate. In fact I think that's an entirely natural and appropriate approach.In other words, assuming you have a multimodule Maven project, you just add another aggregated subproject with only these conversion utilities, and give the project an artifact ID of
jackson-classmate. That subproject would have a dependency on bothjackson-databindandclassmate. Everything compiles and deploys together (as separate artifacts of course). Then developers can simply addjackson-classmateto their project if they need the conversion utilities.garretwilson commentedon Oct 4, 2023
Actually let me take a step back and address the question itself. I have a
GenericType<T>from ClassMate, and I need to callObjectReader.forType(…). There are several variations of this method, includingforType(JavaType valueType)andforType(TypeReference<?> valueTypeRef).It seems the most direct analog of ClassMate
GenericType<?>in Jackson isTypeReference<?>. So I suppose I need to do one of the following:GenericType<?>toTypeReference<?>.GenericType<?>toJavaType(equivalent toTypeReference.getType()).ResolvedTypetoTypeReference<?>.ResolvedType(fromTypeResolver.resolve(GenericType<?>)) toJavaType(equivalent toTypeReference.getType()).Which one of these is easier—or even possible?
Since I'm calling
ObjectReader.forType(…), maybe there is an easier way, because (at the moment) all I care about is getting the correct type ofObjectReader.I see that
forType(TypeReference<?> valueTypeRef)actually calls:That uses the
TypeReference.getType(), which actually calls:So (not having tried this), could we construct a
JavaTypefrom aGenericType<T>like this?I'm inferring all of this just from perusing the source code—I haven't tested any of it, and I don't even now if it will compile. but it seems reasonably simple and straightforward
The only mystery at the moment is that I haven't searched enough to find out how to get a
_config.getTypeFactory()to use in creating theJavaType, but in the question @jingkaimori indicates that I can use a defaultTypeFactoryto do the same thing. (I wonder if there is a benefit of using theDeserializationConfigof theObjectReader.) I'll look into this more.Update: Oh, the
DeserializationConfiggets to theObjectReaderfromObjectMapper.getDeserializationConfig(), which is public. So if worst comes to worse, I can use the config from the object mapper. But the point of having anObjectReaderis that I don't even want to have access to theObjectMapperdirectly, so let me see if there is something better.Update: Duh 🤦♂️ — the
ObjectReaderitself has a publicgetConfig().So all indications are that I would be able to create a
JavaTypefrom aGenericType<?>using anyTypeFactory; and in the case ofObjectReader.forType(…)I would use theTypeFactoryfrom theObjectReaderitself. For a general case, you could useTypeFactory.defaultInstance()as in the question, but I highly recommend using a type factory appropriate for the context for which you need theJavaType—at least the one that comes from an existingObjectMapper.I'm going to pause work on this for today, but tomorrow I'll test the conversion code above and write unit tests for it. There seems to be no mystery—I would be willing to bet a bagel (to invent an expression) that it will work just fine.
garretwilson commentedon Oct 5, 2023
I couldn't wait until tomorrow. 😄 Here's the code:
Here's the unit test:
I'll create another convenience method:
This method will simply delegate to the method above, passing
objectReader.getConfig.getTypeFactory()so that we know we're using the correct type factory, and then delegate toObjectReader.forType(JavaType valueType).I haven't yet tried the resulting
ObjectReader, but sinceforType(TypeReference<?> valueTypeRef)creates aJavaTypeexactly as I do in the unit test, I can't imagine how it couldn't work.garretwilson commentedon Oct 5, 2023
(Note that I'm not sure why
GenericTypedecided to implementSerializable. This means I'll have to suppress a compiler warning about the lack of aserialVersionUIDevery time I do something likenew GenericType<String>(){}, which is like … basically every single time a use it. 😢 I guess I'll open a separate ticket for that. Update: I opened #73 for that issue.)garretwilson commentedon Oct 5, 2023
I had another thought. You'll note that the conversion logic I gave above to convert ClassMate
GenericType<?>to JacksonJavaTypewill actually work with any "Super Type Token", to use Neil Gafter's term. Therefore instead of adding ajackson-classmatelibrary just for conversions, you could actually add a generalized method in Jackson to work with any such type; e.g. in JacksonObjectReader:This should work not only for ClassMate's
GenericType, but for Spring'sParameterizedTypeReference, Guava'sTypeToken, and all the classes from other libraries that follow the same pattern.Of course you would want to add appropriate guard code to ensure the
Objectpassed conforms to what you'd expect for this pattern.You may decide for whatever reason you don't want to add this sort of feature to Jackson. I'm just pointing out it's possible.
cowtowncoder commentedon Oct 5, 2023
Agreed on many points: yes, something could definitely depend on ClassMate and Jackson-databind. Similarly, ability to use "generalized" super token pattern seems like an interesting idea.
My main concern really (beyond avoiding wrong kind of dependencies) has to do with API design. And I think that instead of adding super-type token into
ObjectMapperorObjectReader(and presumablyObjectWritertoo), more natural place would beTypeFactory, which could resolve these intoJavaType, for use anywhere.But I can then see the challenge of users actually finding this functionality...
Still, even if something else were to be added in other places, I would love to get a PR for adding resolution method (with simple validation as suggested that there's 1-and-only-1 type parameter, and super type is
Object) added inTypeFactory.I think method name should reflect "super type" somewhere given that nominal type,
Object, is so... "generic" (no pun intended).I also wonder if it'd be awkward to even require that class name itself contains "Type". Just as additional sanity check.
garretwilson commentedon Oct 5, 2023
I'm not so worried about users finding the conversion code in
TypeFactory; my bigger worry is the developer using the wrongTypeFactorye.g. trying to create anObjectReaderfrom an existing one for a specificGenericType<?>.In other words, I agree that the conversion code should go in
TypeFactory(if we decide we want to do a generaltoJavaType(…)method from anObjectsuper type token), but I also think that anotherObjectReader.forSuperTypeToken(…)should be added that will delegate to theTypeFactory, using the correctTypeFactoryfrom theObjectReader. (The same goes for other locations I'm sure.I wouldn't mind doing this. I'm in the middle of some intense coding for another project, though, so I won't get to it immediately.
garretwilson commentedon Oct 5, 2023
A comment here for whoever gets time to address this: I see this note in Jackson
TypeReference:I think the point here is that we expect the generic super type not to be just a plain class, but some parameterized type that contains type information. But I think if checks were added below specifically for that, those would be more direct and include the check here. I'm referring to:
Thus the improved, pretty code looks like this:
This is code specific to
GenericType, using Java 17 and my own preconditions utility. But it can be modified to work with any "super type token" just by removing the argument check forGenericType.class.equals(parameterizedType.getRawType().cowtowncoder commentedon Oct 5, 2023
Right, Java 17 cannot be used yet.
Point about
ObjectReadermakes sense, although given that method should not bestaticat all (for this reason), maybe it's not quite as likely users use wrong instance (but what do I know).I would still start with
TypeFactoryadding member method, fwtw.garretwilson commentedon Oct 10, 2023
The other issue in my mind is naming; if we have a method that takes a general "super type token" (such as
GenericType<?>orParameterizedType<?>), what do we call it? Above I had been using "super type token", e.g.ObjectReader.forSuperTypeToken(…), using Neil Gafter's term, but that term is sort of awkward and confusing. Upon reading his article again, I realize that "super type token" is really a specialized technique for the general "type token" technique we have been using for eons withClass<?>as the token. This technique is a type token technique using super types holding the token.So one approach for the naming would be to simply call it a "type token", and have code that detects whether we are passing a
Class<?>or a type token "wrapper" such asGenericType<?>. (In fact Guava even calls theirs aTypeToken<?>.) Thus we would have a method likeObjectReader.forTypeToken(…)and there would be a couple of lines sayingif(typeToken instanceof Class<?>) … else //unwrap the super type token. The naming becomes clearer and the method becomes even more general.It would look like this:
That's the generalized "convert type token to
Type" method. (ThetypeToken instanceof Class<?> clazzcan easily be split into two lines for lower Java versions, of course.)I'll likely add a method like this to my PLOOP library, which we discussed years ago and which will soon come back to life.
For Jackson a new
TypeFactorymethod might look like this:Well that was easy!
And finally the convenience method for
ObjectReadermight look like this:Then all the following would work:
myObjectReader.forTypeToken(String.class)myObjectReader.forTypeToken(new GenericType<List<Foo>>(){})myObjectReader.forTypeToken(new ParameterizedTypeReference<List<Foo>>(){})(I realize that at some point we'll need to open a ticket in Jackson to add this. I can add it when I get time; I'm trying to juggle several things at once as I work on my own project which is using this.)
garretwilson commentedon Oct 10, 2023
Let me come full circle back to the original question. I've already fully explained how to convert from a ClassMate
GenericType<?>to a JacksonJavaTypeby extracting theTypefrom theGenericType<?>and using JacksonTypeFactory.constructType(Type)on the extractedType.But what about converting from
ResolvedTypeas @jingkaimori originally asked? In my case, I might decide I want to convert all types to ClassMateResolvedTypeto pass around, and only occasionally call Jackson.ResolvedTypeseems more appropriate to use for general passing across several methods;GenericType<?>is only a (necessary) hack to get the ball rolling. The problem, as Jackson knows nothing aboutResolvedType, is thatResolvedTypeseems to have thrown away the originalType!Let me explain it a different way. If I find a
Method, I can find theType(maybe aParameterizedTypeforList<String>for example) of its first parameter usingmethod.getGenericParameterTypes()[0]. I assume then that I can construct a JacksonJavaTypefor the first parameter usingtypeFactory.constructType(method.getGenericParameterTypes()[0]). But if instead of saving theTypefor the first method parameter, what if I immediately resolve it usingnew TypeResolver.resolve(method.getGenericParameterTypes()[0])? This gives me aResolvedType—but what happened to the originalTyperepresenting the method parameter type? As far as I can tell it has been discarded, so that I can no longer calltypeFactory.constructType(resolvedType), as @jingkaimori pointed out.If only there were a
ResolvedType.getType()or something that would give me back the original type—or some (reasonably short) algorithm to reconstruct it.If there is not, after I pull out my PLOOP library and dust it off, I may improve its
TypeInfo<T>class to store, not only the ClassMateResolvedType, but also the originalTypethat was used as input toTypeResolver. This will give me a convenientTypeInfo<T>type holder to pass around, providing me acess toResolvedTypebut also giving me the originalTypein case I need to use it with Jackson.cowtowncoder commentedon Oct 10, 2023
I agree that converting from
ResolvedTypetoJavaTypewould be much more useful than "super type token" (for which names that I can think of would include "type reference", "super-type reference", "type wrapper").But I am not sure integration for
ResolvedType->JavaTypewould nicely fit into either library as per earlier discussion.However, it could definitely make a reasonable stand-alone library at first.
And maybe, just maybe, one could make it some kind of extension of
TypeFactory... either with existing extension point (TypeModifier? maybe not), or, more likely, adding new one. I doubt I'll have time to focus on that, but -- as always! -- happy to help others as sidekick if they want to give it a try.Does this make sense? Going with PLOOP probably makes sense then.
garretwilson commentedon Oct 10, 2023
Sure, sounds fine. The biggest thing I was wanting to confirm here (besides thinking out loud) is that there wasn't a
ResolvedType->TypeI was missing somewhere. And I think it's clear now there isn't. Thanks for the response.In the meantime, if you get a chance to work on any ClassMate things, I would say #74 and #75 should get the highest priority. That way it would be easier for me and/or others to jump in and help with other tickets if needed.
cowtowncoder commentedon Oct 10, 2023
Correct: there isn't. I am not a fan of
java.lang.reflect.Type. Although TBH I have occasionally wondered about possibly trying to use it for conversions -- it might (but might not) work. At least that type and all subtypes are pureinterfaces so with enough work... maybe?I'll see if I find time to tackle issues mentioned.
Luc-Veldhuis commentedon Aug 22, 2025
Hello @cowtowncoder ! I was wondering if there are any plans to create this conversion from
ResolvedType -> java.lang.reflect.Typesomewhere?Would you be open if I (try to) create a PR with added functionality + tests and docs?
I stumbled upon this issue when looking at this discussion at the hibernate validator project, where losing type information can be solved by adding this.
cowtowncoder commentedon Aug 22, 2025
@Luc-Veldhuis Yes, I would be willing to help if you (or anyone else) wanted to tackle this. Especially given you have an actual use case to test it against.
I do not have time to focus on trying to implement it myself but always try to find time to help others.
Luc-Veldhuis commentedon Aug 26, 2025
Hey! I have a draft MR. However, the only working solution I could come up with required me to create custom implementations of java
Types, so I am not sure if that is the way to go.Maybe if somebody has a better idea, glad to hear it!