Argonaut cheat sheet

August 17, 2017

/***
libraryDependencies += "io.argonaut" %% "argonaut-scalaz"     % "6.2-RC2"
libraryDependencies += "org.http4s"  %% "http4s-argonaut"     % "0.15.8a"
libraryDependencies += "org.http4s"  %% "http4s-blaze-client" % "0.15.8a"
libraryDependencies += "org.http4s"  %% "http4s-blaze-server" % "0.15.8a"
libraryDependencies += "org.http4s"  %% "http4s-dsl"          % "0.15.8a"
*/

import java.net.URL

import argonaut._, argonaut.Argonaut._

import org.http4s.{ HttpService, Request, Method, Uri }
import org.http4s.dsl._, org.http4s.argonaut._
import org.http4s.client._, org.http4s.client.blaze._
import org.http4s.server.Server, org.http4s.server.blaze._

import scalaz._, scalaz.Scalaz._, scalaz.concurrent.Task

Simple JSON objects

case class Person(name: String, email: String, url: String)

val person: Person =
  Person( name = "James Earl Douglas"
        , email = "james@earldouglas.com"
        , url = "https://earldouglas.com"
        )

Encoding

implicit val encodePerson: EncodeJson[Person] =
  EncodeJson {
    (x: Person) =>
      ("name" := x.name) ->:
      ("email" := x.email) ->:
      ("url" := x.url) ->:
      jEmptyObject
  }

val encodedPerson: String = person.asJson.spaces2

println(s"\nencoded Person:\n$encodedPerson")

Decoding

implicit val decodePerson: DecodeJson[Person] =
  DecodeJson {
    (c: HCursor) =>
      for {
        name  <- (c --\ "name").as[String]
        email <- (c --\ "email").as[String] flatMap { email =>
                   DecodeResult {
                     if (email.matches("[^@]+@[^@]+")) Right(email)
                     else Left((s"invalid email $email", c.history))
                   }
                 }
        url   <- (c --\ "url").as[String]
      } yield Person( name = name
                    , email = email
                    , url = url
                    )
  }

val decodedPerson: Either[String, Person] =
  """|{
     |  "name": "James Earl Douglas",
     |  "email": "james@earldouglas.com",
     |  "url": "https://earldouglas.com"
     |}""".stripMargin.decodeEither[Person]

println(s"\ndecoded Person:\n$decodedPerson")

Tagged types

sealed trait EmailTag
type Email = String @@ EmailTag

val email: Email = Tag[String, EmailTag]("james@earldouglas.com")

Encoding

implicit val encodeEmail: EncodeJson[Email] =
  EncodeJson { x: Email => jString(Tag.unwrap(x)) }

val encodedEmail: String = email.asJson.spaces2

println(s"\nencoded Email:\n$encodedEmail")

Decoding

implicit val decodeEmail: DecodeJson[Email] =
  DecodeJson { _.as[String] map { Tag[String, EmailTag](_) } }

val decodedEmail: Either[String, Email] =
  "\"james@earldouglas.com\"".decodeEither[Email]

println(s"\ndecoded Email:\n$decodedEmail")

Stringly typed

This is probably not a good idea.

val url: URL = new URL("https://earldouglas.com")

Encoding

implicit val showURL: Show[URL] =
  new Show[URL] {
    override def shows(x: URL): String = x.toString
  }
implicit def encodeShow[A:Show]: EncodeJson[A] =
  EncodeJson { x: A => jString(x.shows) }

val encodedURL: String = url.asJson.spaces2

println(s"\nencoded URL:\n$encodedURL")

Decoding

trait Read[A] {
  def read(x: String): A
}

implicit class ReadOps(val x: String) {
  def read[A:Read]: A = implicitly[Read[A]].read(x)
}

implicit val readURL: Read[URL] =
  new Read[URL] {
    override def read(x: String): URL = new URL(x)
  }
implicit def decodeShow[A:Read]: DecodeJson[A] =
  DecodeJson { _.as[String] map { _.read[A] } }

val decodedURL: Either[String, URL] =
  "\"https://earldouglas.com\"".decodeEither[URL]

println(s"\ndecoded URL:\n$decodedURL")

http4s

Echo server

val echoService: HttpService =
  HttpService {
    case req @ POST -> Root / "echo" =>
      implicit val personDecoder = jsonOf[Person]
      implicit val personEncoder = jsonEncoderOf[Person]
      Ok(req.as[Person])
  }

val echoServiceBuilder: BlazeBuilder =
  BlazeBuilder.mountService(echoService, "/")

Echo client

val echoServer: Server = echoServiceBuilder.run

val httpClient: Client = PooledHttp1Client()

val echoPersonTask: Task[Person] =
  for {
    entity  <- jsonEncoderOf[Json].toEntity(person.asJson)
    request  = Request( method = Method.POST
                      , uri = Uri.uri("http://localhost:8080/echo")
                      , body = entity.body
                      )
    echoed  <- httpClient.expect(request)(jsonOf[Person])
  } yield echoed

val echoedPerson: Person = echoPersonTask.unsafePerformSync
println(s"\nechoed person:\n$echoedPerson")

echoServer.shutdownNow()

Demo

This post is literate Scala. Try it out with sbt and codedown:

$ curl -s https://earldouglas.com/posts/argonaut.md | codedown scala > script.scala
$ sbt -Dsbt.main.class=sbt.ScriptMain script.scala

encoded Person:
{
  "url" : "https://earldouglas.com",
  "email" : "james@earldouglas.com",
  "name" : "James Earl Douglas"
}

decoded Person:
Right(Person(James Earl Douglas,james@earldouglas.com,https://earldouglas.com))

encoded Email:
"james@earldouglas.com"

decoded Email:
Right(james@earldouglas.com)

encoded URL:
"https://earldouglas.com"

decoded URL:
Right(https://earldouglas.com)

echoed person:
Person(James Earl Douglas,james@earldouglas.com,https://earldouglas.com)