Skip to content

Instantly share code, notes, and snippets.

@heyrutvik
Last active December 3, 2020 13:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save heyrutvik/d5e1162fb8888b4fffdd9110c35d2236 to your computer and use it in GitHub Desktop.
Save heyrutvik/d5e1162fb8888b4fffdd9110c35d2236 to your computer and use it in GitHub Desktop.
package example
import shapeless._
import shapeless.labelled.FieldType
object LabelledDerivation extends App {
import JsonObjectEncoder._
println {
JsonEncoder[IceCream].encode(IceCream("Sunday", 25))
// JsonObject(List((name,JsonString(Sunday)), (price,JsonNumber(25.0))))
}
println {
JsonEncoder[Employee].encode(Employee("A", 31, manager = true))
// JsonObject(List((name,JsonString(A)), (age,JsonNumber(31.0)), (manager,JsonBoolean(true))))
}
println {
List(
Rectangle(2.0, 1.0),
Circle(9.0)
).map(JsonEncoder[Shape].encode).mkString("\n")
// JsonObject(List((Rectangle,JsonObject(List((width,JsonNumber(2.0)), (height,JsonNumber(1.0)))))))
// JsonObject(List((Circle,JsonObject(List((radius,JsonNumber(9.0)))))))
}
}
case class Employee(name: String, age: Int, manager: Boolean)
case class IceCream(name: String, price: Int)
sealed trait Shape
final case class Rectangle(width: Double, height: Double) extends Shape
final case class Circle(radius: Double) extends Shape
sealed trait JsonValue
case class JsonObject(fields: List[(String, JsonValue)]) extends JsonValue
case class JsonArray(items: List[JsonValue]) extends JsonValue
case class JsonString(value: String) extends JsonValue
case class JsonNumber(value: Double) extends JsonValue
case class JsonBoolean(value: Boolean) extends JsonValue
case object JsonNull extends JsonValue
trait JsonEncoder[A] {
def encode(value: A): JsonValue
}
object JsonEncoder {
def apply[A](implicit enc: JsonEncoder[A]): JsonEncoder[A] = enc
def createEncoder[A](func: A => JsonValue): JsonEncoder[A] = new JsonEncoder[A] {
def encode(value: A): JsonValue = func(value)
}
implicit val stringEncoder: JsonEncoder[String] = createEncoder(string => JsonString(string))
implicit val intEncoder: JsonEncoder[Int] = createEncoder(int => JsonNumber(int.toDouble))
implicit val doubleEncoder: JsonEncoder[Double] = createEncoder(double => JsonNumber(double))
implicit val booleanEncoder: JsonEncoder[Boolean] = createEncoder(boolean => JsonBoolean(boolean))
implicit def listEncoder[A](implicit enc: JsonEncoder[A]): JsonEncoder[List[A]] =
createEncoder(list => JsonArray(list.map(enc.encode)))
implicit def optionEncoder[A](implicit enc: JsonEncoder[A]): JsonEncoder[Option[A]] =
createEncoder(opt => opt.map(enc.encode).getOrElse(JsonNull))
}
trait JsonObjectEncoder[A] extends JsonEncoder[A] {
def encode(value: A): JsonObject
}
object JsonObjectEncoder {
def createObjectEncoder[A](func: A => JsonObject): JsonObjectEncoder[A] = new JsonObjectEncoder[A] {
def encode(value: A): JsonObject = func(value)
}
implicit val hnilEncoder: JsonObjectEncoder[HNil] =
createObjectEncoder(_ => JsonObject(Nil))
implicit def hlistEncoder[K <: Symbol, H, T <: HList](implicit
witness: Witness.Aux[K],
hEncoder: JsonEncoder[H],
tEncoder: JsonObjectEncoder[T]
): JsonObjectEncoder[FieldType[K, H] :: T] = {
val fieldName = witness.value.name
createObjectEncoder { hlist =>
val head = hEncoder.encode(hlist.head)
val tail = tEncoder.encode(hlist.tail)
JsonObject((fieldName, head) :: tail.fields)
}
}
implicit val cnilEncoder: JsonObjectEncoder[CNil] =
createObjectEncoder(_ => throw new Exception("Inconceivable!"))
implicit def coproductEncoder[K <: Symbol, H, T <: Coproduct](implicit
witness: Witness.Aux[K],
hEncoder: JsonEncoder[H],
tEncoder: JsonObjectEncoder[T]
): JsonObjectEncoder[FieldType[K, H] :+: T] = {
val typeName = witness.value.name
createObjectEncoder {
case Inl(h) => JsonObject(List(typeName -> hEncoder.encode(h)))
case Inr(t) => tEncoder.encode(t)
}
}
implicit def genericObjectEncoder[A, H](implicit
gen: LabelledGeneric.Aux[A, H],
hEncoder: JsonObjectEncoder[H]
): JsonEncoder[A] =
createObjectEncoder { value =>
hEncoder.encode(gen.to(value))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment