Java Generics : Object Mapper Converting JSON to Java Object - java

I would like to deserialize a json string to a java object. I wanted to write a generic method.
public class ObjectSerializer<T> {
T t;
private ObjectMapper mapper = new ObjectMapper();
/* public Person deSerial(String json) throws Exception {
TypeReference<Person> typeRef = new TypeReference<Person>() {};
return mapper.readValue(json, typeRef);
} */
public T deSerialize(String jsonInput) throws Exception {
TypeReference<T> typeRef
= new TypeReference<T>() {};
return mapper.readValue(jsonInput, typeRef);
}
}
When I call deSerialize(validPersonJsonString) [validPersonJsonString : valid person JSON String], it is not working, it it throwing me the error:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.Person.
Whereas, when in call the commented deSerial method, it works fine. Please explain the issue.
Thanks.

Jackson doesn't support TypeReference with generic type parameters because of type erasure. Here's the bug about it.
As far as your use case is concerned, you don't need to use ObjectSerializer class at all. You can directly expose ObjectMapper object to your service layer and perform the deserialization.
If you want to shield the json serializing mechanism from your service layer then you can wrap ObjectMapper implementation into another class (like you have done in your code) but then, accept the class type as a method argument rather than a generic type.

Related

Can not cast deserialized json array with jackson

I was working with jackson lib in java to deserialize an json file to an array list.
First I use this method and everything worked fine.
ObjectMapper objectMapper = new ObjectMapper();
ArrayList<User> users = (ArrayList<User>) objectMapper.readValue(new File("data.json"), new TypeReference<List<User>>() {});
Then I decided to refactor the code and write a generic method to use for any type of data.
public static <T> ArrayList<T> listFromJson(String filename) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
return (ArrayList<T>) objectMapper.readValue(new File(filename), new TypeReference<List<T>>() {});
}
This method returns the array list without any exceptions. But when I want to use an element of arraylist and store it in a variable the program throws exception like this.
User user = users.get(0);
Exception in thread "main" java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class org.example.User ...
....
I also tried to print out the element without casting and it wasn't an object reference. It was something like a hashmap.
I think it is related to generics but I don't know the cause.
Thanks for your help.
Your object User is a JsonNode, something like this:
{
"userName": "...",
"email": "...",
etc.
}
While this object can be mapped against a User.class if you specify so, when you don't say anything, the only guess that Jackson can take is that this is a Map<String, Object> (and the implementation a LinkedHashMap, meaning a map respecting its insertion order).
So when you refactored your method:
public static <T> ArrayList<T> listFromJson(String filename) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
return (ArrayList<T>) objectMapper.readValue(new File(filename), new TypeReference<List<T>>() {});
}
... you said to Jackson that it will find a List<T> where T can be any object. The only guess it can take is it will be a JsonNode (hence a Map.class).
To solve this issue, you should pass the concrete TypeReference to the method as well:
public static <T> ArrayList<T> listFromJson(String filename, TypeReference<?> expectedTypeReference) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
return (List<T>) objectMapper.readValue(new File(filename), expectedTypeReference);
}
... and use it like this:
TypeReference<List<User>> expectedTypeRef = new TypeReference<>() {};
List<User> users = listFromJson(fileName, expectedTypeRef);
User user = users.get(0); // <-- this should work

Java jax-rs client response entity generic

