hocon

Typed decoding

The untyped Config getters are enough for reading a value here and there, but for a real configuration it is nicer to map the whole document onto a case class…

The untyped Config getters are enough for reading a value here and there, but for a real configuration it is nicer to map the whole document onto a case class and read typed fields directly. config.as[A] does exactly that, deriving a decoder for A from its structure.

import io.github.edadma.hocon.*

case class Server(host: String, port: Int, debug: Boolean)

val config = Hocon.parse("""
  host  = localhost
  port  = 8080
  debug = true
""")

val server = config.as[Server]   // Server("localhost", 8080, true)

Each case-class field is read from the object key of the same name. No annotations, no companion boilerplate, and — because derivation is pure compile-time Mirror work — it runs identically on the JVM, Scala.js, and Scala Native.

Nesting

A field whose type is itself a case class decodes from the nested object:

case class App(name: String, server: Server)

val config = Hocon.parse("""
  name = demo
  server { host = localhost, port = 9000, debug = false }
""")

config.as[App]   // App("demo", Server("localhost", 9000, false))

To decode a value that is not at the root, point getAs at a path:

config.getAs[Server]("server")

Supported field types

Decoders for these are available out of the box, and compose — a List[Server] or Option[Map[String, Int]] works because the element decoders do:

TypeRead from
Stringa string (numbers and booleans coerce to their text)
Int, Long, Doublea numeric literal
Booleantrue/false (also yes/no/on/off)
FiniteDurationa HOCON duration (10s, 5 minutes); a bare number is milliseconds
Option[A]the value, or None when the key is absent or null
List[A]an array, each element decoded as A
Map[String, A]an object, each value decoded as A (key order preserved)
Configthe sub-object, left untyped
ConfigValuethe raw value node — an escape hatch
a nested case classthe nested object
import scala.concurrent.duration.*

case class Service(
  name:    String,
  tags:    List[String],
  timeout: FiniteDuration,
  note:    Option[String],     // absent or null → None
)

Hocon.parse("""
  name    = web
  tags    = [http, public]
  timeout = 30s
""").as[Service]
// Service("web", List("http", "public"), 30.seconds, None)

An Option field is the way to make a setting optional: a missing key decodes to None rather than failing. Every other field is required.

Errors

Decoding reuses the same exceptions as the untyped getters, and each one carries the dotted path of the field that failed — including through nested objects:

case class App(name: String, server: Server)

// server.port is not a number:
Hocon.parse("""name = demo, server { host = h, port = bad, debug = false }""").as[App]
// WrongTypeException: value at 'server.port' has type string, but a number was requested
  • A required field that is absent throws MissingPathException, with the missing field’s path.
  • A field whose value is the wrong shape throws WrongTypeException, with that field’s path.

See the Config reference for the as / getAs signatures.

Search

Esc
to navigate to open Esc to close