Deserialization List to HashMap jackson - java

I have an issue regarding serialization/deserialization with jackson.
I have a class with a Map (occupations) like this:
public class RoomType implements Comparable<RoomType>, Serializable {
private static final long serialVersionUID = 2310443507411437907L;
// Key: idUseOccupation, Value: Occupation
private Map<Long, Occupation> occupations = new LinkedHashMap<>();
...
and the Getter of this field instead of returning a Map, it's returning a Collection
public Collection<Occupation> getOccupations() {
return occupations.values();
}
Setter:
public void setOccupations(Map<Long, Occupation> occupations) {
this.occupations = occupations;
}
So, I have no problem with the serialization, but when I try to deserialize with jackson to a roomType object, I get an error:
Can not deserialize instance of java.util.LinkedHashMap out of START_ARRAY token. Just for testing I have changed the getter from Collection to Map and I checked that it works fine, but I cannot make this change because it will imply multiple changes in lots of classes and it might provoke such a big issues in several applications.
How can I solve this problem?
Thanks in advance.

Related

Object not being able to map to POJO class

I am getting a response, which I converted to Pojo class with one field of type Object. Now when I am trying to cast the Object type to another Pojo class its throwing the error :
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to SecondClass
Code :
FirstClassResponse firstClassResponse = (FirstClassResponse) convertJSONToObject(firstClassResponseJson, FirstClassResponse.class);
//jsonToObject method
public static Object convertJSONToObject(String jsonRequest, Class objectClassType) throws Exception {
Object object = gson.fromJson(jsonRequest, objectClassType);
return object;
}
Here, firsClass object when printed gives following result :
FirstClassResponse [modifiedResponse=null, response={id=123, username=abc, balance=0.0, currencycode=EUR, created=2021-03-30 16:31:54, agent_balance=0.0, sessionid=123}]
Now, the error happens in the following line :
SecondClassResponse modifiedResponse = (SecondClassResponse) firstClassResponse.getResponse();
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to SecondClassResponse
I am sharing the POJO for FirstClassResponse and SecondClassResponse :
public class FirstClassResponse{
private SecondClassResponse modifiedResponse;
private Object response;
//getter, setter
}
public class SecondClassResponse{
private String id;
private String username;
private double balance;
private String currencycode;
private String created;
private double agent_balance;
private String sessionid;
//getter, setter
}
private Object response;
Make this a SecondClassResponse, not an Object. With it being an Object, GSON doesn't know that this should be a SecondClassResponse, so it just shoves the map in there as a Map, which obviously can't be cast.
The entire point of using GSON is to turn everything into specific objects so you can use it in a more Java like way. If you store something as an Object when converting from GSON, you're almost always doing it wrong.
That FirstClassResponse is completely superfluous; use SecondClassResponse instead.
Just look at the JSON ...and then explain to me how to map as FirstClassResponse?
And you've not even object-relational mapping (as the GSON converter does), but you're parsing.
Perhaps gson.fromJson cannot convert the attribute class of the class before. You can try to take out firtClassResponse.getResponse() and do the conversion separately

Spring Boot Rest API Enumerate some Java types

I am building Spring Boot webflux REST API functionality that needs to work with data containing few Java type's (let's consider String, Integer, Double for example) information as part of JSON request/responses. Attribute representing Java type must be persistable inside mongodb as well (should not be problem once JSON can work with such attribute). I have following model class and type enumeration which is used by REST API to serialize/deserialize JSON message's.
#Getter
#ToString
#EqualsAndHashCode(exclude = "id")
#Document(collection = "core_scheme")
#JsonDeserialize(builder = SchemeModel.Builder.class)
#Builder(builderClassName = "Builder", toBuilder = true, setterPrefix = "with")
public class SchemeModel {
#Id
private final String id;
#Field(name = "userId") private final String userId;
#Field(name = "date") private final String creationDate;
#Field(name = "properties") private final Map<String, SchemeTypes> properties;
}
public enum SchemeTypes {
INTEGER, STRING, DOUBLE
}
Serialization and deserialization work's well. Now the problem is that when i want to resolve real Java type's stored inside Map<String, SchemeTypes> properties map i need to do mapping similar to this (just abstraction not real code):
SchemeTypes.INTEGER => Java Integer class
SchemeTypes.STRING => Java String class
SchemeTypes.DOUBLE => Java Double class
Is there any more simple way to represent Java type's stored inside model class and used within serialized/deserialized JSON file's so i can directly use it to deduce Java type without additional validation that it's valid Java type. For example if type's enumarated inside mentioned enum would have exactly same naming as real Java type's i could do following without any mapping:
public void deduceClass(SchemeTypes type) {
Class myClass = Class.forName(type.toString());
}
Note that i am looking for a solution which would work out of the box (i don't have to validate type's provided by user). If such solution would be harder to implement as mentioned mapping i will stick with mapping.
If you weren't saving this entity I could say you can actually directly map the SchemeTypes into corresponding class like following
public enum SchemeTypes {
INTEGER(Integer.class), STRING(String.class), DOUBLE(Double.class);
private final Class clazz;
private SchemeTypes(Class clazz){
this.clazz = clazz;
}
public Class getClazz(){
return clazz;
}
}
But as you are saving this it could cause some issue to deserialize.
Maybe you can save not the SchemaType instance directly but just the name of enum to overcome this like following
private final Map<String, String> properties;
and find the corresponding clazz value with a static method on this class like following
public static Class findClazzFor(String schemeTypeName){
return SchemeTypes.valueOf(schemeTypeName).getClazz();
}
Nevertheless I think cleanest solution would be keeping the SchemeType class instance mapping somewhere as a one-to-one map. And retrieve the corresponding class for provided schemeType as in the getClazz method above.

