Hot questions for Using Joda-Time in serialization

Top Java Programmings / Joda-Time / serialization

Question:

I've a Spring Boot application with RESTful endpoints that I want to add custom serializers to for joda-time but I can't get the applications default Jackson serailzier to recognize my custom one's.

I created RESTFul endpoints using @RepositoryRestResource

@RepositoryRestResource(collectionResourceRel = "x", path = "x") 
public interface XRepository extends PagingAndSortingRepository<X, Long>
{
}

I then have a GET call to return all object X's:

http://localhost:8181/x

This is my serializer:

@Component
public class JsonDateSerializer extends JsonSerializer<DateTime>
{

private static DateTimeFormatter formatter = DateTimeFormat.forPattern("dd/MM/yyyy");

@Override
public void serialize(DateTime value, JsonGenerator gen, 
                      SerializerProvider arg2)
    throws IOException, JsonProcessingException {

    gen.writeString(formatter.print(value));
}
}

And I add it to the property Getter as follows:

@JsonSerialize(using=JsonDateSerializer.class)
public DateTime getDateCreated()
{
    return dateCreated;
}

This serializer works perfectly in a normal application but when I try to use this in my Spring Boot application these serializers get ignored.

How can I get Spring Boot to recognize these serializers?


Answer:

Ok so after much torment I found out the answer. I was using the wrong library for the serialization and deserialization of the joda-datetime.

I was using

org.codehaus.jackson

when I should have been using

com.fasterxml.jackson

I guess this is an easy mistake as both libraries have almost identical properties and methods because com.fasterxml.jackson is built on top of org.codehaus.jackson.

Silly mistake looking back now but a valuable lesson learnt to ALWAYS check your using the correct library!!!!

Question:

I've written some Java software that very frequently persists and retrieves Joda-Time DateTime objects from Redis. I just serialise and deserialise the objects at present. The software reads the objects about 50 times more often than it writes. I've not profiled serialising/deserialising Joda-Time objects, but the software has scaled well, computationally, under load and I'm happy with the performance.

What hasn't scaled well is memory usage. The serialised Joda-Time objects are pretty big and a decent size Redis instance can only take about 3 days worth of customer data before I need to flush it out to a relational database on disk. A secondary issue is Redis' own backup mechanisms seem harder to manage the larger the dataset gets...

Setting aside the temptation to throw more RAM at the problem, I've thought of the following ideas so far:

  • serialise then compress the objects before persisting
  • persist as a ISO date format string
  • persist as some other Joda-compatible string format

I will try out and profile these before deciding, but I wonder if anyone can think of a more efficient way of reducing the memory footprint of persisted Joda objects without breaking the computational bank?


Answer:

ISO 8601

While I know nothing of Redis… Generally speaking, the easiest and most efficient way to serialize Joda-Time objects is to take advantage of their built-in support for the sensible, unambiguous, standard ISO 8601 string formats for date-time values.

For a zoned date-time value, the standard provides a YYYY-MM-DDTHH:MM:SS.SSS±HH:SS format such as 2014-10-24T21:17:30+02:00 or 2014-10-24T19:17:30Z (Z for Zulu means an offset of 00:00 from UTC).

The various Joda-Time 2.5 classes use ISO 8601 as their defaults for parsing and generating String representations of date-time values.

Generating Strings

For DateTime, simply call its toString method either explicitly or implicitly.

String output = DateTime.now( DateTimeZone.forID( "America/Montreal" ) ).toString();

Generally best to work with UTC when storing date-time values. Joda-Time lets you easily adjust to UTC.

DateTime nowMontreal = DateTime.now( DateTimeZone.forID( "America/Montreal" ) );
DateTime nowUtc = nowMontreal.withZone( DateTimeZone.UTC );
String output = nowUtc.toString();

Another example.

DateTime output = DateTime.now( DateTimeZone.UTC ).toString();
Parsing Strings

Parsing is just as easy. The only issue is time zone. If you omit a time zone, generally Joda-Time will assign the JVM’s current default time zone. Usually better if you explicitly specify the desired time zone.

DateTime dateTimeMontreal = new DateTime( "2014-10-24T19:17:30Z", DateTimeZone.forID( "America/Montreal" ) );

or, for UTC…

DateTime dateTimeUtc = new DateTime( "2014-10-24T19:17:30Z", DateTimeZone.UTC ) );
java.time

