I found some strange behavior of the Jackson JSON Processor library and i am curious whether this is intentional or a bug. Please have a look at the code below:
#JsonTypeInfo(use = Id.NAME)
public class Nut {}
…
ObjectMapper mapper = new ObjectMapper();
Nut nut = new Nut();
Object object = new Nut();
Nut[] nuts = new Nut[] { new Nut() };
Object[] objects = new Object[] { new Nut() };
System.out.println(mapper.writeValueAsString(nut));
System.out.println(mapper.writeValueAsString(object));
System.out.println(mapper.writeValueAsString(nuts));
System.out.println(mapper.writeValueAsString(objects));
Output:
{"#type":"Nut"}
{"#type":"Nut"}
[{"#type":"Nut"}]
[{}]
What i expect (and want) is the following:
{"#type":"Nut"}
{"#type":"Nut"}
[{"#type":"Nut"}]
[{"#type":"Nut"}] // <<< type information included
Do i miss something or should i file a bug report?
This is expected behavior. When traversing an object graph for serialization, Jackson uses the declared type of an object when determining what type information to include. The elements in objects have declared type Object, which you haven't told Jackson to include any type information for.
Jackson only looks at the runtime type of the top-level argument to writeValueAsString, because the method argument has type Object; it's not possible in Java to know the declared type of an object passed as an argument to a method (even with generics, thanks to type erasure), so your first two examples (writeValueAsString(nut) and writeValueAsString(object) are effectively identical).
More information here: http://jackson-users.ning.com/forum/topics/mapper-not-include-type-information-when-serializing-object-why
Related
Example code below.
Using Spring Boot 2.2, I want to communicatie with a REST API. the API I'm trying to consume wraps objects in a parent model for paging and sorting and puts an json array of the actual objects in the results field. How would I model my Java code so jackson 'knows' how to deserialize the API responses into my java objects?
I've tried solving this using an generic in ApiResponse, and passing the expected field-type when performing the get request:
String URL_GET_DOGS = "https://localhost/api/v1/dogs/"
ApiResponse<Dog> response = this.restTemplate.getForObject(URL_GET_DOGS, response.getClass());
This compiles AND runs...
Expected result: Successfully created an ApiResponse object with a results field consisting out of a List of Dogs.
Actual result: Successfully created an ApiResponse object but the results field is a List of Objects.
So jackson won't cast the results list properly and instead I appear to get a List<Object> instead of List<Dog> for my results field in my ApiResponse Object. This way I end up with properties of the wrong Type or properties that I don't want to deserialize at all! See Car example.
Now I'm back to an interface-based solution but I'm stuck. Jackson (rightfully, because there is no way to deduce the correct class...) complains that it does not know how to deserialize abstract types and I would need to provide a concrete implementation, I can't use class-type Jackson annotations as described here because I do not control the API generating the responses.
The only way out of this I see right now, is to use classes for each type of response but that means a lot of duplicate code for paging and sorting fields. What am I doing wrong?
Example JSON:
{
"count": 84,
"next": "http://localhost:80/api/v1/dogs/?limit=2&offset=2",
"previous": null,
"results": [
{
"name": "Pebbles"
},
{
"name": "Spot"
}
]
}
and another endpoint:
{
"count": 22,
"next": "http://localhost:80/api/v1/cars/?limit=2&offset=2",
"previous": null,
"results": [
{
"brand": "Mercedes",
"horse_power": 120,
"field_i_dont": "want_to_deserialize"
},
{
"brand": "BMW",
"horse_power": 180,
"field_i_dont": "want_to_deserialize"
}
]
}
Example code:
public class ApiResponse<T>{
// paging and sorting
private Long count;
private String next;
private String previous;
// the actual objects
private List<T> results;
// No-args constructor, getters & setters
}
public class Dog {
private String name;
// No-args constructor, getters & setters
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class Car {
private int horsePower;
private String brand;
// No-args constructor, getters & setters
}
Cause
Upon further investigation I found this is caused by Type Erasure, an answer here on stackoverflow provided a snippet to deal with this using Jackson.
From what I now understand the call to response.getClass() is exactly the same as calling ApiResponse.class. Even though ApiResponse was typed as a class with a generic parameter, generics 'lose' their type parameter because of a compiler rule called 'Type Erasure'. The Internal generic is converted to an Object type and Jackson will use a LinkedHashMap to represent any data in Object fields.
Generics are used for tighter type checks at compile time and to provide a generic programming. To implement generic behaviour, java compiler apply type erasure. Type erasure is a process in which compiler replaces a generic parameter with actual class or bridge method. In type erasure, compiler ensures that no extra classes are created and there is no runtime overhead.
Type Erasure rules
Replace type parameters in generic type with their bound if bounded type parameters are used.
Replace type parameters in generic type with Object if unbounded type parameters are used.
Insert type casts to preserve type safety.
Generate bridge methods to keep polymorphism in extended generic types.
Solution
In order to 'fix' the example in my original question
ApiResponse<Dog> response = this.restTemplate.getForObject(URL_GET_DOGS, response.getClass())
Had to be changed so the raw JSON could be mapped using the JavaType class:
ObjectMapper mapper = new ObjectMapper(); // Defined as final in rest-client class.
String rawJsonResponse = this.restTemplate.getForObject(URL_GET_DOGS, String.class)
ApiResponse<Dog> response = mapper.readValue(
rawJsonResponse,
mapper.getTypeFactory().constructParametricType(
ApiResponse.class,
Dog.class)
);
In my spring(mvc) web application, I am using org.codehaus.jackson.map.ObjectMapper in my scala code to map my json to scala objects using case classes. My Json String is an array of json objects objects. so I am using:
val user = mapper.readValue(myJson, classOf[List[MyClass]])
This line throws an error:
Exception in thread "main"
org.codehaus.jackson.map.JsonMappingException: Can not construct
instance of scala.collection.immutable.List, problem: abstract types
can only be instantiated with additional type inform
Am I using it right or is there any other way?
The problem is the Java type erasure. classOf[List[MyClass]] at runtime is the same as classOf[List[_]]. That is why Jackson cannot know, which types of the elements to create.
Luckily Jackson does support parsing with the JavaType, which describes the types themselves.
Here a simple sample in Java:
JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, MyClass.class);
mapper.readValue(myJson, type);
Because of type erasure, the parameterized type of the List is lost at runtime.
Instead, use the Scala module for Jackson and you can simply do:
mapper.readValue(myJson, new TypeReference[List[MyClass]])
So long as the Scala module has been registered - this means a Scala List will be created.
I am facing a very strange behavior in Java.
I got two different classes that has no hierarchical connection:
Class Template (Type hierarchy is Object -> A -> B -> Template), and class TemplateDto (Object -> TemplateDto).
I am using ModelMapper (org.modelmapper.ModelMapper) for mapping between the two classes (which uses the default mapping since the field names are identical).
There is the following code:
List<Template> templates = cvService.getTemplates();
List<TemplateDto> resultDtos = new ArrayList<TemplateDto>();
modelMapper.map(templates,resultDtos);
TemplateDto example = resultDtos.get(0);
And the last line throws:
java.lang.ClassCastException: com.vs.framework.domain.cv.Template cannot be cast to com.vs.framework.dto.cv.TemplateDto
This is very weird. When i am debugging this section i see that after the mapping, resultDtos is a list of type List instead of List which blows my mind.
I have tried to clean my tomcat, maven clean install but it still happens.
Any ideas?
Java implements generics with type erasure, meaning that the runtime code has no way of knowing that your ArrayList is supposed to be an ArrayList<TemplateDto>.
http://modelmapper.org/user-manual/generics/ describes how to use a TypeToken to get around this problem with lists. It should look something like this:
List<Template> templates = cvService.getTemplates();
Type listType = new TypeToken<List<TemplateDto>>() {}.getType();
List<TemplateDto> resultDtos = modelMapper.map(templates, listType);
TemplateDto example = resultDtos.get(0);
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.
I'm trying to deserialize a Json array to an ArrayList of objects. I have found some documentation on what i'm trying to do, but I'm getting an error on compile.
Here is how I'm trying to approach it with Jackson 2.2.2:
ArrayList<Friends> list = objectMapper.readValue(result, new TypeReference<ArrayList<Friends>>() {});
The error I get is:
The method readValue(String, Class<T>) in the type ObjectMapper is not applicable for the arguments (String, new TypeReference<ArrayList<Friends>>(){})
I'm guessing some of the references I have been reading is based on an older version of Jackson. How can this be accomplished in Jackson 2.2 +
Try the following:
JavaType type = objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, Friends.class);
ArrayList<Friends> friendsList = objectMapper.readValue(result, type);
Note that I pulled this from the following answer: Jackson and generic type reference
As of Jackson 1.3 (a while back) they recommend you use TypeFactory.
EDIT
On further inspection, what you have above is working for me... I'm able to pass in a TypeReference sub class to readValue and everything works correctly. Are you sure you have the right type of TypeReference imported? Usually those types of errors are from accidentally importing the wrong type (some other library might have a TypeReference class).