Using the instantiated collection type for Jackson deserialization instead of initializing a new one?

If I have a class
class DTO {
final MySet<Types> values = MySetWrapper(EnumSet.of(Types.class));
public MySet getValues() {
return values;
}
}
where MySet extends Set. Jackson complains that
Cannot find a deserializer for non-concrete Collection type MySet
which I understand, but I already instantiated the collection. What I want is for jackson to just call add for each value after it created an instance, something like:
DTO o = new DTO();
MySet<Types> values = o.getValues();
for (Types type : jsonArray) {
values.add(type );
}
I don't want it to try to create a new collection itself.
That error message means that the DTO class is configured (by default or explicitly) to deserialize the values part of the JSON input into the DTO values field of DTO :
Cannot find a deserializer for non-concrete Collection type MySet
If you consider that Jackson should not perform the deserialization directly on this field, you could define a constructor to set values and also make sure that Jackson will not perform automatically the deserialization work : to achieve it, remove setter for that field (or add #JsonIgnore on it) and any jackson module that will use reflection to deserialize to fields.
It would give :
final MySet<Types> values = MySetWrapper(EnumSet.of(Types.class));
#JsonCreator
public MyFoo(Set<Types> values) {
this.values.addAll(values);
}
Note that I specified in the constructor Set and not MySet (should not be an issue as interface doesn't declare fields), otherwise you would get the same issue since you didn't define a deserializer for MySet.
But if you implement a deserializer for that you could of course do :
public MyFoo(MySet<Types> values) {
this.values.addAll(values);
}
Found an answer using #JsonProperty:
#JsonProperty
private void setValues(Set<Types> types) {
values.addAll(types);
}
Pretty short and simple thankfully.
Edit: seems like you don't even need the annotation.

JSON Parsing errors with RESTeasy/JAXB

I'm trying to make a call to the iTunes REST service that returns information about the genres defined in iTunes via a RESTeasy client. The JSON object returned by this call looks something like this:
{
"35":{
"name":"iPod Games",
"id":"35",
"url":"https://itunes.apple.com/us/genre/ipod-games/id35"
},
"36":{
"name":"App Store",
"id":"36",
"url":"https://itunes.apple.com/us/genre/ios/id36?mt=8"
}
}
I've defined my response object model like this:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#JsonIgnoreProperties(ignoreUnknown = true)
public class ITunesGenre implements Serializable {
private static final long serialVersionUID = 4330727214147295490L;
#XmlElement
private String name = null;
#XmlElement
private String id = null;
...
}
However, when I make the call via my RESTeasy client, however, I get serialization errors. I believe it is due to the fact that this is not a true List or array of objects. Instead, it seems like each entry has an "identifier" on it (in the example above, the "35" or "36").
Given a JSON object like this, how do I map this so that the RESTeasy client can deserialize it? I've not encountered objects of this format before. I obviously can't hard-code each identifier, since there will be several and they could potentially change.
You can see the full JSON object returned by this call (it's sizable) by clicking here. You'll see that this object structure is found throughout this object, rather than using simple Lists or Arrays of objects.
Any ideas? I'd really appreciate any help you can give.
The response is a Map so root element is not your ITunesGenre class but the Map.I suppose that it is clear how to modify response object.
I suppose it could look something like this (though i haven't tested it)
#XmlRootElement
public class Response implements Serializable
{
public Response(){
}
private java.util.Map<String, Genre> genres = new java.util.HashMap<String, Genre> ();
}
public class Genre implements Serializable {
private static final long serialVersionUID = 4330727214147295490L;
public Genre(){
}
#XmlElement
private String name = null;
#XmlElement
private String id = null;
...
}

Jackson Data-Binding with Heterogeneous Json Object

I'm calling a rest service that returns a json object. I'm trying to deserialize the responses to my Java Beans using Jackson and data-binding.
The example Json is something like this:
{
detail1: { property1:value1, property2:value2},
detail2: { property1:value1, property2:value2},
otherObject: {prop3:value1, prop4:[val1, val2, val3]}
}
Essentially, detail1 and detail2 are of the same structure, and thus can be represented by a single class type, whereas OtherObject is of another type.
Currently, I've set up my classes as follows (this is the structure I would prefer):
class ServiceResponse {
private Map<String, Detail> detailMap;
private OtherObject otherObject;
// getters and setters
}
class Detail {
private String property1;
private String property2;
// getters and setters
}
class OtherObject {
private String prop3;
private List<String> prop4;
// getters and setters
}
Then, just do:
String response = <call service and get json response>
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(response, ServiceResponse.class)
The problem is I'm getting lost reading through the documentation about how to configure the mappings and annotations correctly to get the structure that I want. I'd like detail1, detail2 to create Detail classes, and otherObject to create an OtherObject class.
However, I also want the detail classes to be stored in a map, so that they can be easily distinguished and retrieved, and also the fact that the service in in the future will return detail3, detail4, etc. (i.e., the Map in ServiceResponse would look like
"{detail1:Detail object, detail2:Detail object, ...}).
How should these classes be annotated? Or, perhaps there's a better way to structure my classes to fit this JSON model? Appreciate any help.
Simply use #JsonAnySetter on a 2-args method in ServiceResponse, like so:
#JsonAnySetter
public void anySet(String key, Detail value) {
detailMap.put(key, value);
}
Mind you that you can only have one "property" with #JsonAnySetter as it's a fallback for unknown properties. Note that the javadocs of JsonAnySetter is incorrect, as it states that it should be applied to 1-arg methods; you can always open a minor bug in Jackson ;)

Categories