Another alternative is the new java.time package built into Java 8. Inspired by Joda-Time, java.time is similar in many ways. But one difference is that java.time by default generates string representations by extending the ISO 8601 standard to append the name of the time zone. While standard format has an offset-from-UTC, you loose the actual time zone information. (A time zone is an offset plus the rules for Daylight Saving Time and other anomalies in the present, future, and past.)

On the other hand, generally it is best to store date-time in UTC. If you really care about the time zone at the time of data-entry, it’s generally best to store that information separately in addition to the UTC-adjusted value.

In java.time, the Instant class represents a moment on the timeline in UTC.

Instant instant = Instant.parse( "2014-10-24T19:17:30Z" );
String outputInstant = instant.toString();

2014-10-24T19:17:30Z

To adjust into a time zone, specify a ZoneId to get a ZonedDateTime.

ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );
String outputZdt = zdt.toString();

2014-10-24T15:17:30-04:00[America/Montreal]

Question:

I have an existing app using Java JodaTime. However, after upgrading to the latest json4s-core library 3.6.0-M3, I'm getting the following errors converting date strings with a timezone to a Date.

Caused by: java.lang.IllegalArgumentException: No instant converter found for type: org.json4s.ext.DateParser$ZonedInstant

It happens when I write a custom DateTime serializer with fallback for multiple formats:

 case JString(s) ⇒ Try(dateTimeFormat.parseDateTime(s)).getOrElse(new DateTime(DateParser.parse(s, format)))

Example string causing the issue: 2018-05-02T21:43:29Z

I made sure I'm using jodatime 2.9.2 and the matching json4s-ext lib


Answer:

I'll leave my answer in case someone runs into a similar issue. I realized that since I'm overriding the default DateTime serializer, I needed to make the following change to my custom serializer to deal with ZonedInstant

case JString(s) ⇒ Try(dateTimeFormat.parseDateTime(s)).getOrElse({
    val zonedInstant = DateParser.parse(s, format)
    new DateTime(zonedInstant.instant, DateTimeZone.forTimeZone(zonedInstant.timezone))
  })

Question:

I've got REST webservice exposing resources with creation date. It is written in Java 8 - using LocalDateTime. Jackson 2 is serializing it to:

"createdDate": [2016, 5, 19, 18, 6, 59, 639000000]

In other application my goal is to consume this rest, but there is only Java 7, so I decided to use joda-time library in DTO. I've set up RestTemplate like this:

        RestTemplate restTemplate = new RestTemplate();
        MappingJackson2HttpMessageConverter e = new MappingJackson2HttpMessageConverter();
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JodaModule());
        e.setObjectMapper(mapper);
        messageConverters.add(e);
        restTemplate.setMessageConverters(messageConverters);
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<NewUserData> request = new HttpEntity<>(user, headers);

POST is successful, however while deserializing answer (with createdDate field from above) exception is thrown:

