Skip to content
vic

jcouyang/dhall-generic

Dhall generic decoder for Scala

jcouyang/dhall-generic.json
{
"createdAt": "2020-10-12T12:42:53Z",
"defaultBranch": "master",
"description": "Dhall generic decoder for Scala",
"fullName": "jcouyang/dhall-generic",
"homepage": "",
"language": "Scala",
"name": "dhall-generic",
"pushedAt": "2024-12-03T19:06:50Z",
"stargazersCount": 46,
"topics": [
"dhall",
"scala"
],
"updatedAt": "2025-03-18T12:59:27Z",
"url": "https://github.com/jcouyang/dhall-generic"
}

Load Dhall config directly into Scala case class

Build and Test

libraryDependencies += "us.oyanglul" %% "dhall-generic" % "0.3.+"

There is a dhall config

let Shape = <Rectangle: {width: Double, height: Double}| Circle: {radius: Double}>
in Shape.Circle {radius = 1.2}

And you can parse them into dhall Expr

import org.dhallj.syntax._
val expr = """
let Shape = <Rectangle: {width: Double, height: Double}| Circle: {radius: Double}>
in Shape.Circle {radius = 1.2}
""".parseExpr

Supposed that you have some Scala case classes

enum Shape:
case Rectangle(width: Double, height: Double)
case Circle(radius: Double)

to load a dhall expr into Scala case classes, simply just

  1. derives Decoder
import us.oyanglul.dhall.generic.Decoder
enum Shape derives Decoder: // derive will generate decoder inline
case Rectangle(width: Double, height: Double)
case Circle(radius: Double)
  1. as[Shape]
import us.oyanglul.dhall.generic.as
expr.normalize.as[Shape]
// => Right(Circle(1.2)): Either[DecodingFailure, Shape]

Supposed that you have some Scala case classes

sealed trait Shape
object Shape {
case class Rectangle(width: Double, height: Double) extends Shape
case class Circle(radius: Double) extends Shape
}

to load a dhall expr into Scala case classes, simply just

import org.dhallj.codec.syntax._ // for `.as[A]` syntax
import org.dhallj.codec.Decoder._ // Decoders for primity types
import us.oyanglul.dhall.generic._ // generate generic decoders
expr.normalize.as[Shape]
// => Right(Circle(1.2)): Either[DecodingFailure, Shape]

It is very simple to replace HOCON application.conf with dhall.

To load src/main/resources/application.dhall from classpath your will need dhall-imports

import org.dhallj.syntax._
import org.dhallj.codec.syntax._
import us.oyanglul.dhall.generic._
import org.dhallj.imports.syntax._
BlazeClientBuilder[IO]!(ExecutionContext.global).resource.use { implicit c =>
IO.fromEither("classpath:/application.dhall".parseExpr)
.flatMap(_.resolveImports[IO])
.flatMap{expr => IO.fromEither(expr.normalize.as[Config])}
}

This project itself is an example of how to load dhall into build.sbt

It is recursively using [previous version]!(./project/build.sbt#L4) [to load]!(./project/loadDhall.scala) [./build.dhall]!(./build.dhall) to libraryDependencies

libraryDependencies ++= dhall.load.modules.map{case Module(o, n, v) => o %% n % v},

Create new decoder from existing decoder

i.e. UUID

given (using d: Decoder[String]): Decoder[UUID] = d.map(UUID.fromString)
implicit val decodeUUID: Decoder[UUID] = decodeString.map(UUID.from)