3

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

Ionuț G. Stan
  • 176,118
  • 18
  • 189
  • 202
Josh
  • 145
  • 1
  • 10

1 Answers1

4

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)
  }
Josh
  • 145
  • 1
  • 10
Ionuț G. Stan
  • 176,118
  • 18
  • 189
  • 202
  • Thanks Ionut. Much appreciated mate. I kept trying to map the emptyObject as jBool(false) and kept getting errors. This works absolutely perfectly :) and I've learnt how to do this too now :) – Josh Feb 18 '15 at 02:27
  • Just out of curiosity how do you make it such that if the Thing was none it would not bother with trying to encode that to a "name":null? For example BiggerThing would then return {"stuff":"stuff"}. would I do a similar thing as above for each element in the BiggerThing? – Josh Feb 18 '15 at 03:25
  • Thanks for that. That makes sense. I think Im beginning to understand this now - I just need to sort of implicitly intercept it before the actual encoding and then transform and then encode? In the event that there was more than one parameter that I wanted not to show up if it was empty would I then pick them each up individually (to get option[Json]) and then chain them along as an array? – Josh Feb 18 '15 at 21:55
  • @Josh can say for sure without knowing more details (open up a new question maybe), but sounds like you're on the right track. The basic idea is that if you have something with a non-standard, i.e. as defined by argonaut, then you have to create a custom instance of the `EncodeJson` type class. Any transformation from a type X to an actual JSON string goes like this: X -> Json -> String. Both arrows are handled by argonaut, but it's the first arrow that's overridable through `EncodeJson`. – Ionuț G. Stan Feb 19 '15 at 13:03
  • I forgot to mention that `Json` is Argonaut's tree representation of JSON strings. Also, I've missed a few words above. I hope you can infer them, because I can't edit the comment anymore. – Ionuț G. Stan Feb 19 '15 at 13:58
  • Thanks @Ionut. Appreciate the guidance. I think Im getting the hang of this now. I've almost got it working the way I want so I'll keep going and if I can't get it working I'll put up a new question. Have a great day mate – Josh Feb 20 '15 at 21:58