org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Value 696000000 for millisOfSecond must be in the range [0,999] (through reference chain: com.foobar.dto.user.UserItem["createdDate"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Value 696000000 for millisOfSecond must be in the range [0,999] (through reference chain: com.foobar.dto.user.UserDisplayItem["createdDate"])

My dependencies looks like this:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>3.2.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc-portlet</artifactId>
        <version>3.2.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>3.2.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time</artifactId>
        <version>2.9.3</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-joda</artifactId>
        <version>2.7.4</version>
    </dependency>

Should I write my own Jackson deserializer or maybe i could use other library/version?


Answer:

I believe Jackson serializes Java 8 temporal types with nanosecond precision by default, while Joda-Time only supports milliseconds. What you'd need in your server written in Java 8 is to serialize the LocalDateTime property as "createdDate": [2016, 5, 19, 18, 6, 59, 639] instead of "createdDate": [2016, 5, 19, 18, 6, 59, 639000000].

You can change this behaviour in your Java 8 server by configuring the ObjectMapper instance you're using for serialization:

ObjectMapper mapper = ... //this is the instance used to serialize the data
mapper.disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);

In case you cannot or do not want to change the Java 8 server, Jackson has a corresponding DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS flag. Sadly, I don't think the joda module supports it at the moment (see implementation). Because of this, I think your only option right now is to implement a custom deserializer, or better yet, submit a PR with the improvement to joda module.

Question:

I'm using Spring Boot with JPA and Jodatime.

Currently I have a model attribute annotated like this:

@Type(type="org.jadira.usertype.dateandtime.joda.PersistentLocalTime")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
private LocalTime startTime;

When serialized, the JSON is becoming:

{
"startTime" : {
      "hourOfDay" : 12,
      "minuteOfHour" : 0,
      "secondOfMinute" : 0,
      "millisOfSecond" : 0,
      "chronology" : {
        "zone" : {
          "fixed" : true,
          "id" : "UTC"
        }
      }
}

I want to know if there is a way to serialize that attribute to be like this:

{
  "startTime": "12:00"
}

I have tried to put that @JsonFormat annotation, but it does not seem to work.


Answer:

You could do something like:

@JsonSerialize(using = MyLocalTimeSerializer.class)
private LocalTime startTime;

and then create MyLocalTimeSerializer.class:

public class MyLocalTimeSerializer extends JsonSerializer<LocalTime> {

    @Override
    public void serialize(
            LocalTime time, 
            JsonGenerator gen, 
            SerializerProvider arg2) throws IOException, JsonProcessingException {
        gen.writeString(time.toString("HH:mm"));
    }

}

This requires the jackson-databind library.

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>${jackson.databind-version}</version>
    </dependency>

Question:

I have seen answers like this one that show the use of TypeFactory.constructMapType(...) to de-serialise a JSON string to a map where the key/value combinations are other than String. I have a situation where I have strings that should de-serialise to multiple different types, not just one.

I realise that one solution would be define my own class and not use Map, but I am wondering if I can use pure configuration instead?

Here is my test code.

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.datatype.joda.JodaModule;

public class JodaTimeMapTest {

   public static void main(final String[] args) throws Exception {
      // Map with dates.
      final DateTime now = new DateTime().withZone(DateTimeZone.UTC);
      final LocalDateTime nowLocal = new LocalDateTime();
      final LocalDateTime notNowLocal = new LocalDateTime(2007, 3, 25, 2, 30, 0);
      final Map<String, Object> dateMap = new HashMap<>();
      dateMap.put("now", now);
      dateMap.put("nowLocal", nowLocal);
      dateMap.put("notNowLocal", notNowLocal);

      // Serialise map to string.
      final ObjectMapper mapper = mapper();
      final String dateMapJson = mapper.writeValueAsString(dateMap);

      // De-serialise string to map.
      final TypeFactory typeFactory = mapper.getTypeFactory();
      final MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, Object.class);
      final HashMap<String, Object> dateMapFromJson = mapper.readValue(dateMapJson, mapType);

      // First one has dates, second has strings.
      printMap(dateMap);
      printMap(dateMapFromJson);
   }

   private static void printMap(final Map<String, Object> map) {
      System.out.println(map.entrySet().stream().map(entry -> {
         return entry.getKey() + ", type = " + entry.getValue().getClass().getName() + ", value = " + entry.getValue();
      }).collect(Collectors.joining(System.lineSeparator())));
   }

   private static ObjectMapper mapper() {
      final ObjectMapper mapper = new ObjectMapper();
      mapper.registerModule(new JodaModule());
      mapper.configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
      return mapper;

   }

}

The output of this class shows that reading in, Jakcson can only assume these are strings:

now, type = org.joda.time.DateTime, value = 2018-05-04T09:10:26.063Z
notNowLocal, type = org.joda.time.LocalDateTime, value = 2007-03-25T02:30:00.000
nowLocal, type = org.joda.time.LocalDateTime, value = 2018-05-04T19:10:26.193
now, type = java.lang.String, value = 2018-05-04T09:10:26.063Z
notNowLocal, type = java.lang.String, value = 2007-03-25T02:30:00.000
nowLocal, type = java.lang.String, value = 2018-05-04T19:10:26.193
Sample Solution

Based on the answer aussie gave, here is a solution that works for me. In my example, the map key is all I need to determine what sort of Joda date/time class the value is.

First is my implementation of the de-serialiser aussie told me about.

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

import org.joda.time.DateTime;
import org.joda.time.LocalDateTime;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

/** De-serialise values from a map that contains Joda times and strings. */
public class JodaMapDeserialiser extends StdDeserializer<Object> {

   /** Mapping between keys in the map to a type of Joda time. */
   enum DateType {
      DATE_TIME("now"), LOCAL_DATE_TIME("notNowLocal", "nowLocal");

      final List<String> keys;

      DateType(final String... keys) {
         this.keys = Arrays.asList(keys);
      }

      public static DateType forKeyString(final String keyString) {
         return Stream.of(values()).filter(dateTypes -> dateTypes.keys.contains(keyString)) //
               .findFirst().orElse(null);
      }
   }

   public JodaMapDeserialiser() {
      super(Object.class);
   }

   @Override
   public Object deserialize(final JsonParser p, final DeserializationContext ctxt)
         throws IOException, JsonProcessingException {

      // Each entry in the map has a key and value.
      final String value = p.readValueAs(String.class);
      final String key = p.getCurrentName();

      // Convert the value depending on what the key is.
      switch (DateType.forKeyString(key)) {
         case DATE_TIME:
            return DateTime.parse(value);

         case LOCAL_DATE_TIME:
            return LocalDateTime.parse(value);

         default:
            return value;
      }
   }
}

And here is some slightly revised testing code.

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.datatype.joda.JodaModule;

public class JodaTimeMapTest {

   public static void main(final String[] args) throws Exception {

      // Map with dates.
      final DateTime now = new DateTime().withZone(DateTimeZone.UTC);
      final LocalDateTime nowLocal = new LocalDateTime();
      final LocalDateTime notNowLocal = new LocalDateTime(2007, 3, 25, 2, 30, 0);
      final Map<String, Object> dateMap = new HashMap<>();
      dateMap.put("now", now);
      dateMap.put("nowLocal", nowLocal);
      dateMap.put("notNowLocal", notNowLocal);

      // Serialise map to string.
      final ObjectMapper mapper = mapper();
      final String dateMapJson = mapper.writeValueAsString(dateMap);

      // De-serialise string to map.
      final TypeFactory typeFactory = mapper.getTypeFactory();
      final MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, Object.class);
      final HashMap<String, Object> dateMapFromJson = mapper.readValue(dateMapJson, mapType);

      // First one has dates, second has strings.
      System.out.println("Actual map.");
      printMap(dateMap);
      System.out.println("Map de-serialised from JSON.");
      printMap(dateMapFromJson);
      System.out.println("Maps are equal: " + dateMap.equals(dateMapFromJson));
   }

   private static void printMap(final Map<String, Object> map) {
      System.out.println(map.entrySet().stream().map(entry -> {
         return "  " + entry.getKey() + ", type = " + entry.getValue().getClass().getName() + ", value = "
               + entry.getValue();
      }).collect(Collectors.joining(System.lineSeparator())));
   }

   private static ObjectMapper mapper() {
      final ObjectMapper mapper = new ObjectMapper();
      mapper.registerModule(new JodaModule());
      mapper.configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

      final SimpleModule dateDeserializerModule = new SimpleModule();
      dateDeserializerModule.addDeserializer(Object.class, new JodaMapDeserialiser());
      mapper.registerModule(dateDeserializerModule);

      return mapper;

   }
}

