// Define a very simple JSON AST sealed trait Json final case class JsObject(get: Map[String, Json]) extends Json final case class JsString(get: String) extends Json final case class JsNumber(get: Double) extends Json case object JsNull extends Json ////////////////// Step 1 // The "serialize to JSON" behaviour is encoded in this trait trait JsonWriter[A] { def write(value: A): Json } final case class Person(name: String, email: String) ////////////////// Step 2 // any definitions marked implicit in Scala MUST BE placed inside an object or trait object JsonWriterInstances { implicit val stringWriter: JsonWriter[String] = (value: String) => JsString(value) implicit val personWriter: JsonWriter[Person] = (value: Person) => JsObject( Map("name" -> JsString(value.name), "email" -> JsString(value.email)) ) // etc... } ////////////////// Step 3 - 1 object Json { def toJson[A](value: A)(implicit w: JsonWriter[A]): Json = w.write(value) } ////////////////// Step 3 - 2 object JsonSyntax { implicit class JsonWriterOps[A](value: A) { def toJson(implicit w: JsonWriter[A]): Json = w.write(value) } } ////////////////// Usage object Main extends App { import JsonWriterInstances._ Json.toJson(Person("Dave", "dave@example.com")) import JsonWriterInstances._ import JsonSyntax._ Person("Dave", "dave@example.com").toJson } ////////////////// Cats Show Usage final case class Cat(name: String, age: Int, color: String) object CatShowMain extends App { implicit val catShow = Show.show[Cat] { cat => import cats.instances.int._ // for Show import cats.instances.string._ // for Show val name = cat.name.show val age = cat.age.show val color = cat.color.show s"$name is a $age year-old $color cat." } println(Cat("Garfield", 38, "ginger and black").show) println(Cat("Garfield", 38, "ginger and black").show) } ////////////////// Cats Show Source code // 1 trait Show[T] extends Show.ContravariantShow[T] trait ContravariantShow[-T] extends Serializable { def show(t: T): String } // 2 (default, and need to define the customized class type instance) trait StringInstances extends cats.kernel.instances.StringInstances { implicit val catsStdShowForString: Show[String] = Show.fromToString[String] } // 3 trait Ops[A] { def typeClassInstance: Show[A] def self: A def show: String = typeClassInstance.show(self) } trait ToShowOps { implicit def toShow[A](target: A)(implicit tc: Show[A]): Ops[A] = new Ops[A] { val self = target val typeClassInstance = tc } } trait ShowSyntax extends Show.ToShowOps { implicit final def showInterpolator(sc: StringContext): Show.ShowInterpolator = Show.ShowInterpolator(sc) }