What gets generated

From a template at nomos/templates/com/example/models/user.json:

FileContents
com/example/models/user/User.scalaThe User case class + companion
com/example/models/NomosFormats.scalaEmbedded template and validator
…/<Enum>.scalaOne file per generated enum type

The case class & companion

case class User(id: String, username: String, email: String, age: Option[Int], roles: List[String])

object User {
  import com.example.models.NomosFormats._
  import dev.cjfravel.nomos.json._
  import dev.cjfravel.nomos.serialization.Codecs
  import dev.cjfravel.nomos.validation.ValidationError

  def decode(json: JsonValue): Either[String, User] = { ... }  // built from Codecs combinators
  def encode(obj: User): JsonValue = { ... }
  def fromJson(json: String): Either[String, User] = Json.parse(json).right.flatMap(decode)
  def toJson(obj: User): String = Json.write(encode(obj))
  def validate(json: String): Either[List[ValidationError], User] = { ... }
}

Discriminated unions instead generate a sealed trait, a case class per variant, and a companion whose fromJson dispatches on the discriminator value. variantMatch: prefix uses startsWith; variantMatch: regex uses full-string regular-expression matching in declaration order and requires explicit variantNames. Value-preserving modes retain the original discriminator for round trips. A fallbackVariant adds a catch-all case that preserves an unknown discriminator value and its raw payload, so unrecognized types round-trip rather than fail.

NomosFormats

A single NomosFormats per base package holds the reconstructed MultiTemplate and a MultiValidator built from it. Generated companions import it to validate without the original template file.

object NomosFormats {
  lazy val embeddedTemplate: MultiTemplate = ... // reconstructed from your template
  lazy val validator: MultiValidator = new MultiValidator(embeddedTemplate)
}

Enum files

An enumType enum generates a self-contained sealed trait with case objects and string (de)serialization over JsonValue — no extra setup.

sealed trait Priority
object Priority {
  case object Low extends Priority
  case object High extends Priority
  val values: List[Priority] = List(Low, High)
  def fromString(s: String): Option[Priority] = ...
  def asString(v: Priority): String = ...
  def decode(json: JsonValue): Either[String, Priority] = ...
  def encode(v: Priority): JsonValue = ...
}

A discriminator with discriminatorEnum renders its discriminator values as the same enum shape. When that union also sets fallbackVariant, the enum is open: it gains an Unknown(value) member and a total fromString, so an unrecognized discriminator value decodes to Unknown and encodes back to its original string.

Generation options

Set these at the top level of a template (project-wide in multi-file builds):

OptionDefaultEffect
listType"List"List or Array
mapType"Map"Map or java.util.Map for $map fields
dateTypejava.time.LocalDatee.g. java.util.Date
dateTimeTypejava.time.LocalDateTimeTarget for datetime
fromJsonStyle"either"Only "throwing" is special (throwing fromJson); any other value yields the Either-returning fromJson.
visibility—Access prefix on every generated top-level definition (e.g. private, private[models]).

Per-field, nullable: true yields a raw nullable type (value types boxed as java.lang.Integer/Long/Double/Boolean) and accepts explicit JSON null. nullable: "surfaceOnly" generates the same raw, null-defaulted surface while rejecting explicit JSON null; an omitted field still decodes to null. default is accepted only on non-optional string, numeric, boolean, and enum fields; date, datetime, reference, collection, and object defaults — and an int/long default outside the target range — are rejected at parse time. Defaults render per type: enum → EnumName.Value, long → 123L, a whole-number number/double → 1.0, and decimal → BigDecimal("…").

fromJson style

{ "fromJsonStyle": "throwing", "definitions": [ ... ] }

either (default)

def fromJson(json: String): Either[String, T]

throwing

def fromJson(json: String): T  // throws on failure

The style applies to plain definitions and to discriminated/variantNames unions alike; validate adapts to whichever style is generated.

File headers

Every generated file starts with a provenance header. Files tied to a template also point back to it.

// Code generated by Nomos. DO NOT EDIT.
// Source: com/example/models/user.json
package com.example.models.user

Next: Maven Plugin — wiring generation into your build.