jscheam - A Simple JSON Schema Library

Package Version Hex Docs

A Gleam library for generating JSON Schema documents (Draft 7 compliant). I looked for a simple way to create JSON schemas in Gleam but every things I tried where either outdated or incomplete. This library was born out of that need. This library provides a fluent API for building JSON schemas programmatically, making it easy to create validation schemas for APIs, configuration files, and data structures.

Table of Contents

Installation

gleam add jscheam

Usage

Basic Types

import jscheam/schema
import gleam/json

// Create simple types
let name_schema = schema.string()
let age_schema = schema.integer()
let active_schema = schema.boolean()
let score_schema = schema.float()

// Generate JSON Schema
let json_schema = schema.to_json(name_schema) |> json.to_string()
// Result: {"type":"string"}

Object Schemas

import jscheam/schema
import gleam/json

// Create an object with default additional properties behavior (allows any)
let user_schema = schema.object([
  schema.prop("name", schema.string()),
  schema.prop("age", schema.integer()),
  schema.prop("email", schema.string())
])

let json_schema = schema.to_json(user_schema) |> json.to_string()
// Result: {
//   "type": "object",
//   "properties": {
//     "name": {"type": "string"},
//     "age": {"type": "number"},
//     "email": {"type": "string"}
//   },
//   "required": ["name", "age", "email"]
// Note: additionalProperties is omitted (defaults to true as per JSON Schema Draft 7)
// }

// Create an object with strict additional properties
let strict_user_schema = schema.object([schema.prop("name", schema.string())])
  |> schema.disallow_additional_props()
  |> schema.to_json(strict_user_schema) |> json.to_string()
// Result: {..., "additionalProperties": false}

// Create an object with constrained additional properties
let constrained_user_schema = schema.object([schema.prop("id", schema.string())])
  |> schema.constrain_additional_props(schema.string())
  |> schema.to_json(constrained_user_schema) |> json.to_string()
// Result: {..., "additionalProperties": {"type": "string"}}

// Explicitly allow additional properties
let explicit_allow_schema = schema.object([
  schema.prop("name", schema.string())
])
|> schema.allow_additional_props()
|> schema.to_json(explicit_allow_schema) |> json.to_string()
// Result: {..., "additionalProperties": true}

Optional Properties and Descriptions

import jscheam/schema
import gleam/json

let user_schema = schema.object([
  schema.prop("name", schema.string()) |> schema.description("User's full name"),
  schema.prop("age", schema.integer()) |> schema.optional(),
  schema.prop("email", schema.string())
    |> schema.description("User's email address")
    |> schema.optional()
])

let json_schema = schema.to_json(user_schema) |> json.to_string()
// Result: {
//   "type": "object",
//   "properties": {
//     "name": {"type": "string", "description": "User's full name"},
//     "age": {"type": "number"},
//     "email": {"type": "string", "description": "User's email address"}
//   },
//   "required": ["name"]
// }

Arrays

import jscheam/schema
import gleam/json

// Array of strings
let tags_schema = schema.array(schema.string())

// Array of objects
let users_schema = schema.array(
  schema.object([
    schema.prop("name", schema.string()),
    schema.prop("age", schema.integer()) |> schema.optional()
  ])
)

let json_schema = schema.to_json(tags_schema) |> json.to_string()
// Result: {
//   "type": "array",
//   "items": {"type": "string"}
// }

Union Types

Union types allow a property to accept multiple types. Some API require all fields to be “required”“ (no optional fields), so the only way to add nullability is to use union types.

import jscheam/schema
import gleam/json

// Simple union: string or null
let nullable_string_schema = schema.union([schema.string(), schema.null()])

// Used in an object
let user_schema = schema.object([
  schema.prop("name", schema.string()),
  schema.prop("nickname", schema.union([schema.string(), schema.null()]))
    |> schema.description("Optional nickname, can be string or null")
])

let json_schema = schema.to_json(user_schema) |> json.to_string()
// Result: {
//   "type": "object",
//   "properties": {
//     "name": {"type": "string"},
//     "nickname": {
//       "type": ["string", "null"],
//       "description": "Optional nickname, can be string or null"
//     }
//   },
//   "required": ["name", "nickname"]
// }

Constraints

Constraints allow you to add validation rules to your schema properties. jscheam supports enum and pattern constraints with more to come in the future.

Enum Constraints

Enum constraints restrict values to a fixed set of allowed values. It uses the json module to define the allowed values as enum values can be any valid JSON type (string, number, boolean, null, …)

import jscheam/schema
import gleam/json

// String enum
let color_schema = schema.object([
  schema.prop("color", schema.string())
  |> schema.enum([
    json.string("red"),
    json.string("green"),
    json.string("blue")
  ])
  |> schema.description("Primary colors only")
])

// Mixed type enum with union
let status_schema = schema.object([
  schema.prop("status", schema.union([schema.string(), schema.null(), schema.integer()]))
  |> schema.enum([
    json.string("active"),
    json.string("inactive"),
    json.null(),
    json.int(42)
  ])
  |> schema.description("Status with mixed types")
])

let json_schema = schema.to_json(color_schema) |> json.to_string()
// Result: {
//   "type": "object",
//   "properties": {
//     "color": {
//       "type": "string",
//       "enum": ["red", "green", "blue"],
//       "description": "Primary colors only"
//     }
//   },
//   "required": ["color"]
// }

Pattern Constraints

Pattern constraints use regular expressions to validate string values:

import jscheam
import gleam/json

// Email validation
let user_schema = schema.object([
  schema.prop("email", schema.string())
  |> schema.pattern("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
  |> schema.description("Valid email address"),

  schema.prop("phone", schema.string())
  |> schema.pattern("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$")
  |> schema.description("Phone number in US format")
])

let json_schema = schema.to_json(user_schema) |> json.to_string()
// Result: {
//   "type": "object",
//   "properties": {
//     "email": {
//       "type": "string",
//       "pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
//       "description": "Valid email address"
//     },
//     "phone": {
//       "type": "string",
//       "pattern": "^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$",
//       "description": "Phone number in US format"
//     }
//   },
//   "required": ["email", "phone"]
// }

Nested Objects

import jscheam
import gleam/json

let profile_schema = schema.object([
  schema.prop("user", schema.object([
    schema.prop("name", schema.string()),
    schema.prop("age", schema.integer()) |> schema.optional()
  ])),
  schema.prop("preferences", schema.object([
    schema.prop("theme", schema.string()) |> schema.description("UI theme preference"),
    schema.prop("notifications", schema.boolean()) |> schema.optional()
  ])),
  schema.prop("tags", schema.array(schema.string())) |> schema.description("User tags")
])

let json_schema = schema.to_json(profile_schema) |> json.to_string()
// Result: {
//   "type": "object",
//   "properties": {
//     "user": {
//       "type": "object",
//       "properties": {
//         "name": {"type": "string"},
//         "age": {"type": "number"}
//       },
//       "required": ["name"]
//     },
//     "preferences": {
//       "type": "object",
//       "properties": {
//         "theme": {"type": "string", "description": "UI theme preference"},
//         "notifications": {"type": "boolean"}
//       },
//       "required": ["theme"]
//     },
//     "tags": {
//       "type": "array",
//       "items": {"type": "string"},
//       "description": "User tags"
//     }
//   },
//   "required": ["user", "preferences"]
// }

TODO: Future Features

Restrictions

Development

gleam run   # Run the project
gleam test  # Run the tests

Contributing

Contributions are welcome! Please feel free to submit issues and enhancement requests.

License

This project is licensed under the MIT License.

Search Document