The Swift JSON Schema library provides a type-safe way to generate and validate JSON schema documents directly in Swift, with deterministic, byte-stable JSON output at every layer — schema emission, validation results, and the underlying JSON value type.
The package ships four libraries you can import independently:
| Library | Use it when you need |
|---|---|
OrderedJSON |
An order-preserving JSON parser, serializer, and value type. Standalone — use it without the validator if you just want stable JSON I/O across processes. |
JSONSchema |
The JSON Schema 2020-12 validator. Re-exports OrderedJSON so types like JSONValue are available unchanged from import JSONSchema. |
JSONSchemaBuilder |
A result-builder DSL plus the @Schemable macro for generating schemas from Swift types. Builds on JSONSchema. |
JSONSchemaConversion |
Type bridges (URL, UUID, Date) for use with JSONSchemaBuilder. |
- Schema Generation
- Macros
- Validation
- Parsing
- Why not Foundation's JSON?
- Example Projects
- Documentation
- Installation
- Next Steps
- License
Use the power of Swift result builders to generate JSON schema documents.
@JSONSchemaBuilder var personSchema: some JSONSchemaComponent {
JSONObject {
JSONProperty(key: "firstName") {
JSONString()
.description("The person's first name.")
}
JSONProperty(key: "lastName") {
JSONString()
.description("The person's last name.")
}
JSONProperty(key: "age") {
JSONInteger()
.description("Age in years which must be equal to or greater than zero.")
.minimum(0)
}
}
.title("Person")
}Generated JSON Schema
Schema returned from personSchema.definition() conforms to Codable.
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let schemaData = try! encoder.encode(personSchema.definition())
let string = String(data: schemaData, encoding: .utf8)!
print(string){
"title": "Person",
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "The person's first name."
},
"lastName": {
"type": "string",
"description": "The person's last name."
},
"age": {
"description": "Age in years which must be equal to or greater than zero.",
"type": "integer",
"minimum": 0
}
}
}Use the @Schemable macro from JSONSchemaBuilder to automatically generate the result builders.
@Schemable
@ObjectOptions(.additionalProperties { false })
struct Person {
let firstName: String
let lastName: String?
@NumberOptions(.minimum(0), .maximum(120))
let age: Int
/// A short bio or summary about the person, shown on their public profile.
@StringOptions(.maxLength(500))
let bio: String?
}Expanded Macro
struct Person {
let firstName: String
let lastName: String?
let age: Int
/// A short bio or summary about the person, shown on their public profile.
let bio: String?
// Auto-generated schema ↴
static var schema: some JSONSchemaComponent<Person> {
JSONSchema(Person.init) {
JSONObject {
JSONProperty(key: "firstName") {
JSONString()
}
.required()
JSONProperty(key: "lastName") {
JSONString()
}
JSONProperty(key: "age") {
JSONInteger()
.minimum(0)
.maximum(120)
}
.required()
JSONProperty(key: "bio") {
JSONString()
.maxLength(500)
.description(#"""
A short bio or summary about the person, shown on their public profile.
"""#)
}
}
.additionalProperties(false)
}
}
}
extension Person: Schemable {}@Schemable can be applied to enums.
@Schemable
enum Status {
case active
case inactive
}Expanded Macro
enum Status {
case active
case inactive
static var schema: some JSONSchemaComponent<Status> {
JSONString()
.enumValues {
"active"
"inactive"
}
.compactMap {
switch $0 {
case "active":
return Self.active
case "inactive":
return Self.inactive
default:
return nil
}
}
}
}
extension Status: Schemable {}Enums with associated values are also supported using anyOf schema composition. See the JSONSchemaBuilder documentation for more information.
For details on modeling dependencies and other conditional constructs, check the Conditional Validation guide.
Using the Schema type, you can validate JSON data against a schema.
let schemaString = """
{
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1
}
}
}
"""
let schema1 = try Schema(instance: schemaString)
let result = try schema1.validate(instance: #"{"name": "Alice"}"#)Alternatively, you can use the JSONSchemaBuilder builders (or macros) to create a schema and validate instances.
let nameBuilder = JSONObject {
JSONProperty(key: "name") {
JSONString()
.minLength(1)
}
}
let schema = nameBuilder.defintion()
let instance1: JSONValue = ["name": "Alice"]
let instance2: JSONValue = ["name": ""]
let result1 = schema.validate(instance1)
dump(result1, name: "Instance 1 Validation Result")
let result2 = schema.validate(instance2)
dump(result2, name: "Instance 2 Validation Result")Instance 1 Validation Result
▿ Instance 1 Validation Result: JSONSchema.ValidationResult
- isValid: true
▿ keywordLocation: #
- path: 0 elements
▿ instanceLocation: #
- path: 0 elements
- errors: nil
▿ annotations: Optional([JSONSchema.Annotation<JSONSchema.Keywords.Properties>(keyword: "properties", instanceLocation: #, schemaLocation: #/properties, absoluteSchemaLocation: nil, value: Set(["name"]))])
▿ some: 1 element
▿ JSONSchema.Annotation<JSONSchema.Keywords.Properties>
- keyword: "properties"
▿ instanceLocation: #
- path: 0 elements
▿ schemaLocation: #/properties
▿ path: 1 element
▿ JSONSchema.JSONPointer.Component.key
- key: "properties"
- absoluteSchemaLocation: nil
▿ value: 1 member
- "name"
Instance 2 Validation Result
- isValid: false
▿ keywordLocation: #
- path: 0 elements
▿ instanceLocation: #
- path: 0 elements
▿ errors: Optional([JSONSchema.ValidationError(keyword: "properties", message: "Validation failed for keyword \'properties\'", keywordLocation: #/properties, instanceLocation: #, errors: Optional([JSONSchema.ValidationError(keyword: "minLength", message: "The string length is less than the specified \'minLength\'.", keywordLocation: #/properties/name/minLength, instanceLocation: #/name, errors: nil)]))])
▿ some: 1 element
▿ JSONSchema.ValidationError
- keyword: "properties"
- message: "Validation failed for keyword \'properties\'"
▿ keywordLocation: #/properties
▿ path: 1 element
▿ JSONSchema.JSONPointer.Component.key
- key: "properties"
▿ instanceLocation: #
- path: 0 elements
▿ errors: Optional([JSONSchema.ValidationError(keyword: "minLength", message: "The string length is less than the specified \'minLength\'.", keywordLocation: #/properties/name/minLength, instanceLocation: #/name, errors: nil)])
▿ some: 1 element
▿ JSONSchema.ValidationError
- keyword: "minLength"
- message: "The string length is less than the specified \'minLength\'."
▿ keywordLocation: #/properties/name/minLength
▿ path: 3 elements
▿ JSONSchema.JSONPointer.Component.key
- key: "properties"
▿ JSONSchema.JSONPointer.Component.key
- key: "name"
▿ JSONSchema.JSONPointer.Component.key
- key: "minLength"
▿ instanceLocation: #/name
▿ path: 1 element
▿ JSONSchema.JSONPointer.Component.key
- key: "name"
- errors: nil
- annotations: nil
When using builders or macros, you can also parse JSON instances into Swift types.
@Schemable
enum TemperatureUnit {
case celsius
case fahrenheit
}
@Schemable
struct Weather {
let temperature: Double
let unit: TemperatureUnit
let conditions: String
}
let data = """
{
"temperature": 20,
"unit": "celsius",
"conditions": "Sunny"
}
"""
let weather: Parsed<Weather, ParseIssue> = Weather.schema.parse(instance: data)Optionally, combine parsing and validation in a single step.
let weather: Weather = try Weather.schema.parseAndValidate(instance: data)Shoutout to the swift-parsing library and the Point-Free Parsing series for the inspiration behind the parsing API and implementation.
You can use the validator with JSONDecoder / JSONEncoder if you want — JSONValue is Codable. But this package ships its own JSON parser (OrderedJSON) for one reason: declared key order is preserved through the entire parse → validate → serialize pipeline, byte-stably across processes and platforms.
Foundation's parsers don't promise that:
| Operation | JSONDecoder |
JSONSerialization |
OrderedJSON |
|---|---|---|---|
| Object key order preserved on parse | ❌ unspecified | ✅ iOS 17+/macOS 14+/tvOS 17+/watchOS 10+ | ✅ all platforms |
| Object key order preserved on emit | ❌ unspecified | ❌ alphabetical with .sortedKeys |
✅ insertion order |
| Round-trip byte-stable | ❌ | ✅ |
For schema validation specifically, this matters because:
- Annotation/error output is deterministic — the order of
result.annotationsandresult.errorsreflects the validation traversal, which itself follows the instance's declared key order. Snapshot tests against validation output don't flake. - Serialized schemas are byte-stable — a schema constructed via
JSONSchemaBuilder(or parsed in) emits JSON in dialect-deterministic order, every run. Snapshots, signed payloads, generated artifacts all stay reproducible. - Linux parity — Foundation's
JSONSerializationonly preserves key order on iOS 17+ / macOS 14+ / tvOS 17+ / watchOS 10+; older OS versions and corelibs Foundation (Linux) make no such promise.OrderedJSONworks everywhere SwiftPM does.
Use OrderedJSON directly if you don't need the validator:
import OrderedJSON
let value = try JSONValue.parse(data) // preserves source key order
let bytes = try value.serializedData() // emits in that same order
let pretty = try value.serialized(options: .pretty)See issue #149 for the full background on why the determinism work happened.
Explore these companion repositories to see swift-json-schema in action:
- SwiftFunctionToolsExperiment – demonstrates creating type-safe OpenAI API function tool calls using schemas built with this library.
- swift-mcp-toolkit – a toolkit built on top of the official Model Context Protocol Swift SDK (modelcontextprotocol/swift-sdk) that makes it easy to define strongly-typed tools for MCP servers and clients.
- swift-json-schema-playground (live demo) – an in-browser JSON Schema validation playground that runs
swift-json-schemacompiled to WebAssembly via SwiftWasm, with a Monaco editor UI built on Vite + React.
Have a project of your own? We'd love to showcase it. Open a PR to add your repository to this list.
The full documentation for this library is available through the Swift Package Index.
You can add the SwiftJSONSchema package to your project using Swift Package Manager (SPM) or Xcode.
To add SwiftJSONSchema to your project using Swift Package Manager, add the following dependency to your Package.swift file:
dependencies: [
.package(url: "https://github.com/ajevans99/swift-json-schema", from: "0.2.1")
]Then, include JSONSchema and/or JSONSchemaBuilder as a dependency for your target:
targets: [
.target(
name: "YourTarget",
dependencies: [
.product(name: "JSONSchema", package: "swift-json-schema"),
.product(name: "JSONSchemaBuilder", package: "swift-json-schema"),
]
)
]- Open your project in Xcode.
- Navigate to File > Swift Packages > Add Package Dependency...
- Enter the repository URL: https://github.com/ajevans99/swift-json-schema
- Follow the prompts to add the package to your project.
Once added, you can import JSONSchema in your Swift files and start using it in your project.
This library is released under the MIT license. See LICENSE for details.