lift-json - how to deserialize empty string to None for Option[String] case class values?

53 views
Skip to first unread message

Jamie Swain

unread,
May 20, 2017, 9:04:01 PM5/20/17
to Lift
Hi all,

Does anyone know if there's an easy way to map empty string to None when deserializing parsed json to case class fields that are of Option[String]?

Here is a code example of what I mean:

import com.typesafe.scalalogging.StrictLogging
import net.liftweb.json._
import org.specs2.mutable.Specification

object Dummy {
  case class Person(first: String, last: String, job: Option[String])
}

case object StringNotEmptySerializer extends CustomSerializer[String](format => (
  {
    case JString(s) => s
    case JNull => null
  },
  {
    case "" => null
    case s: String => JString(s)
  }
))

class JsonNoneStringTest extends Specification with StrictLogging {
  implicit val formats = DefaultFormats + StringNotEmptySerializer

  "Dummy" >> {
    val p1 = Dummy.Person("lola", "", Some("dev"))
    val j1 = compactRender(Extraction.decompose(p1))
    p1.first must_== "lola"
    p1.last must_== ""
    p1.job must_== Some("dev")

    val d2 = """{ "first": "jamie", "last": "", "job": "" }"""
    val p2 = parse(d2).extract[Dummy.Person]
    p2.first must_== "jamie"
    p2.last must_== ""
    p2.job must_== None

    ok
  }
}



My apologizes in advance if I missed something obvious.

Jamie
 

Antonio Salazar Cardozo

unread,
May 21, 2017, 9:03:04 PM5/21/17
to Lift
At a glance I believe Strings go through an optimized path and bypass
formatter handling. To do this, you'll probably need to transform the JSON
before trying to extract it:

val parsed = parse(d2)
val nulledEmptyStrings =
  parsed transform {
    case JString("") => JNull
  }
val extracted = nulledEmptyStrings.extract[Dummy.Person]

This of course isn't field-targeted. You can target a particular field:

val parsed = parse(d2)
val nulledEmptyStrings =
  parsed transformField {
    case JField("job", JString("")) => JField("job", JNull)
  }
val extracted = nulledEmptyStrings.extract[Dummy.Person]

I don't think you can intercept this with a custom serializer, but I might be
wrong, in which case hopefully someone will step in to correct me :)
Thanks,
Antonio

Jamie Swain

unread,
May 26, 2017, 1:15:38 AM5/26/17
to Lift
Hi Antonio,

Thanks for the suggestion!  Unfortunately that will also strip the non-Option strings causing extraction to fail.

I got some help with Shapeless and found out you can do it this way:

import shapeless._
import shapeless.poly._

object ObjectUtil {
  object someEmptyToNone extends ->((os: Option[String]) => os flatMap (s => if (s == "") None else Some(s)))

  def transform[A](a: A)(implicit ev: Case1.Aux[EverywhereAux[someEmptyToNone.type], A, A]): A =
    ev(a)
Reply all
Reply to author
Forward
0 new messages