4

Using circe or argonaut, how can I write a Json => A (note - Json may not be the name of the type) where A is given by the SSN class:

  // A USA Social Security Number has exactly 8 digits.
  case class SSN(value: Sized[List[Nat], _8])

?

Pseudocode:

// assuming this function is named f

f(JsArray(JsNumber(1))) would fail to become an A since its size is 1, whereas

f(JsArray(JsNumber(1), ..., JsNumber(8))) === SSN(SizedList(1,...,8))

Kevin Meredith
  • 41,036
  • 63
  • 209
  • 384

1 Answers1

5

circe doesn't (currently) provide instances for Sized, but it probably should. In any case you can write your own pretty straightforwardly:

import cats.data.Xor
import io.circe.{ Decoder, DecodingFailure }
import shapeless.{ Nat, Sized }
import shapeless.ops.nat.ToInt
import shapeless.syntax.sized._

implicit def decodeSized[L <: Nat, A](implicit
  dl: Decoder[List[A]],
  ti: ToInt[L]
): Decoder[Sized[List[A], L]] = Decoder.instance { c =>
  dl(c).flatMap(as =>
    Xor.fromOption(as.sized[L], DecodingFailure(s"Sized[List[A], _${ti()}]", c.history))
  )
}

I've restricted this to List representations, but you could make it more generic if you wanted.

Now you can write your SSN instance like this (note that I'm using Int instead of Nat for the individual numbers, since once you've got something statically typed as a Nat it's not worth much):

case class SSN(value: Sized[List[Int], Nat._8])

implicit val decodeSSN: Decoder[SSN] = Decoder[Sized[List[Int], Nat._8]].map(SSN(_))

And then:

scala> import io.circe.jawn.decode
import io.circe.jawn.decode

scala> decode[SSN]("[1, 2, 3, 4, 5, 6, 7, 8]")
res0: cats.data.Xor[io.circe.Error,SSN] = Right(SSN(List(1, 2, 3, 4, 5, 6, 7, 8)))

scala> decode[SSN]("[1, 2, 3, 4, 5, 6, 7]")
res1: cats.data.Xor[io.circe.Error,SSN] = Left(DecodingFailure(Sized[List[A], _8], List()))

If you really want a Json => SSN you could do this:

val f: Json => SSN = Decoder[SSN].decodeJson(_).valueOr(throw _)

But that's not really idiomatic use of circe.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • Thanks, Travis. For those interested in this question, you might find http://stackoverflow.com/questions/39183907/class-with-list-of-nats-between-0-and-2 interesting too. I'm trying to refine the usage of `List[Int]` to `List[A]` where `A` consists of `Nat'`s less than or equal to 2. – Kevin Meredith Aug 27 '16 at 17:37