Jackson serialization exception when trying to serialize LocalDateTime - java

I can't figure why when trying to serialize an object I get an exception which looks related to deserialization. My object has a field which is of joda type LocalDateTime
ObjectMapper mapper = new ObjectMapper();
mapper.writeValueAsString(response));
I got the following exception:
org.codehaus.jackson.map.JsonMappingException: java.lang.String cannot be cast to org.joda.time.LocalDateTime
I am trying to serialize. Why it is trying to convert String value to object? I tried to add custom deserializers, but it does not work.
update More of the exception:
org.codehaus.jackson.map.JsonMappingException: java.lang.String cannot be cast to org.joda.time.LocalDateTime (through reference chain: com.my.AccountDetailResponse["registrationDate"])
at org.codehaus.jackson.map.JsonMappingException.wrapWithPath(JsonMappingException.java:218) ~[jackson-mapper-asl-1.9.13.jar:1.9.13]
at org.codehaus.jackson.map.JsonMappingException.wrapWithPath(JsonMappingException.java:183) ~[jackson-mapper-asl-1.9.13.jar:1.9.13]
at org.codehaus.jackson.map.ser.std.SerializerBase.wrapAndThrow(SerializerBase.java:140) ~[jackson-mapper-asl-1.9.13.jar:1.9.13]
at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:158) ~[jackson-mapper-asl-1.9.13.jar:1.9.13]
at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:112) ~[jackson-mapper-asl-1.9.13.jar:1.9.13]
at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:610) ~[jackson-mapper-asl-1.9.13.jar:1.9.13]
at org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:256) ~[jackson-mapper-asl-1.9.13.jar:1.9.13]
at org.codehaus.jackson.map.ObjectMapper._configAndWriteValue(ObjectMapper.java:2575) ~[jackson-mapper-asl-1.9.13.jar:1.9.13]
at org.codehaus.jackson.map.ObjectMapper.writeValueAsString(ObjectMapper.java:2097) ~[jackson-mapper-asl-1.9.13.jar:1.9.13]
tried to add deserializer:
CustomDeserializerFactory deserializerFactory = new CustomDeserializerFactory();
deserializerFactory.addSpecificMapping(LocalDateTime.class, new CustomLocalDateTimeDeserializer());
ObjectMapper mapper = new ObjectMapper();
mapper.setDeserializerProvider(new StdDeserializerProvider(deserializerFactory));
try {
remoteActionDto.setPayload(mapper.writeValueAsString(response));
} catch (IOException e) {
logger.error("Can not convert response to json!", e);
.....
}
the deserializer itself. I does not convert actually, but only proof of concept:
private static class CustomLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
#Override
public LocalDateTime deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return new LocalDateTime();
}
}

I found the problem (or actually a colleague of mine did it). It is the most stupid java behaviour I've ever met. The problem was, that the DTO which contained the LocalDateTime field was populated via reflection, and it seems possible to successfully set a value of type String. A class cast exception occurs when you try to use this field (not when it is being set).
public class MyDto {
// trough reflection, the contained object is a java.lang.String
private LocalDateTime myDate;
}
If you ask why this happened - because we haven't configured a converter for LocalDateTime, but for DateTime instead. My colleague used LocalDateTime by mistake and Jackson silently deserialized it as a String

I've written a quick test class to check what you've provided. It seems to run fine and output the following:
{"value":[2014,2,24,13,42,44,745]}
Granted, that may not be the exact format you're looking for, but either way, here is the class:
public class JsonSerialization {
public static void main(final String[] args) {
try {
final Response response = new Response();
final ObjectMapper mapper = new ObjectMapper();
final String value = mapper.writeValueAsString(response);
System.out.println(value);
}
catch (final Exception e) {
e.printStackTrace();
}
}
public static class Response {
LocalDateTime value = new LocalDateTime();
public LocalDateTime getValue() {
return this.value;
}
public void setValue(final LocalDateTime value) {
this.value = value;
}
}
}
I guess this raises the questions:
Do you have getters and setters for your LocalDateTime property (registrationDate)?
Are you sure the error is occurring where you think it is occurring? The exception is just the part about Jackson, where does it say the writeValueAsString method is called within your code?
I know this isn't part of your question, but in 1.9.13 of Jackson, you should register custom (de)serializers like so:
SimpleModule module = new SimpleModule("", new Version(1, 0, 0, "");
module.addDeserializer(LocalDateTime.class, new CustomLocalDateTimeDeserializer());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);