And the output is:

Actual map.
  now, type = org.joda.time.DateTime, value = 2018-05-05T04:03:20.684Z
  notNowLocal, type = org.joda.time.LocalDateTime, value = 2007-03-25T02:30:00.000
  nowLocal, type = org.joda.time.LocalDateTime, value = 2018-05-05T14:03:20.809
Map de-serialised from JSON.
  now, type = org.joda.time.DateTime, value = 2018-05-05T04:03:20.684Z
  notNowLocal, type = org.joda.time.LocalDateTime, value = 2007-03-25T02:30:00.000
  nowLocal, type = org.joda.time.LocalDateTime, value = 2018-05-05T14:03:20.809
Maps are equal: true

Finally, my maven dependencies (joda time is included in jackson-datatype-joda).

<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-core</artifactId>
   <version>2.9.5</version>
</dependency>
<dependency>
   <groupId>com.fasterxml.jackson.datatype</groupId>
   <artifactId>jackson-datatype-joda</artifactId>
   <version>2.9.5</version>
</dependency>
Other options

Overall, the options I found:

  • Create type definition for a single type combination: Hashmap with String keys and DateTime values.
  • Create a custom class to map key/values to.
  • Create a de-serialiser to define rules for how to translate string to object.

To further explore the different options I found, I wrote up this blog post.


Answer:

Your date objects are serialized as string thanks to the Jodamodule that you registered: "now":"2018-05-04T11:42:15.454Z"