I have a problem with a generic function. I want to use a function to which I assign a certain class / type to first generate the corresponding result from a rest response and then return it.
public class TimerService {
[...]
public <T extends myObjInterface> RestMessageContainer<T> send(String endpointPath, Map<String, String> parameters, Class<T> clazz) {
[...]
Response response = webTarget.request(MediaType.APPLICATION_JSON_TYPE).get();
RestMessageContainer<T> container = response.readEntity(new GenericType<RestMessageContainer<T>>() {});
return container;
}
}
public class RestMessageContainer<T extends myObjInterface> {
[...]
#XmlAttribute(name = "data")
private List<T> data;
[...]
}
I get the following error message at runtime.
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.test.myObjInterface` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
14:47:41,982 ERROR [stderr] (EJB default - 2) at [Source: (org.jboss.resteasy.client.jaxrs.internal.ClientResponse$InputStreamWrapper); line: 3, column: 14] (through reference chain: com.test.RestMessageContainer["data"]->java.util.ArrayList[0])
The error is output for the line RestMessageContainer<T> container = response.readEntity(new GenericType<RestMessageContainer<T>>() {});
Is my approach correct at all or how should I solve my problem?
Thanks for your advice,
I have several subclasses. There is no information about the type in the JSON strings. The type results from the reqeust address. I couldn't configure Jackson to recognize the subtype. There is no unique field in the JSON strings that I could use as a type.
I am not able to change the web service that delivers the JSON strings.
[UPDATE]
I have found a solution. I no longer let the JAX-RS client convert the JSON string. I have the JSON string returned to me as a string and convert it independently using Jackson.
public <T extends myObjInterface> RestMessageContainer<T> send(String endpointPath, Map<String, String> parameters, Class<T> clazz) {
[...]
Response response = webTarget.request(MediaType.APPLICATION_JSON_TYPE).get();
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
RestMessageContainer<T> container = mapper.readValue(response.readEntity(String.class), mapper.getTypeFactory().constructParametricType(RestMessageContainer.class, clazz));
return container;
}
You cannot create an instance of an abstract class. However, you can solve the problem with a simple annotation – #JsonDeserialize on the abstract class:
#JsonDeserialize(as = Cat.class)
abstract class Animal {...}
In your case, the abstract class would be myObjInterface.
Note: If you have more than one subtype of the abstract class, then you should consider including subtype information as shown in this post.

Best way to decode JSON string to Java object?

I have a ServerEndpoint that will be recieving different JSON formats. Since we are only allowed one message handler per message type, my one decoder will have to convert the message to the corresponding Java objects.
In my decoder, I am trying to convert the message recieved to either SubClassA or SubClassB (which implemets the same interface) using the ObjectMapper class. The ObjectMapper class has a readValue method that requires the type I am trying to map the json to and will throw an exception when it cannot convert to the specified type.
I am currently decoding it like the following, but it is not very elegant.
#Override
public Message decode(String message) throws DecodeException {
ObjectMapper mapper = new ObjectMapper();
try {
SubclassA obj = mapper.readValue(message, SubclassA .class);
return obj;
} catch (IOException e) {
}
try {
SubclassB obj = mapper.readValue(message, SubclassB .class);
return obj;
} catch (IOException e) {
}
throw new DecodeException(message, "Failed to decode message.");
}
What is the best way to decode the JSON string into the corresponding Java object using ObjectMapper?
Use Jackson, you can declare subtypes for the parent object. Your Json will contain #type with with the name of the subtype, look at this post
Deserialize JSON with Jackson into Polymorphic Types - A Complete Example is giving me a compile error
Jackson is one of the best options to work with JSON in Java.
For polymorphic deserialization, consider the following:
Using the #JsonTypeInfo annotation.
For situations where the #JsonTypeInfo annotation is not suitable, consider using a StdDeserializer. This answer will help you.

Jackson deserialize json property into object

I need to deserialize the following json:
{
//...
"foo_id":1
//...
}
Into an object of class Foo with its id property set to the foo_id json property.
I need to do this within a custom deserializer.
What is the most easy way to accomplish this?
I was thinking to somehow "transform" the json to
{
//...
"foo_id":{
"id":1
}
//...
}
and then delegate this back to Jackson.
In this case, the object is of type Foo, but there are others which might not be of this class. Also, in this case, that json is a number, but I would like to support if it was a string as well.
So, I need a kind of generic way to do this, that's why I think delegating back to Jackson might be a good idea.
No annotations allowed. Suppose you're already writing the Deserializer for this property.
Take a look at this. Here is a code that I think might help you to get some ideas.
public class MyDeserializer extends JsonDeserializer< Message >
{
#Override
public Message deserialize( JsonParser jp, DeserializationContext arg1 ) throws IOException,
JsonProcessingException
{
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
ObjectNode root = (ObjectNode) mapper.readTree(jp);
Class<? extends Message> subClass = null;
Iterator<Entry<String, JsonNode>> elementsIterator = root.getFields();
while (elementsIterator.hasNext())
{
Entry<String, JsonNode> element = elementsIterator.next();
String name = element.getKey();
if ("foo_id".equals(name))
{
if(element.getValue().isInt())
subClass = FooInteger.Class;
break;
}
}
if (subClass == null) return null;
return mapper.readValue(root, subClass);
}
}
Have you considered use of mix-in annotations? With Jackson 2.2, you could also use converters to do two-step processing (#JsonDeserialize(converter=MyConverter.class).

Jackson trying to deserialize json into a List of typed objects

I am trying to use Jackson to deserialize a property on a object which is a List of typed objects. and i get the following error when I try to do it
Can not instantiate value of type [map type; class java.util.HashMap, [simple type, class java.lang.String] -> [simple type, class java.lang.String]] from JSON String; no single-String constructor/factory method
So far I have the following but it does not seem to work.
Terms.class
#JsonDeserialize(as=JsonMapDeserializer)
private List<ObjectA> results = null; //ommitted getter and setters
My Deserializer class is as follows.
public class JsonMapDeserializer extends JsonDeserializer<List<ObjectA>> {
List<ObjectA> retMap = new ArrayList<ObjectA>();
TypeReference<HashMap<String,String>[]> typeRef = new TypeReference<HashMap<String,String>[]>() {};
#Override
public List<ObjectA> deserialize(JsonParser parser, DeserializationContext ctx)
throws IOException, JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
//read the json string into the map
HashMap<String, String>[] maps = mapper.readValue(parser, typeRef);
if(maps != null) {
for(HashMap<String, String> map : maps) {
ObjectA result = new ObjectA("id", map.get("id"));
retMap.add(result);
}
}
return retMap;
}
}
and I am using simple modules to add the deserializer as follows
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("safety", Version.unknownVersion());
module.addDeserializer(List.class, new JsonMapDeserializer());
mapper.registerModule(module);
The JSON string that I end up trying to desrialize is as follows
"SearchTerms":{"results":[{id":"1010","checked":"true"}] // there are other fields I have just omitted them
When I run the code to deserialize I get the following stack trace
org.codehaus.jackson.map.JsonMappingException: Can not instantiate value of type [map type; class java.util.HashMap, [simple type, class java.lang.String] -> [simple type, class java.lang.String]] from JSON String; no single-String constructor/factory method (through reference chain: com.model.search["searchTerm1"])
at org.codehaus.jackson.map.deser.std.StdValueInstantiator._createFromStringFallbacks(StdValueInstantiator.java:379)
at org.codehaus.jackson.map.deser.std.StdValueInstantiator.createFromString(StdValueInstantiator.java:268)
at org.codehaus.jackson.map.deser.std.MapDeserializer.deserialize(MapDeserializer.java:244)
at org.codehaus.jackson.map.deser.std.MapDeserializer.deserialize(MapDeserializer.java:33)
at org.codehaus.jackson.map.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:104)
at org.codehaus.jackson.map.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:18)
at org.codehaus.jackson.map.ObjectMapper._readValue(ObjectMapper.java:2695)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1294)
at com.az.rd.ke.json.JsonColumnMapDeserializer.deserialize(JsonColumnMapDeserializer.java:41)
at com.az.rd.ke.json.JsonColumnMapDeserializer.deserialize(JsonColumnMapDeserializer.java:27)
at org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:299)
at org.codehaus.jackson.map.deser.SettableBeanProperty$MethodProperty.deserializeAndSet(SettableBeanProperty.java:414)
at org.codehaus.jackson.map.deser.BeanDeserializer.
From looking at the stack trace and debugging because my module has been set up as follows
module.addDeserializer(List.class, new JsonMapDeserializer());
When deserializing it seems to complain as soon as it gets to the first property in my object which is a list, because searchTerm1 which it complains about is only a list of strings.
Can anybody please advise on how I would deserialize a list of typed objects, or how I could add the deserializer correctly. If I changed the adddeserializer method to
module.addDeserializer(List<ObjectA>.class, new JsonMapDeserializer());
it has compiler issues because the deserializer class is typed as List<ObjectA>.
it does look like the object you want to deserialise is a Map<String, List<Map<String,String>>>, not a List<Map<String,String>>.
Maybe try that instead?

Categories

Resources