Consider this External execution class
public class ExternalCommands {
private Logger log = LoggerFactory.getLogger(ExternalCommands.class);
private static final ObjectMapper objectMapper = new ObjectMapper();
public <T> CustomResponse<T> executeQuery(Clients client, Query query, Class<T> classType) throws Exception {
if (Objects.isNull(clients))
throw new Exception("external client is null in external commands");
log.debug("Query : {}", query);
Response queryResponse = clients.getElasticClient().executeQuery(query);
log.debug("query response : {}", queryResponse);
if (queryResponse.status() == 200) {
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(CustomResponse.class, classType); // This is the coding sin m talking about
return objectMapper.readValue(queryResponse.body().asInputStream(), javaType);
}
}
}
So Basically this executeQuery function fetches all the data as per query from an elastic client and deserialise it as per the generic classType as sent in function BUT
this is more like explicitly doing the deserialisation instead of using Generics.
See below code on how this execution works along with inline comments :
public ArrayList<EmpData> getEmpData() throws Exception {
ArrayList<EmpData> merchantUrnMap = new ArrayList<>();
List<Filter> filters = new ArrayList<>();
filters.add("Added filter 1 here");
filters.add("Added filter 2 here");
filters.add("Added filter 3 here");
ExternalCommands commands = new ExternalCommands();
Query query = commands.getQuery(filters);
// get "clients"
// this is how it works now
CustomResponse<EmpData> response = commands.executeQuery(clients, query, EmpData.class);
// this is how i WANT IT TO WORK - without passing "EmpData.class"
// but in this case <T> in "CustomResponse<T>" would not deserialise to "EmpData"
// resulting in LinkedHashMap in deseralised object instead of class object
// CustomResponse<EmpData> response = commands.<EmpData>executeQuery(clients, query);
// some operations
return response
}
any suggestions on how to achieve this?
Case 1: assuming that constructParametricType requires the classType argument to function properly, and you can't change the implementation of methods/classes that are implied but not provided in your posted code.
Your proposed method signature/invocation not possible due to type erasure in Java.
You use classType in the constructParametricType(CustomResponse.class, classType) call, and you're trying to replace classType with T somehow. This is impossible, because when the code is compiled, the T is erased completely. There is no way to do something like constructParametricType(CustomResponse.class, T.class) because T doesn't exist at runtime.
The correct solution is to pass in the class as a method argument, which is precisely what your existing approach does.
Case 2: you really want to have the call commands.<EmpData>executeQuery(clients, query); and you're willing to change anything to achieve that goal.
Since we cannot pass T as an argument to constructParametricType, it must be called as constructParametricType(CustomResponse.class), yet it needs to return a JavaType representing CustomResponse<T>. The only way to do that is to declare
<T> JavaType<T> constructParametricType(Class<?> cls)
Note that JavaType now also has to be parameterized for the same reason (we can't get T at runtime). Finally, we have to declare
CustomResponse<T> readValue(InputStream stream, JavaType<T> javaType)
to match the declared return type of executeQuery.
After all of these changes, the line
CustomResponse<EmpData> response = commands.<EmpData>executeQuery(clients, query);
should compile. Here's a minimal example:
class CustomResponse<T> {}
class Clients{}
class Query{}
class EmpData{}
class ObjectMapper {
JavaTypeFactory getTypeFactory() {
return new JavaTypeFactory();
}
<T> CustomResponse<T> readValue(InputStream s, JavaType<T> j) {
return new CustomResponse<>();
}
}
class JavaTypeFactory {
<T> JavaType<T> constructParametricType(Class<?> cls) {
return new JavaType<>(cls);
}
}
class JavaType<T> {
JavaType(Class<?> cls) {}
}
class ExternalCommands {
private static final ObjectMapper objectMapper = new ObjectMapper();
public <T> CustomResponse<T> executeQuery(Clients clients, Query query) throws Exception {
InputStream queryResponseStream = null;
JavaType<T> javaType = objectMapper.getTypeFactory().<T>constructParametricType(CustomResponse.class);
return objectMapper.readValue(queryResponseStream, javaType);
}
}
class SomeClass {
public void getEmpData() throws Exception {
ExternalCommands commands = new ExternalCommands();
Query query = null;
Clients clients = null;
CustomResponse<EmpData> response = commands.<EmpData>executeQuery(clients, query);
}
}
Beware that some of the described changes might not be easy/possible given the rest of your system (especially parameterizing JavaType), and I don't recommend this approach. I recommend sticking with what you have; it's the cleanest approach IMO.
Related
I have data structure that looks more or less like this
class ResponseWrapper<T> {
T response;
public ResponseWrapper(T response) {
this.response = response;
}
}
And service that handles reading that response from JSON to actual DTO.
public class GenericService<T> {
public ResponseWrapper<T> read(String json, Class<T> clazz) throws Exception {
T response = new ObjectMapper().readValue(json, clazz);
return new ResponseWrapper<>(response);
}
}
And I can call it like this:
GenericResponse<SomeData> response = new GenericService<SomeData>().read("json value", SomeData.class)
And what I'm trying to achieve is:
GenericResponse<SomeData> response = new GenericService<SomeData>().read("json value")
And I'm wondering, is it actually possible? This is obviously not working
public ResponseWrapper<T> read(String json) throws Exception {
T response = new ObjectMapper().readValue(json, T.class);
return new ResponseWrapper<>(response);
}
No. It is not possible.
Java generics work by type erasure ... and that means that the actual class associated with generic type parameter is not available at runtime. If your code needs to know that class, you need to pass a Class object explicitly.
And, yes, T.class is a compilation error.
And, yes, there is no way to get the class of T.
I wrote this method:
public <T> T jsonToObject(String json, Class<T> klazz) {
ObjectMapper mapper = new ObjectMapper();
T object = null;
try {
object = mapper.readValue(json, klazz);
} catch (IOException e) {
e.printStackTrace();
}
return object;
}
I want to call it:
List<Device> devicesList= jsonUtils.jsonToObject(response.getEntityInputStream(), new TypeLiteral<List<Device>.class);
what is the correct way to pass a Class<T> of List<Device> ?
Well, if you want to call it with a TypeLiteral (and that's indeed the way to capture the generic type of the list), your method needs to accept a TypeLiteral<T> as argument, not a Class<T>.
And the caller needs to use an anonymous class to properly capture the type:
jsonUtils.jsonToObject(response.getEntityInputStream(),
new TypeLiteral<List<Device>>() {});
I am currently working on a serialization routine which uses a library of generically typed adapters. If the object being serialized is an instance of one of the specific adapters I have, then I need to call that adapter on the object prior to performing my other serialization procedures.
The following code works:
private final static String serialize(Object obj, Map<Class<?>,
XmlAdapter<?,?>> classToAdapterMap) throws JAXBException
{
Object adaptedObj = null;
for (Class<?> clazz : classToAdapterMap.keySet()) {
if (clazz.isInstance(obj)) {
XmlAdapter<?,?> adapter = classToAdapterMap.get(clazz);
Class<?>[] argTypes = new Class[] {clazz};
try {
Method method = adapter.getClass().getMethod("marshal", argTypes);
adaptedObj = method.invoke(adapter, obj);
break;
} catch (Exception e) {
// handle method retrieval and invocation related exceptions
}
}
}
// serialize
}
However, I had originally thought that I would be able to do this more simply, for example with code like:
/* DOES NOT WORK */
private final static String serialize(Object obj, Map<Class<?>,
XmlAdapter<?,?>> classToAdapterMap) throws JAXBException
{
Object adaptedObj = null;
for (Class<?> clazz : classToAdapterMap.keySet()) {
if (clazz.isInstance(obj)) {
XmlAdapter<?,?> adapter = classToAdapterMap.get(clazz);
adaptedObj = adapter.marshal(clazz.cast(obj));
break;
}
}
// serialize
}
Clearly the problem is that the wildcard generically typed adapter isn't guaranteed to handle an object of type clazz. However, I can't indicate that these two are the same by changing the method signature—as I might otherwise do—to private final static <T> String serialize(Object obj, Map<Class<T>, XmlAdapter<?,T>> classToAdapterMap), because the map needs to hold adapters of all different types.
What would be a better way to do this? Or should I stick with the Reflection based solution?
Thanks in advance,
-Dan
There are several solutions to circumvent this problem.
Most likely, the easiest one is using raw types: don't specify the type parameters for the adapter, and the compiler will happily accept the marshall call (with a raw type warning of course):
XmlAdapter adapter = classToAdapterMap.get(clazz);
adaptedObj = adapter.marshal(obj);
(This is actually roughly the same solution as Bastian's, without the intermediate type)
If you don't like raw types, you may choose the unchecked cast to an Object-parameterized adapter. It's not really better, but it also works (by tricking the compiler…):
XmlAdapter<?, Object> adapter = (XmlAdapter<?, Object>) classToAdapterMap.get(clazz);
adaptedObj = adapter.marshal(obj);
My last solution is to use a type parameter at the method level. This time, what you do is semantically correct (as long as the map itself is correct), and the unchecked cast really means “I know what I am doing here”:
private final static <T> String serialize(T obj, Map<Class<?>,
XmlAdapter<?,?>> classToAdapterMap) throws JAXBException
{
Object adaptedObj = null;
for (Class<?> clazz : classToAdapterMap.keySet()) {
if (clazz.isInstance(obj)) {
try {
XmlAdapter<?, ? super T> adapter = (XmlAdapter<?, ? super T>) classToAdapterMap.get(clazz);
adaptedObj = adapter.marshal(obj);
break;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
// serialize
}
The semantic correctness comes from the following:
you may consider T to be the actual class of obj since T is a method-bound parameter, not used elsewhere in the signature;
clazz is a super type of the type of T since we checked clazz.isInstance(obj);
adapter can handle instances of clazz or a super-type of it since it is how the map was built;
by consequent, adapter can handle all instances of an (unknown) super type of T, hence the ? super T declaration.
There is a simpler and safer way without using reflection:
At first, we need a small specialization of the XmlAdapter as it allows us to ask the adapter for the type it can handle.
public abstract class TalkingXmlAdapter<ValueType, BoundType> extends XmlAdapter<ValueType, BoundType> {
public abstract Class<BoundType> getBoundType();
}
My custom adapters now need to extend TalkingXmlAdapter:
public class AppleXmlAdapter extends TalkingXmlAdapter<String, Apple> {
#Override
public Class<Apple> getBoundType() {
return Apple.class;
}
#Override
public Apple unmarshal(String v) throws Exception {
System.out.println("Unmarshalling Apple");
return new Apple();
}
#Override
public String marshal(Apple v) throws Exception {
System.out.println("Marshalling Apple");
return "Apple";
}
}
public class BananaXmlAdapter extends TalkingXmlAdapter<String, Banana> {
#Override
public Class<Banana> getBoundType() {
return Banana.class;
}
#Override
public Banana unmarshal(String v) throws Exception {
System.out.println("Unmarshalling Banana");
return new Banana();
}
#Override
public String marshal(Banana v) throws Exception {
System.out.println("Marshalling Banana");
return "Banana";
}
}
That allows us to write a simplified serialization method:
public class SimpleSerializer {
public static final String serialize(Object obj, List<TalkingXmlAdapter> allAdapters) throws Exception {
Object adaptedObj = null;
for (TalkingXmlAdapter adapter : allAdapters) {
if (adapter.getBoundType().isInstance(obj)) {
adaptedObj = adapter.marshal(obj);
break;
}
}
// serialize
System.out.println("Simple serializing for " + obj.toString());
return "Simply serialized " + obj.toString();
}
}
Using the code e.g. like in the subsequent listing shows the behavior you want:
List<TalkingXmlAdapter> allAdapters = new ArrayList<>();
allAdapters.add(new AppleXmlAdapter());
allAdapters.add(new BananaXmlAdapter());
SimpleSerializer.serialize(new Banana(), allAdapters);
SimpleSerializer.serialize("Lemmon", allAdapters);
SimpleSerializer.serialize(new Apple(), allAdapters);
Output:
Marshalling Banana
Simple serializing for generic.adapter.Banana#659e0bfd
Simple serializing for Lemmon
Marshalling Apple
Simple serializing for generic.adapter.Apple#2a139a55
To sum this up, the solution gives you following advantages:
You don't need reflection which simplifies your code.
You need fewer generic programming in your serialization routine which simplifies your code.
The solution is more safe. Note that no type cast is needed. Every adapter accepts the type Object. However by using the generic method getBoundType() you can ensure the specific runtime type is the correct one. When building your map as in the reflection solution, a wrongly mapped class results in a runtime exception. In the proposed solution the super class TalkingXmlAdapter enforces each adapter to state their correct type by using generics.
The price you pay is:
Introduction of a new super type.
Requires small adaptions for your custom adapters.
Hope that helps!
Certainly I'm quite new in all this Java stuff, so I have a question, I'm trying to deserialize a response obtained on a WCF service, everything works fine, but, I'm trying to make a generic function to do this.
Basically what I do is
public List<msg> GetService(String method){
List<msg> response = new ArrayList<msg>();
Type msgType = new TypeToken<List<msg>>(){}.getType();
//Obtaining result
response = uJSON.fromJson(serviceResponse, msgType);
//uJSON is an instance of Gson library, for deserializing it just needs
//the service response and a Class<T> or Type to reflect the obtained message
}
What I'm trying to do is obtaining the Type "msg" generic, it means...
public <thing> void GetInstanceService(String method){
List<thing> response = new ArrayList<thing>();
Type rType2 = new TypeToken<List<thing>>(){}.getType(); //Got java.util.List<thing>
//And when I'm trying to deserialize I just obtain a List of object
//[java.lang.Object#5c7a987e, java.lang.Object#74b1a7a0]
type2 = uJSON.fromJson(new String(entity), rType2);
}
But I'm calling like this.
comm.<msgType>GetInstanceService("listTestType");
So, when I call "GetInstanceService", "thing" is "msgType" Type, for the
List<thing> and also response shouldn't be List<msgType> instead of List <Object>?
Besides, when I'm trying to explicitly pass the type through a "Type" parameter, it just causes me compilation time error like this.
public void GetInstanceService(Type type){
List<type> type2 = new ArrayList<type>(); //Compilation time error
//Or
msgType oType = new msgType();
Class classType = oType.getClass();
List<classType> type3; //Compilation time error
}
So, if none of these attempts was effective, how could I set the type for deserialization?
Guava class TypeToken does not support that mode of usage. You are creating the type token with a type variable and there not enough information for it to reconstruct List<String> from List<T>. You should create an instance of TypeToken where you have all the required compile-time information.
The documentation says:
Note that it's critical that the actual type argument is carried by a
subclass. The following code is wrong because it only captures the <T>
type variable of the listType() method signature; while <String> is
lost in erasure:
class Util {
static <T> TypeToken<List<T>> listType() {
return new TypeToken<List<T>>() {};
}
}
TypeToken<List<String>> stringListType = Util.<String>listType();
But as said above, you can instantiate the TypeToken at call-site, where all type info are available, and then pass it as a parameter. Something like this:
public <thing> void GetInstanceService(String method, TypeToken<List<thing>> token){
List<thing> response = new ArrayList<thing>();
Type rType2 = token.getType();
type2 = uJSON.fromJson(new String(entity), rType2);
}
comm.GetInstanceService("listTestType", new TypeToken<List<msgType>>() {});
Update
Paul Bellora notes that you can also accept a parameter TypeToken<thing> token, and construct a TypeToken<List<thing>> inside the method from that token:
public <thing> void GetInstanceService(String method, TypeToken<thing> token) {
List<thing> response = new ArrayList<thing>();
Type rType2 = new TypeToken<List<thing>>() {}
.where(new TypeParameter<thing>() {}, token); // where() binds "thing" to token
.getType();
type2 = uJSON.fromJson(new String(entity), rType2);
}
comm.GetInstanceService("listTestType", new TypeToken<msgType>() {});
Due to something called type erasure, the class object you need is not available at runtime.
However, there is a standard work-around: pass a type token into your method, like this:
public <T> List<T> getService(String method, Class<T> c) {
// the caller has passed in the class object
List<T> list = new ArrayList<T>();
// fill list
return list;
}
Using Jersey I'm defining a service like:
#Path("/studentIds")
public void writeList(JsonArray<Long> studentIds){
//iterate over studentIds and save them
}
Where JsonArray is:
public class JsonArray<T> extends ArrayList<T> {
public JsonArray(String v) throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new MappingJsonFactory());
TypeReference<ArrayList<T>> typeRef = new TypeReference<ArrayList<T>>() {};
ArrayList<T> list = objectMapper.readValue(v, typeRef);
for (T x : list) {
this.add((T) x);
}
}
}
This works just fine, but when I do something more complicated:
#Path("/studentIds")
public void writeList(JsonArray<TypeIdentifier> studentIds){
//iterate over studentIds and save them by type
}
Where the Bean is a simple POJO such as
public class TypeIdentifier {
private String type;
private Long id;
//getters/setters
}
The whole thing breaks horribly. It converts everything to LinkedHashMap instead of the actual object. I can get it to work if I manually create a class like:
public class JsonArrayTypeIdentifier extends ArrayList<TypeIdentifier> {
public JsonArrayTypeIdentifier(String v) throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new MappingJsonFactory());
TypeReference<ArrayList<TypeIdentifier>> typeRef = new TypeReference<ArrayList<TypeIdentifier>>(){};
ArrayList<TypeIdentifier> list = objectMapper.readValue(v, typeRef);
for(TypeIdentifier x : list){
this.add((TypeIdentifier) x);
}
}
}
But I'm trying to keep this nice and generic without adding extra classes all over. Any leads on why this is happening with the generic version only?
First of all, it works with Longs because that is sort of native type, and as such default binding for JSON integral numbers.
But as to why generic type information is not properly passed: this is most likely due to problems with the way JAX-RS API passes type to MessageBodyReaders and MessageBodyWriters -- passing java.lang.reflect.Type is not (unfortunately!) enough to pass actual generic declarations (for more info on this, read this blog entry).
One easy work-around is to create helper types like:
class MyTypeIdentifierArray extends JsonArray<TypeIdentifier> { }
and use that type -- things will "just work", since super-type generic information is always retained.