DevUA

/CTO @Software Service and Innovation /JUGLviv leader /JDayLviv co-organiser www.jug.lviv.ua

Meta


Tricky issue on jackson serialization/deserialization

Andriy AndrunevchynAndriy Andrunevchyn

Just few days ago I was implementing small feature on one of our minor projects .  The idea process some data on user interface pass it to backend and send it further to third party service.

So on UI we have code like this

$.post( "/backendService", JSON.stringify(idList); );

Obviously idList contains plain long type id values e.g. 54396 so that on backendService we have following piece of code

@SuppressWarnings("unchecked")
List<Long> idList= mapper.readValue(idListJson, List.class);

And later on I would like include that list as part of other structure

public class ThirdPartyServiceRequest {

public List<Long> idList = new ArrayList<Long>();
..........

so  I did following

ThirdPartyServiceRequest request = new ThirdPartyServiceRequest();
            request.idList.addAll(idList);
            ObjectMapper mapper = new ObjectMapper();
            String json = mapper.writeValueAsString(request);
            executePOSTQuery("ThirdPartyServiceURL", json);
......

So everything pretty clear I wrote unit test where I manually created idList and so on, everything worked like a  charm, but when I started testing the whole solution following line

String json = mapper.writeValueAsString(request);

threw me exception

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: java.lang.String cannot be cast to java.lang.Long (through reference chain: com.pack.ThirdPartyServiceRequest ["idList"]->java.util.ArrayList[0])
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:210)
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:189)
    at com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:216)
    at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContentsUsing(IndexedListSerializer.java:142)
    at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:82)
    at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:73)
    at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:19)
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:575)
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:666)
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:156)
    at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:129)
    at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3387)
    at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:2781)
    at com.pack.BackendService(BackendService.java:151)
Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Long
    at com.fasterxml.jackson.databind.ser.std.NumberSerializers$LongSerializer.serialize(NumberSerializers.java:175)
    at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContentsUsing(IndexedListSerializer.java:136)
    ... 10 more

 

So what a ClassCastException from String to Long is mentioned if we do exactly opposite and convert List<Long> to String ?

I’m sure many watchful readers already figured out issue. I did a mistake in following line

@SuppressWarnings("unchecked")
List<Long> idList = mapper.readValue(idListJson, List.class);

We cannot do it. If we check json we received from UI we will see following line

JSON.stringify(idList)

returns such json

"["26512","26515"]"

so that when we use jackson to deserialize we should be careful and use proper generic for list

@SuppressWarnings("unchecked")
List<String> idList= mapper.readValue(idListJson, List.class);

or provide proper type

List<Long> idList = mapper.readValue(idListJson, new TypeReference<list<long>>() {})

It’s pure developer faults and it’s quite easy to do such mistake so remember about it

 

UPD.

Thx to comments MVMn the proper fix would change not server side but UI part

 

So before writing to idList you have check if it’s really int and not a String and convert it to int on UI if needed with function parseInt

In my case I receive id as value of checkboxes and obviously it is String not int

/CTO @Software Service and Innovation /JUGLviv leader /JDayLviv co-organiser www.jug.lviv.ua