I am using fasterxml.jackson. I am confused about readValue(). Here is my question.
I know jackson deserialize normal JavaBean and Collection in two different ways.
For JavaBean, we can pass MyBean.class or new TypeReference<MyBean> to readValue(). For Collections, we must pass new TypeReference<List<MyBean>>. That is because TypeReference saves the type erased by Collection. Am I right? :)
Now I am confused. If MyBean contains a list, then I can still pass MyBean.class and it works. How does jackson do that?
public class MyBean {
String str;
List<String> strList;
}
You are passing MyBean.class as the second argument to readValue() and Jackson can get the type from this through reflection. I'd guess Jackson does something like this :
MyBean.class.getDeclaredField("strList").getGenericType();
which will result in a type of java.util.List<java.lang.String>.
Note that you have a non generic class MyBean containing a List<String>. If you had for instance:
class MyGenBean<T> {
List<T> list;
}
then
MyGenBean.class.getDeclaredField("list").getGenericType();
would return java.util.List<T> and you would need a TypeReference.
Related
Can someone please help in deserializing xml string : List<A> to Object.class using jackson in java.Xml looks like :
<list> <A><id>1</id><name>Jeff</name> <id>2</id><name>John</name> </A>
It returns a HashMap object if I convert it to Object.class . I have created a utility function XMLToObject which returns Object class. This will be typcasted on the caller function end to get the required type of Object.
public Object XMLtoObject(String xml){
return mapper.readValue(xml,Object.class)
}
But if I use List.class in place of Object.class it deserializes it to list of hashmaps.
public Object XMLtoObject(String xml){
return mapper.readValue(xml,List.class)
}
I am looking a way to convert it into Object.class which I can typecast on caller function . Is that possible in jackson ? I know we can do it through xstream
The problem with List.class is that it doesn't have any information about its elements. You need one of the overloaded readValue methods. For instance, using TypeReference:
return mapper.readValue(xml, new TypeReference<List<Object>>() {});
This creates an anonymous sub class of TypeReference, from which the complete generic type (List<Object>) can be read by Jackson. The result should therefore be a List<Object> instead of a List<HashMap>.
Is there some way for a Jackson Delegate-based creator to access the raw Json String?
#JsonCreator
private static MyClass createFromJson(Map<String, Object> jsonProperties) {
return new MyClass(rawJson);
}
I am able to get the raw input as a Map of Strings to Objects in the code above, but I want to be able to access the json as a string. I tried the code below (based off of http://www.cowtowncoder.com/blog/archives/2011/07/entry_457.html) but that code as written is never invoked.
#JsonCreator
private static MyClass createFromJson(String rawJson) {
return new MyClass(rawJson);
}
Note: This is a spring boot application (1.3.1.RELEASE) that uses Jackson 2.6.4.
Looks like this type of functionality would not make sense in this context. In fact, it appears to me now that requesting the JSON string in this instance defeats the purpose of using jackson in the first place. However if anyone finds themselves here, then the comments from Sotirios Delimanolis may be useful:
"Hack: you can receive a JsonNode as the parameter type and use its toString method to get the corresponding JSON."
"It looks like you want a JsonDeserializer"
I'm using Google's JSON library called Gson in one of my project.
I have a code for converting JSON String into object using GSON. I have following method to do that:
public static <T> ApiResponse<T> fromJson(String json)
{
return new Gson().fromJson(json, new TypeToken<ApiResponse<T>>() {}.getType());
}
And it seems to work fine when I do something like that:
ApiResponse<List<JobModel>> response = ApiResponse.fromJson(new String(bytes));
OR
ApiResponse<Double> response = ApiResponse.fromJson(new String(bytes));
But when I try do this:
ApiResponse<JobModel> response = ApiResponse.fromJson(new String(bytes));
Where JobModel is my own class I get the following error:
com.google.gson.internal.LinkedTreeMap cannot be cast to com.pcf.api.model.JobModel
So then I went and implemented another method in ApiResponse:
public static <T> ApiResponse<T> fromJson(String json, TypeToken<ApiResponse<T>> token)
{
return new Gson().fromJson(json, token.getType());
}
And this time call it using function above:
ApiResponse<JobModel> response = ApiResponse.fromJson(new String(bytes), new TypeToken<ApiResponse<JobModel>>() {});
It seems to work fine.
I just can't get my head around this as two functions do exactly same thing. The only difference is that in first it purely relies on Java's generics where in second one I pass TypeToken as a parameter.
Can anyone explain me why is that happening and is there any way to fix it ?
A TypeToken is kind of a hack with generics. It depends on subclassing the type, either with an anonymous or normal class, and using Class#getGenericSuperclass() which states
If the superclass is a parameterized type, the Type object returned
must accurately reflect the actual type parameters used in the source
code.
In other words, in an anonymous class declaration like this
new TypeToken<ApiResponse<T>>() {}.getType())
the superclass is TypeToken<ApiResponse<T>>. It's equivalent to
class Subclass extends TypeToken<ApiResponse<T>>
assuming T was in scope. So when you call Class#getGenericSuperclass(), it will return a ParameterizedType that knows about ApiReponse<T> since that is the actual type parameters used in the source code.
When you call your original function with any of
ApiResponse<List<JobModel>> response = ApiResponse.fromJson(new String(bytes));
ApiResponse<Double> response = ApiResponse.fromJson(new String(bytes));
ApiResponse<JobModel> response = ApiResponse.fromJson(new String(bytes));
although the compiler will infer and bind the corresponding type as a type argument to the method invocation, the internals of the method will pass the same TypeToken object with ApiResponse<T>. Since Gson doesn't know what T is, it will use a default that depends on what it sees in the JSON. If it sees an object, it will use a LinkedTreeMap. If it sees a numeric primitive, it will use the double. Etc.
In the case where you pass a TypeToken,
ApiResponse.fromJson(new String(bytes), new TypeToken<ApiResponse<JobModel>>() {});
it's equivalent to
class Subclass extends TypeToken<ApiResponse<JobModel>>
In other words, Class#getGenericSuperclass() will return a ParameterizedType that has ApiResponse<JobModel>. Gson can extract the JobModel and use it as a hint for deserializing the JSON.
Can anyone explain me why is that happening and is there any way to
fix it ?
There's nothing really to fix. That's just how it works.
Additional reading:
is it possible to use Gson.fromJson() to get ArrayList<ArrayList<String>>?
Gson TypeToken with dynamic ArrayList item type
how does the method infer the type of <T>
Generics work at compile-time,due to lack of reified Generics in Java (it isn't possible to do a T t = new T()), Gson itself is forced to use the TypeToken approach, as you see. Otherwise Gson would have done it in a much more elegant manner.
How do you pass a class type as parameter?
public Configuration getConfig(Object mapper, Object format) {
Configuration conf = new Configuration(false);
conf.setClass("mapreduce.map.class", TestMapper.class, Mapper.class);
conf.setClass("mapreduce.inputformat.class", DatastoreInputFormat.class, InputFormat.class);
return conf;
}
I want to replace the value TestMapper.class and DatastoreInputFormat.class with the parameters mapper and format, respectively.
I'm not sure how to convert from Object to TestMapper.class.
The parameters to getConfig() should perhaps have Class type in in the first place instead of Object's. However:
If you want the runtime class of whatever your mapper and format Objects are, just call their .getClass() methods.
conf.setClass("mapreduce.map.class",mapper.getClass(), Mapper.class);
conf.setClass("mapreduce.inputformat.class",format.getClass(),InputFormat.class);
So, if your mapper method parameter is actually an instance of TestMapper , mapper.getClass() will return the same object as TestMapper.class does.
If your mapper and parameter already is the .class objects, i.e. you have called the method as foo.getConfig(TestMapper.class,DatastoreInputFormat.class); you can cast your Objects back to a Class object, assuming the conf.setClass() takes Class<?> type:
conf.setClass("mapreduce.map.class",(Class<?>)mapper, Mapper.class);
conf.setClass("mapreduce.inputformat.class",(Class<?>)format,InputFormat.class);
Unfortunately it is not clear what is the prototype of Configuration.setClass(). If it is Configuration.setClass(String, Class, Class) you can convert from Object to Class by casting: (Class)mapper and (Class)format`.
Although it is a bad style as all casting... Why getConfig() receives Objects? May be it should receive arguments of type Class? In this case you even do not need any casting at all.
I am trying to write a generic method for deserializing json into my model. My problem is that I don't know how to get Class from the generic type T. My code looks something like this (and doesn't compile this way)
public class JsonHelper {
public <T> T Deserialize(String json)
{
Gson gson = new Gson();
return gson.fromJson(json, Class<T>);
}
}
I tried something else, to get the type, but it throws an error I had the class as JsonHelper<T> and then tried this
Class<T> persistentClass = (Class<T>) ((ParameterizedType)getClass()
.getGenericSuperclass())
.getActualTypeArguments()[0];
The method signature looks like this
com.google.gson.Gson.fromJson(String json, Class<T> classOfT)
So, how can I translate along T so that when I call JsonHelper.Deserialize<MyObject>(json); I get an instance of the correct object?
You need to get a Class instance from somewhere. That is, your Deserialize() method needs to take a Class<T> as a parameter, just like the underlying fromJson() method.
Your method signature should look like Gson's:
<T> T Deserialize(String json, Class<T> type) ...
Your calls will look like this:
MyObject obj = helper.Deserialize(json, MyObject.class);
By the way, the convention to start method names with a lowercase letter is well established in Java.
Unfortunately, the way Java handles generics, you cannot get the class like you're asking. That's why Google's stuff asks specifically for the class as an argument. You'll have to modify your method signature to do the same.