When you deseriallize the Json string you expect a HashMap with String keys and Object values. How would Jackson know that those objects should be different type of dates, it sees only strings..?

What you could do is to create a custom deserializer for this and implement the logic to deserialize each date correctly (for example you could determine the type by regex).

public class MyDateDeserializer extends StdDeserializer<Object> {
    public MyDateDeserializer() {
        super(Object.class);
    }
    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        return convertStringToTheProperDate(p.readValueAs(String.class));
    }
    private Object convertStringToTheProperDate(String dateAsString) {
       // implement the logic to convert the string to the proper type
       return null;
    }
}

And then register the deserializer:

SimpleModule dateDeserializerModule = new SimpleModule();
dateDeserializerModule.addDeserializer(Object.class, new MyDateDeserializer());
mapper.registerModule(dateDeserializerModule);

Question:

There is a table

CREATE TABLE `test` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(255) NOT NULL,
 `lastmodifiedTimestamp` DATETIME ON UPDATE CURRENT_TIMESTAMP,
  `creationTimestamp` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`),
 UNIQUE KEY `Unique` (`name`)
)

and a Entity class which i use for Json conversion as well as database storage. The issue here is that when i try to create a Test hibernate gives an exception com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'creationTimestamp' cannot be null However i can run an sql query without passing Timestamp fields.How can i make hibernate avoid sending these timestamp fields in the insert query?

I cannot mark them as transient because i need it the timestamp fields during de-serialization.When i do a get for Test object.

It anyway gives Caused by: javax.persistence.EntityNotFoundException: No row with the given identifier exists perhaps because of both @Column and @Transient on the fields

Below is the entity class

@Entity
@Table(name = "test")
public class Test implements Serializable {
 private long id;
 @JsonSerialize(using = DateTimeSerializer.class)
  private DateTime creationTimestamp; //joda datetime used in response body
  @JsonIgnore                       //used in http header
  private DateTime lastModofiedTimestamp;

@Id
  @Column(name = "id")
  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  @Column(name = "creationTimestamp")
  @Type(type = "org.joda.time.contrib.hibernate.PersistentDateTime")
  public DateTime getCreationTimestamp() {
    return creationTimestamp;
  }

  public void setCreationTimestamp(DateTime creationTimestamp) {
    this.creationTimestamp = creationTimestamp;
  }
@Column(name = "lastmodifiedTimestamp")
  @Type(type = "org.joda.time.contrib.hibernate.PersistentDateTime")
  public DateTime getLastModofiedTimestamp() {
    return lastModofiedTimestamp;
  }

  public void setLastModofiedTimestamp(DateTime lastModofiedTimestamp)   {
    this.lastModofiedTimestamp = lastModofiedTimestamp;
  }
}

If i pass a proper value of creationTimestamp from the UI which populates the field on json de-serialization is used by hibernate to pass to the insert query.It works in this case and creates the row in database But i want this value not to be sent from UI and ignored if sent.This is for the create call But during get call I want the timestamp values to be populated in the object


Answer:

You can use @Formula instead of the @Column to annotate the creationTimestamp field but use the same creationTimestamp in the formula. See for example

Question:

I am trying to figure out if Joda DateTime is serializable (when using default Java serializable) or do I need to provide my own serialization implementation (using externalizable or a third party serialization library). Currently, I tried serializing and deserializing a class containing an instance variable of type Joda DateTime but I get a serialzation exception for DateTime.

relevant section of class

public class TestClass implements Serializable {

private DateTime dateTime;

    protected DateTime getDateTime() {
        return dateTime;
    }

    protected void setDateTime(DateTime dateTime) {
        this.dateTime = dateTime;
    }

Answer:

Joda DateTime is serializable - the error you're sharing could be a project issue. Try cleaning and building the project again to see if the issue gets resolved. Are there are any other errors in the project or after you run the project?

Question:

Currently, I'm trying to migrate a project form joda time to java8 time api.

After the replacing joda components with java8 components, I have serialization problems. Of course, I added dependencies and register new JavaTimeModule() module. But when I try to serialize

LocalDateTime.of(1988, 11, 10, 7, 31, 32, 0)

I receive an array [1988,11,10,7,31,32] but [1988,11,10,7,31,32,**0**] is expected (with nanos).

So, the question is: is it possible to serialize time with nanos, even if it's 0?


Answer:

After having a look at the jackson LocalDateTimeSerializer code, the seconds and the nanos are explicitly ignored when their values are 0

private final void _serializeAsArrayContents(LocalDateTime value, JsonGenerator g, SerializerProvider provider) throws IOException {
    g.writeNumber(value.getYear());
    g.writeNumber(value.getMonthValue());
    g.writeNumber(value.getDayOfMonth());
    g.writeNumber(value.getHour());
    g.writeNumber(value.getMinute());
    int secs = value.getSecond();
    int nanos = value.getNano();
    if (secs > 0 || nanos > 0) {
        g.writeNumber(secs);
        if (nanos > 0) {
            if (this.useNanoseconds(provider)) {
                g.writeNumber(nanos);
            } else {
                g.writeNumber(value.get(ChronoField.MILLI_OF_SECOND));
            }
        }
    }
}

In order to achieve the desired result you have to create a custom serializer and register its module after the JavaTimeModule

public class LocalDateTimeWithNanoSerializer extends StdSerializer<LocalDateTime> {

    public LocalDateTimeWithNanoSerializer() {
        super(LocalDateTime.class);
    }

    @Override
    public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeStartArray();
        gen.writeNumber(value.getYear());
        gen.writeNumber(value.getMonthValue());
        gen.writeNumber(value.getDayOfMonth());
        gen.writeNumber(value.getHour());
        gen.writeNumber(value.getMinute());
        gen.writeNumber(value.getSecond());
        gen.writeNumber(value.getNano());
        gen.writeEndArray();
    }
}
public class Application {
    public static void main(String[] args) {
        SimpleModule myModule = new SimpleModule();
        myModule.addSerializer(new LocalDateTimeWithNanoSerializer());
        ObjectMapper objectMapper = new ObjectMapper()
              .registerModule(new JavaTimeModule())
              .registerModule(myModule)
    }
}

Question:

According to the documentation found here https://github.com/FasterXML/jackson-datatype-joda

the following code snippet should pass

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JodaModule());

final String INPUT_JSON = "{\"start\" : \"1972-12-28T12:00:01.000Z\"}";
Bean bean = mapper.readValue(INPUT_JSON, Bean.class);
String json = mapper.writeValueAsString(bean);
Assert.assertEquals(INPUT_JSON, json);

However, this instead returns:

org.junit.ComparisonFailure: 
Expected :{"start" : "1972-12-28T12:00:01.000Z"}
Actual   :{"start":94392001000}

Here is the pom file i'm using:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>test</groupId>
    <artifactId>test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.7</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-joda</artifactId>
            <version>2.4.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

</project>

Why isn't the datetime object not serialized correctly?


Answer:

The test seems to be missing this feature.

mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS ,false);

Question:

I have Pojo as

public class Person
{

    private String name;

    @JsonSerialize(using = LocalDateSerializer.class)
    @JsonDeserialize(using = LocalDateDeserializer.class)
    private LocalDate dob;

    public Person()
    {
    }

    public Person(String name, LocalDate dob)
    {
        this.name = name;
        this.dob = dob;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public LocalDate getDob()
    {
        return dob;
    }

    public void setDob(LocalDate dob)
    {
        this.dob = dob;
    }
}

When I tried to run code like

public static void main(String[] args) throws IOException{

        Person person = new Person("Bob",new LocalDate(1900,02,22));
        Gson gson = new Gson();
        String json = gson.toJson(person);
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JodaModule());
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);


        Person returnPojo = mapper.readValue(json, Person.class);

        System.out.print(returnPojo);
}

I keep get Exception as

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (START_OBJECT), expected START_ARRAY: expected JSON Array, String or Number at [Source: {"name":"Bob","dob":{"iLocalMillis":-2204496000000,"iChronology":{"iBase":{"iMinDaysInFirstWeek":4}}}}; line: 1, column: 14] (through reference chain: pojo.Person["dob"])

What have I done wrong?


Answer:

You are writing JSON with Gson(which doesn't understand @JsonSerialize). Since the date is not in the right format, Jackson fails to read the date.

Try something like this:

    Person person = new Person("Bob",new LocalDate(1900,02,22));

    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JodaModule());
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

    String json = mapper.writer().writeValueAsString(person);

    Person returnPojo = mapper.readValue(json, Person.class);

    System.out.print(returnPojo);