4

I am having trouble deserializing a org.joda.time.DateTime field from JSON into a case class.

The JSON:
val ajson=parse(""" { "creationDate": "2013-01-02T10:48:41.000-05:00" }""")

I also set these serialization options:
implicit val formats = Serialization.formats(NoTypeHints) ++ net.liftweb.json.ext.JodaTimeSerializers.all

And the deserialization:
val val1=ajson.extract[Post]

where Post is:
case class Post(creationDate: DateTime){ ... }

The exception I get is:

 net.liftweb.json.MappingException: No usable value for creationDate
    Invalid date format 2013-01-02T10:48:41.000-05:00

How can I deserialize that date string into a DateTime object?

EDIT:
This works: val date3= new DateTime("2013-01-05T06:24:53.000-05:00") which uses the same date string from the JSON as in the deserialization. What's happening here?

Adrian
  • 5,603
  • 8
  • 53
  • 85

2 Answers2

9

Seems like it is the DateParser format that Lift uses by default. In digging into the code, you can see that the parser attempts to use DateParser.parse(s, format) before passing the result to the constructor for org.joda.time.DateTime.

object DateParser {
  def parse(s: String, format: Formats) = 
    format.dateFormat.parse(s).map(_.getTime).getOrElse(throw new MappingException("Invalid date format " + s))
}

case object DateTimeSerializer extends CustomSerializer[DateTime](format => (
  {
    case JString(s) => new DateTime(DateParser.parse(s, format))
    case JNull => null
  },
  {
    case d: DateTime => JString(format.dateFormat.format(d.toDate))
  }
))

The format that Lift seems to be using is: yyyy-MM-dd'T'HH:mm:ss.SSS'Z'

To get around that, you could either specify the correct pattern and add that to your serialization options, or if you would prefer to just have the JodaTime constructor do all the work, you could create your own serializer like:

case object MyDateTimeSerializer extends CustomSerializer[DateTime](format => (
  {
    case JString(s) => tryo(new DateTime(s)).openOr(throw new MappingException("Invalid date format " + s))
    case JNull => null
  },
  {
    case d: DateTime => JString(format.dateFormat.format(d.toDate))
  }
))

And then add that to your list of formats, instead of net.liftweb.json.ext.JodaTimeSerializers.all

jcern
  • 7,798
  • 4
  • 39
  • 47
  • I have 2 follow-up questions: (1) is this "YYYY-MM-dd'T'HH:mm:ss.SSS'-'Z" my format for dates like "2013-01-02T10:48:41.000-05:00" (2) is this the correct way to add my format to formats: `implicit val formats = Serialization.formats(NoTypeHints) ++ MyDateTimeSerializer – Adrian Apr 15 '13 at 15:30
  • figured out (2): wrap MyDateTimeSerializer in a list: for those interested it looks like this: `implicit val formats = Serialization.formats(NoTypeHints) ++ List(MyDateTimeSerializer)` – Adrian Apr 15 '13 at 15:43
  • I'm not super familiar with all the intricacies of DateFormat patterns, but what you suggest looks close. Two things, the year should be lowercase `y` and the '-' is part of the timezone (IE -5 hours from UTC), so you should probably remove that. `yyyy-MM-dd'T'HH:mm:ss.SSSZ` Is probably close to what you are looking for, but that may have an issue with the `:` in the TZ. As for adding the custom serializer, wrapping it in the `List` should work for you. – jcern Apr 15 '13 at 15:48
  • ok I went with (2) and it's not working, but I think the parsing is fine, but the JSON string may have some funky chars in it which need to be escaped. I deduced by because this works `new DateTime(parse(""" { "creationDate": "2013-01-05T06:24:53.000-05:00" }""").extract[Post3].creationDate)` but when the JSON is given as input by user it doesn't work `val date3= new DateTime(parse(Text(name).toString()).extract[Post3].creationDate)` – Adrian Apr 15 '13 at 16:28
0

Not perhaps 100% elegant but is just a few lines, quite readable and works:

val SourISODateTimeFormat = DateTimeFormat.forPattern("YYYY-MM-dd'T'HH:mm:ss.SSSZ")
val IntermediateDateTimeFormat = DateTimeFormat.forPattern("YYYY-MM-dd'T'HH:mm:ss'Z'")

def transformTimestamps(jvalue: JValue) = jvalue.transform {
  case JField(name @ ("createdTime" | "updatedTime"), JString(value)) =>
    val dt = SourceISODateTimeFormat.parseOption(value).get
    JField(name, JString(IntermediateDateTimeFormat.print(dt)))
}
Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111