Search code examples
jsonscalaargonaut

Scala argonaut encode a jEmtpyObject to 'false' rather than 'null'


Im dealing with some code outside of my immediate control, where I need to encode an option[Thing] where the case is as per normal if Thing exists, however the None case must return 'false' rather than null. Can this be accomplished easily? I'm looking at the docs but not having much success.

My code looks like this:

   case class Thing(name: String)
   case class BiggerThing(stuff: String, thing: Option[Thing])

   implict val ThingEncodeJson: EncodeJson[Thing] =
     EncodeJson(t => ("name" := t.name ) ->: jEmptyObject)

and the equivalent for BiggerThing, and the json needs to look like:

  1. For a Some:

    "thing":{"name": "bob"} 
    
  2. For a None:

    "thing": false
    

but at present the None case gives:

"thing":null

How do I get it to return false? Could someone point me in the right direction please?

Cheers


Solution

  • You just need a custom CodecJson instance for Option[Thing]:

    object Example {
      import argonaut._, Argonaut._
    
      case class Thing(name: String)
      case class BiggerThing(stuff: String, thing: Option[Thing])
    
      implicit val encodeThingOption: CodecJson[Option[Thing]] =
        CodecJson(
          (thing: Option[Thing]) => thing.map(_.asJson).getOrElse(jFalse),
          json =>
            // Adopt the easy approach when parsing, that is, if there's no
            // `name` property, assume it was `false` and map it to a `None`.
            json.get[Thing]("name").map(Some(_)) ||| DecodeResult.ok(None)
        )
    
      implicit val encodeThing: CodecJson[Thing] =
        casecodec1(Thing.apply, Thing.unapply)("name")
    
      implicit val encodeBiggerThing: CodecJson[BiggerThing] =
        casecodec2(BiggerThing.apply, BiggerThing.unapply)("stuff", "thing")
    
      def main(args: Array[String]): Unit = {
        val a = BiggerThing("stuff", Some(Thing("name")))
        println(a.asJson.nospaces) // {"stuff":"stuff","thing":{"name":"name"}}
        val b = BiggerThing("stuff", None)
        println(b.asJson.nospaces) // {"stuff":"stuff","thing":false}
      }
    }
    

    How to encode a BiggerThing without a thing property when thing is None. You need a custom EncodeJson[BiggerThing] instance then:

    implicit val decodeBiggerThing: DecodeJson[BiggerThing] =
      jdecode2L(BiggerThing.apply)("stuff", "thing")
    
    implicit val encodeBiggerThing: EncodeJson[BiggerThing] =
      EncodeJson { biggerThing =>
        val thing = biggerThing.thing.map(t => Json("thing" := t))
        ("stuff" := biggerThing.stuff) ->: thing.getOrElse(jEmptyObject)
      }