Circe: decodeBoolean doesn't decode string values

Created on 28 Jan 2017  路  3Comments  路  Source: circe/circe

Unlike numeric decoders, decodeBoolean doesn't decode string values.

E.g.

  implicit final val decodeDouble: Decoder[Double] = new DecoderWithFailure[Double]("Double") {
    final def apply(c: HCursor): Result[Double] = c.value match {
      case JNumber(number) => Right(number.toDouble)
      //!!! trying to convert string to double !!!
      case JString(string) => JsonNumber.fromString(string).map(_.toDouble) match {
        case Some(v) => Right(v)
        case None => fail(c)
      }
      case other if other.isNull => Right(Double.NaN)
      case _ => fail(c)
    }
  }
  implicit final val decodeBoolean: Decoder[Boolean] = new Decoder[Boolean] {
    final def apply(c: HCursor): Result[Boolean] = c.value match {
      case JBoolean(b) => Right(b)
      // ----> nope
      case _ => Left(DecodingFailure("Boolean", c.history))
    }
  }

(from https://github.com/circe/circe/blob/6101fb6f27ba6b3be57df95584a7beba31c89c41/modules/core/shared/src/main/scala/io/circe/Decoder.scala)

Is there a reason for that? It makes sense to me to attempt conversion also for booleans.

Most helpful comment

@travisbrown we also just ran into this when migrating from argonaut as it does support the string to boolean. We were parsing responses from the authy api but one of their operations uses a string rather than a boolean json type whereas the rest of them all used boolean. We started getting parse errors. They were returning "true" rather than true. I would think it'd be easy to support anything that the Scala toBoolean operation on string supports. I didn't think of overriding the boolean implicit decoder. I did a much different solution and created two different case classes and a recoverErrorsWith.

All 3 comments

The argument is that there's a reasonably common convention among JSON libraries that large numbers may be encoded as strings. I'm not aware of the same thing for booleans, and it seems a little odd to me, since there's not a similar difficulty in representing booleans in a standard type. If you need this behavior it's not too hard to write your own:

import io.circe.Decoder

implicit val myBooleanDecoder: Decoder[Boolean] = Decoder.decodeBoolean.or(
  Decoder.decodeString.emap {
    case "true" => Right(true)
    case "false" => Right(false)
    case _ => Left("Boolean")
  }
)

And then:

scala> io.circe.jawn.decode[Boolean](""""true"""")
res0: Either[io.circe.Error,Boolean] = Right(true)

Does that seem reasonable?

@travisbrown thanks for your answer, I actually wrote my own decoder as you suggest.

I see your point, it's good to adhere to common conventions. Though, it feels odd to have different behaviors for the very same task - I didn't expect this.

It looks to me that conversion to strings is ad-hoc for numbers and shouldn't be generalized. I guess I can close this issue.

@travisbrown we also just ran into this when migrating from argonaut as it does support the string to boolean. We were parsing responses from the authy api but one of their operations uses a string rather than a boolean json type whereas the rest of them all used boolean. We started getting parse errors. They were returning "true" rather than true. I would think it'd be easy to support anything that the Scala toBoolean operation on string supports. I didn't think of overriding the boolean implicit decoder. I did a much different solution and created two different case classes and a recoverErrorsWith.

Was this page helpful?
0 / 5 - 0 ratings