Register your mapper with JodaModule module.
mapper.register(new JodaModule())
Or try with JodaMapper:
import com.fasterxml.jackson.datatype.joda.JodaMapper;
public static void main(String args[]){
DateTime dateTime = new DateTime(12353434);
JodaMapper mapper = new JodaMapper();
try {
serializedString = mapper.writeValueAsString(dateTime);
System.out.println(serializedString);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
Console Output:
12353434

Related

Use Jackson ObjectMapper to convert between java.time.Instant and java.util.Date

I'm trying to use Jackson's ObjectMapper to map between two classes (SrcMessage and DestMessage, for example).
My SrcMessage class looks something like:
class SrcMessage {
public Instant workTime;
}
My DestMessage class looks like:
class DestMessage {
public Date workTime;
}
I'm using the ObjectMapper like so:
SrcMessage src = new SrcMessage ();
src.workTime = Instant.now ();
ObjectMapper mapper = new ObjectMapper ( );
mapper.registerModule (new JavaTimeModule ());
DestMessage dest = mapper.convertValue (src, DestMessage.class);
When I make the convertValue call I get an exception with a message:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.Date` out of VALUE_NUMBER_FLOAT token
at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: DestMessage["workTime"])
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1468)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1242)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1148)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseDate(StdDeserializer.java:517)
at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateBasedDeserializer._parseDate(DateDeserializers.java:200)
at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:290)
at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:273)
at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:293)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:156)
at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:4231)
I'm stuck. I thought that the JavaTimeModule would be enough to handle this conversion, but apprently not. Is there some other module that I need? Other suggestions on how to make this work? So far the only solution I've found is to ignore the workTime field when doing the mapping, and then convert the value after the mapper has completed. Needless to say, this works for a simple case, but I'd rather not have to enumerate all of the Instant/Date fields and handle them manually. I'd much rather have Jackson handle it internally.
Any ideas?
For converting dates I would recommend using benefits of types, instead of using Jackson ObjectMapper. Use Date from(Instant instant) static method to convert java.time.Instant type into java.util.Date type.
You can do it as presented in example:
import java.time.Instant;
import java.util.Date;
public class StackOverflowAnswer {
public static void main(String[] args) {
SrcMessage srcMessage = new SrcMessage(Instant.now());
DestMessage destMessage = DestMessage.fromSrcMessage(srcMessage);
}
}
class SrcMessage {
public Instant workTime;
public SrcMessage(Instant workTime) {
this.workTime = workTime;
}
}
class DestMessage {
public Date workTime;
DestMessage(Date workTime) {
this.workTime = workTime;
}
static DestMessage fromSrcMessage(SrcMessage srcMessage) {
return new DestMessage(Date.from(srcMessage.workTime));
}
}

Make Jackson deserialize into private fields without setter or constructor

I managed to configure Jackson to serialize a class without any getters or annotations on the private fields inside the class by just using
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
But I don't manage to make a JSON string of this non-static class to get deserialized into an object without any parameterized constructor or setters. Is this possible at all - I think it should through reflection but I don't get exactly how...
Here are 2 tests which need to pass to achieve what I need:
public class ObjectMapperTest {
private ObjectMapper mapper;
#Before
public void init() {
mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}
#Test
public void serialize() throws Exception {
Payload payloadToSerialize = new Payload();
payloadToSerialize.data = "testData";
String serializedPayload = mapper.writeValueAsString(payloadToSerialize);
assertThat(serializedPayload, is("{\"data\":\"testData\"}"));
// --> OK
}
#Test
public void deserialize() throws Exception {
Payload deserializedPayload = mapper.readValue("{\"data\":\"testData\"}", Payload.class);
assertThat(deserializedPayload.data, is("testData"));
// com.fasterxml.jackson.databind.JsonMappingException:
// No suitable constructor found for type [simple type, class ...ObjectMapperTest$Payload]:
// can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
// at [Source: {"data":"testData"}; line: 1, column: 2]
}
public class Payload {
private String data;
public Payload() {
// empty constructor for Jackson
}
}
}
Making the Payload class static would fix the test but static classes are not an option for me as I am not working with inner payload classes in the project. Any ideas how to fix it through object mapper configuration change?
EDIT
As I am using the Jackson object mapper in a Spring MVC application to serialize / deserialize under the hood I need a solution which changes or extends the object mapper configuration only.
You can write your own deserializer to parse the JSON and create the instance of Payload, and then set the data value using reflection.
Exemple :
#Before
public void init() {
mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
mapper.registerModule(
new SimpleModule()
.addDeserializer(Payload.class, new JsonDeserializer<Payload>() {
#Override
public Payload deserialize(JsonParser parser, DeserializationContext ctx)
throws IOException, JsonProcessingException {
JsonNode obj = parser.readValueAsTree(); // Read the JSON as a node
Payload payload = new Payload();
if (obj.isObject() && obj.has("data")) { // The node is an object and has a "data" field
try {
// Use reflection to set the value
Field dataField = payload.getClass().getDeclaredField("data");
dataField.setAccessible(true);
dataField.set(payload, obj.get("data").asText());
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
throw new IOException("Reflection error", ex);
}
}
return payload;
}
}));
}
Edit: If you want something more "generic" you can try to create the instance yourself and change the accessibility of all the fields. Then you tell Jackson to update the values using the JSON.
public <T> T deserialize(final String json, final T instance) throws Exception {
for (Field field : instance.getClass().getDeclaredFields()) {
field.setAccessible(true);
}
mapper.readerForUpdating(instance).readValue(json);
return instance;
}
#Test
public void deserializeUpdate() throws Exception {
Payload deserializedPayload = deserialize("{\"data\":\"testData\"}", new Payload());
assertThat(deserializedPayload.data, is("testData"));
}
I tested this on your Payload class, maybe it doesn't work on more complex objects.

How java jackson deserializer handle both Boolean and Object on same field

I'm working with a 3rd party JSON API, it returns data like this:
{details: {...}, ...}
I use Java Jackson to deserialize this JSON string into a POJO object, the field declaration is :
#JsonProperty("details")
public Details getDetails(){...}
and Details is another class.
Everything is fine until I found that API may return data like this:
{details: false, ...}
If details is empty, it returns false!!! And jackson gave me this exception:
com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class Details] from Boolean value; no single-boolean/Boolean-arg constructor/factory method (through reference chain: ...["details"])
So, how to handle this kind of JSON string? I only need this field to set to null if empty.
The error message from Jackson hints that the library has bulit in support for static factory methods. This is (perhaps) a simpler solution than a custom deserializer:
I created this example POJO, with a static factory method, annotated so that Jackson uses it:
public class Details {
public String name; // example property
#JsonCreator
public static Details factory(Map<String,Object> props) {
if (props.get("details") instanceof Boolean) return null;
Details details = new Details();
Map<String,Object> detailsProps = (Map<String,Object>)props.get("details");
details.name = (String)detailsProps.get("name");
return details;
}
}
test method:
public static void main(String[] args)
{
String fullDetailsJson = "{\"details\": {\"name\":\"My Details\"}} ";
String emptyDetailsJson = "{\"details\": false} ";
ObjectMapper mapper = new ObjectMapper();
try {
Details details = mapper.readValue(fullDetailsJson, Details.class);
System.out.println(details.name);
details = mapper.readValue(emptyDetailsJson, Details.class);
System.out.println(details);
} catch (Exception e) {
e.printStackTrace();
}
}
result is as expected:
My Details
null
Make a custom JsonDeserializer to handle deserializing your Details object in which you either return null if you get false or pass the object to the default deserializer if it's an actual object. Pseudocode:
public class CustomDeserializer extends JsonDeserializer<Details>{
#Override
public Details deserialize(JsonParser jsonParser, DeserializationContext ctx){
//if object use default deserializer else return null
}
}
You'll also have to write an ObjectMapperProvider to register your deserializer like so:
#Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper>{
private ObjectMapper mapper;
public ObjectMapperProvider(){
mapper = new ObjectMapper();
SimpleModule sm = new SimpleModule();
sm.addDeserializer(Details.class, new CustomDeserializer());
mapper.registerModule(sm);
}
public ObjectMapper getContext(Class<?> arg0){
return mapper;
}
}

How to preserve the offset while deserializing OffsetDateTime with Jackson

In an incoming JSON, I have an ISO8601-compliant datetime field, containing zone offset. I'd like to preserve this offset, but unfortunately Jackson defaults to GMT/UTC while deserializing this field (what I understood from http://wiki.fasterxml.com/JacksonFAQDateHandling).
#RunWith(JUnit4.class)
public class JacksonOffsetDateTimeTest {
private ObjectMapper objectMapper;
#Before
public void init() {
objectMapper = Jackson2ObjectMapperBuilder.json()
.modules(new JavaTimeModule())
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.build();
}
#Test
public void test() throws IOException {
final String json = "{ \"date\": \"2000-01-01T12:00:00.000-04:00\" }";
final JsonType instance = objectMapper.readValue(json, JsonType.class);
assertEquals(ZoneOffset.ofHours(-4), instance.getDate().getOffset());
}
}
public class JsonType {
private OffsetDateTime date;
// getter, setter
}
What I'm getting here is:
java.lang.AssertionError: expected:<-04:00> but was:<Z>
How can I make the returned OffsetDateTime to contain the original Offset?
I'm on Jackson 2.8.3.
Change your Object Mapper to this to disable the ADJUST_DATES_TO_CONTEXT_TIME_ZONE.
objectMapper = Jackson2ObjectMapperBuilder.json()
.modules(new JavaTimeModule())
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
.build();
Could you try
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
?
According to FAQ you linked, it should provide you with format 1970-01-01T00:00:00.000+0000. This format contains timezone offset (+0000).

JodaTime LocalTime to JSON - Actual Stack Overflow

I am trying to serialize Java objects to JSON. One of my Java objects has a JodaTime LocalTime object as one of its fields.
A fair number of my Java objects also have various fields that are Collections that could be empty. I want to prevent the serialization of JSON that looks like this:
{id: 2348904, listOfThings: [], listOfStuff: [], nowASet: []}
In this scenario where those three Collections are empty, I would rather see this JSON:
{id: 2348904}
The correct way to do such a thing is to configure the ObjectMapper with the following line of code:
objectMapper.setSerializationInclusion(Include.NON_EMPTY);
This works just fine...until I hit that Java object with the LocalTime inside of it. That's when I get an actual java.lang.StackOverflowError.
It seems to be ping-ponging between JodaDateSerializerBase.isEmpty() and JsonSerializer.isEmpty(). I'm not sure how, though, because they don't call each other.
I managed to make a SSSSSSCCCCEEEE, or whatever the hell the acronym is, as follows:
package whatever.you.like;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import org.joda.time.LocalTime;
import org.junit.Test;
public class TestClass {
public class JodaMapper extends ObjectMapper {
private static final long serialVersionUID = 34785437895L;
public JodaMapper() {
registerModule(new JodaModule());
}
public boolean getWriteDatesAsTimestamps() {
return getSerializationConfig().isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
public void setWriteDatesAsTimestamps(boolean state) {
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, state);
}
}
private class Thing {
private LocalTime localTime;
public Thing() {}
public void setLocalTime(LocalTime localTime) {
this.localTime = localTime;
}
public LocalTime getLocalTime() {
return localTime;
}
}
#Test
public void extendObjectMapperTest() throws JsonProcessingException {
JodaMapper objectMapper = new JodaMapper();
objectMapper.setWriteDatesAsTimestamps(false);
objectMapper.setSerializationInclusion(Include.NON_EMPTY);
Thing thing = new Thing();
LocalTime localTime = new LocalTime(12389340L);
thing.setLocalTime(localTime);
System.out.println("Never manages to print this out: " + objectMapper.writeValueAsString(thing));
}
#Test
public void configureObjectMapperTest() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JodaModule());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.setSerializationInclusion(Include.NON_EMPTY);
Thing thing = new Thing();
LocalTime localTime = new LocalTime(12389340L);
thing.setLocalTime(localTime);
System.out.println("Never manages to print this out: " + objectMapper.writeValueAsString(thing));
}
}
I tried both extending the ObjectMapper and configuring the ObjectMapper, and I get the same error each time.
Dependencies:
JodaTime 2.6
FasterXML's Jackson 2.5.0
FasterXML's Jackson-DataType-Joda 2.5.0
Interestingly, you can find in that GitHub a unit test ("testLocalDateSer()") that claims to succeed using the Include.NON_EMPTY qualifier. I fail to see how it could possibly function.
Upgrade to
FasterXML's Jackson 2.5.3
FasterXML's Jackson-DataType-Joda 2.5.3.
This works.
#Test
public void configureObjectMapperTest() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JodaModule());
// objectMapper.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false);
objectMapper.setSerializationInclusion(Include.NON_EMPTY);
Thing thing = new Thing();
LocalTime localTime = new LocalTime(12389340L);
thing.setLocalTime(localTime);
System.out.println("Never manages to print this out: " + objectMapper.writeValueAsString(thing));
}

Categories

Resources