goralph
goralph

Reputation: 1086

Map Option[String] to Option[Int] in JSON formatter

Here's my use case:

External JSON

..
lat: "xx.xx",
lng: "xx.xx",
..

My (working) reader, as is:

..
(__ \ 'lat).read[Option[String]] ~
(__ \ 'lng).read[Option[String]] ~
..

All I would like to do is map the String to an Int. Since, logically, lat & long coords should be represented as such.

Here is what I tried, and doesn't work:

(__ \ 'lat).read[Option[String]].map(_.map(_.toInt) orElse None)) ~

My case class when trying to do the above, otherwise they're Option[String] and work:

...
lat: Option[Int],
lng: Option[Int],
...

I think the problem might be simply syntactical, however any other combinations with parens doesn't help.

Edit:

It compiles, however the JSON doesn't parse. It simply doesn't build my objects.

If I try this:

(__ \ 'lat).read[Option[String]].map(_.toInt)

I get an error:

value toInt is not a member of Option[String]

However in the REPL this works:

val stringOpt: Option[String] = Some("10")
stringOpt.map(_.toInt)
res0: Option[Int] = Some(10)

Upvotes: 0

Views: 339

Answers (1)

Michael Zajac
Michael Zajac

Reputation: 55569

When debugging Reads, its helpful to look through the validation errors and exceptions that may occur from validate[T]. You haven't posted your case class, but deserializing fields that are formatting as xx.xx to Int doesn't make any sense. You'll get a NumberFormatException when calling "20.20".toInt. Double would probably make more sense, or BigDecimal.

This works:

case class Location(lat: Option[Double], lon: Option[Double])

implicit val reads: Reads[Location] = (
    (__ \ "lat").readNullable[String].map(_.map(_.toDouble)) and 
    (__ \ "lon").readNullable[String].map(_.map(_.toDouble))
)(Location.apply _)

Of course, if lat or lon is not a number, this will throw an exception.

The best way to handle this is to not format your numbers in JSON as strings at all. Then the play-json library will handle the errors for you. If this is not possible, you might consider using Try.

(__ \ "lat").readNullable[String].map(_.flatMap(x => Try(x.toDouble).map(Some(_)).getOrElse(None)))

Upvotes: 4

Related Questions