Skip to content

ramytanios/json-schema-lib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

67 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

json-schema-lib

A Scala 3 library that leverages type classes and macros to derive JSON schemas from case classes at compile time.

⚠️ Project Status

This project is purely experimental and was created for me to experiment with Claude Code and explore vibe-based coding workflows. It's a learning exercise and playground for testing AI-assisted development patterns. Use at your own risk!

Installation

Add to your build.sbt:

libraryDependencies += "io.github.ramytanios" %% "json-schema-lib" % "<version>"
libraryDependencies += "io.github.ramytanios" %% "json-schema-lib-excel" % "<version>"

Features

  • derives keyword support - Idiomatic Scala 3 derivation at the definition site
  • Compile-time schema generation - Zero runtime overhead with macro-based derivation
  • Primitive type support - String, Int, Long, Float, Double, Boolean
  • java.time support - LocalDate β†’ date, LocalTime/OffsetTime β†’ time, Instant/LocalDateTime/OffsetDateTime/ZonedDateTime β†’ date-time, Duration β†’ duration
  • Scala duration support - scala.concurrent.duration.Duration and FiniteDuration map to { "type": "string" } (no standard JSON Schema format exists for Scala durations)
  • java.util.UUID support - UUID maps to { "type": "string", "format": "uuid" }
  • Enum support - Scala 3 enums automatically map to JSON Schema enums
  • Nested case class support - Case class fields are recursively inlined into the schema
  • Seq support - Mutable and immutable sequences
  • Map support - Map[String, V] maps to { "type": "object", "additionalProperties": ... }
  • Option support - Optional fields automatically excluded from required list
  • Circe integration - Built-in JSON encoding for schemas
  • $schema declaration - Annotate the root schema with a JSON Schema draft URI via withSchemaVersion

Example

import jsonschema.*

enum Role:
  case Admin, User, Guest

case class Address(street: String, city: String)

// Idiomatic `derives` syntax (recommended)
@Title("User profile")
case class Profile(
  @MinLength(3) @MaxLength(50) username: String,
  @MinimumInt(18) @MaximumInt(120) age: Int,
  active: Boolean,
  role: Role,
  address: Address,
  @MinItems(1) tags: List[String],
  bio: Option[String]
) derives JsonSchema

// Equivalent explicit form (also supported)
// object Profile:
//   given JsonSchema[Profile] = DeriveJsonSchema.derived

val json = JsonSchema[Profile].schema
  .withSchemaVersion(JsonSchemaVersion.Draft202012)
  .toJson

Generated JSON Schema:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "title": "User profile",
  "properties": {
    "username": { "type": "string", "minLength": 3, "maxLength": 50 },
    "age": { "type": "integer", "minimum": 18, "maximum": 120 },
    "active": { "type": "boolean" },
    "role": { "type": "string", "enum": ["Admin", "User", "Guest"] },
    "address": {
      "type": "object",
      "properties": {
        "street": { "type": "string" },
        "city": { "type": "string" }
      },
      "required": ["street", "city"]
    },
    "tags": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
    "bio": { "type": "string" }
  },
  "required": ["username", "age", "active", "role", "address", "tags"]
}

Nested case classes are inlined (no $ref) and work to arbitrary depth.

Limitation: Mutually recursive case classes (e.g. A contains B, B contains A) will cause a compile-time stack overflow. Workaround: provide an explicit given JsonSchema[B] before deriving A.

Excel

An optional module (excel) exposes Scala functions as Excel custom functions via an HTTP server.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Excel                                                                      β”‚
β”‚                                                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚   Functions runtime  β”‚            β”‚           Task pane              β”‚   β”‚
β”‚  β”‚   (functions.html)   β”‚            β”‚         (taskpane.html)          β”‚   β”‚
β”‚  β”‚                      β”‚            β”‚                                  β”‚   β”‚
β”‚  β”‚  on startup          β”‚            β”‚  on load / "Reload Functions"    β”‚   β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚            β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚   β”‚
β”‚  β”‚  β”‚loadAndRegister │──┼────────────┼─▢│  GET /functions.json       β”‚  β”‚   β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚            β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚   β”‚
β”‚  β”‚                      β”‚            β”‚       β”‚ re-render list           β”‚   β”‚
β”‚  β”‚  poll every 2 s      β”‚            β”‚       β–Ό                          β”‚   β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚  signal    β”‚  OfficeRuntime.storage           β”‚   β”‚
β”‚  β”‚  β”‚OfficeRuntime   │◀─┼────────────┼─  .setItem("cf-reload-signal")   β”‚   β”‚
β”‚  β”‚  β”‚.storage.getItemβ”‚  β”‚            β”‚                                  β”‚   β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚  β”‚          β”‚ detected  β”‚                                                   β”‚
β”‚  β”‚          β–Ό           β”‚                                                   β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚                                                   β”‚
β”‚  β”‚  β”‚loadAndRegister β”‚  β”‚  (all IDs re-associated on every reload)          β”‚
β”‚  β”‚  β”‚  (full reload) β”‚  β”‚                                                   β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚                                                   β”‚
β”‚  β”‚          β”‚           β”‚                                                   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              β”‚ POST /invoke  {functionId, params}
              β–Ό
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚   Scala server  β”‚
     β”‚  (http4s/ember) β”‚
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  • /functions.json β€” served at startup from the in-memory function list; always reflects the running server's functions.
  • /functions.js β€” fetches /functions.json at runtime and calls CustomFunctions.associate() dynamically; polls OfficeRuntime.storage every 2 s for a reload signal.
  • Reload without add-in restart β€” clicking "Reload Functions" signals the runtime to re-associate all functions from scratch and refreshes the taskpane list. Formula-bar autocomplete for brand-new functions still requires a full add-in reload (Excel limitation).

About

Compile-time JSON schemas πŸ‘€

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors