# jsonschema
[](https://hackage.haskell.org/package/jsonschema)
Haskell library for deriving and validating JSON Schema (2020-12).
This library provides:
- Automatic JSON Schema derivation from Haskell types via `GHC.Generics`.
- Sensible encodings for records, products, and sum types (with tags).
- `$defs`/`$ref` support for recursive types.
- A validator that implements the core 2020-12 validation + applicator vocabularies.
- Helpful error reporting with instance paths when you want detailed feedback.
- High-level API exposed through `Data.JSON.JSONSchema` (re-exporting `ToJSONSchema` and helpers).
## Features
- Derive schemas with the `ToJSONSchema` type class; generic default handles most ADTs.
- Records become JSON objects with named properties, emit `"required"` for every field, and forbid extras via `additionalProperties: false`. Non-record products become fixed-length arrays with `prefixItems`, `items: false`, and `minItems`/`maxItems` pinning the exact length.
- Sum types are modeled with discriminator tags:
- Record constructors: object with a required `tag` (constructor name) and the record fields.
- Non-record constructors: object `{ tag, contents }`, both required, where `contents` carries the constructor’s payload (array/object).
- Recursive types are emitted under `"$defs"` and referenced with `"$ref"`.
- Validation covers: `type`, `const`, `enum`, numeric and string constraints, arrays (`prefixItems`, `items`, `contains`, `minContains`, `maxContains`), objects (`properties`, `patternProperties`, `additionalProperties`, `propertyNames`, `required`, `dependentSchemas`, `dependentRequired`), combinators (`anyOf`, `oneOf`, `allOf`, `not`), conditionals (`if`/`then`/`else`), and pragmatic `unevaluated*`.
- Local `$ref` resolution using JSON Pointers within the same document.
Notes and limits:
- JSON Schema version: 2020-12. `format` and `content*` are treated as annotations (not asserted).
- `$ref` resolution is local (`#...`) only; external URIs/anchors are not resolved.
- `unevaluatedProperties`/`unevaluatedItems` are implemented with a practical, local approximation.
## Quick Start
Add the library to your build, then import the high-level module:
```haskell
import GHC.Generics (Generic)
import Data.Aeson (ToJSON, Value, object, (.=))
import Data.Proxy (Proxy(..))
import Data.Text (Text)
import Data.JSON.JSONSchema -- ToJSONSchema(..), Proxy(..), validateJSONSchema, validate, validateWithErrors, ValidationError(..)
```
### 1) Derive a schema for your type
```haskell
data Person = Person
{ name :: Text
, age :: Int
} deriving (Show, Eq, Generic)
instance ToJSON Person
instance ToJSONSchema Person
-- Produce the JSON Schema (as an Aeson Value)
personSchema :: Value
personSchema = toJSONSchema (Proxy :: Proxy Person)
```
What you get (shape, simplified):
```jsonc
{
"$defs": {
"Person": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"}
},
"additionalProperties": false,
"required": ["name", "age"]
}
},
"$ref": "#/$defs/Person"
}
```
Sum types are tagged. For example:
```haskell
data Shape
= Circle Double
| Rectangle Double Double
deriving (Show, Eq, Generic)
instance ToJSON Shape
instance ToJSONSchema Shape
shapeSchema :: Value
shapeSchema = toJSONSchema (Proxy :: Proxy Shape)
```
Non-record constructors encode as objects like `{ tag: { const: "Circle" }, contents: <payload> }`.
Record constructors encode as objects with a `tag` plus their named fields.
### 2) Validate data against a schema
Use the simple boolean check:
```haskell
import Data.Aeson (toJSON)
valid :: Bool
valid = validateJSONSchema personSchema (toJSON (Person "Alice" 30))
```
Or collect all validation errors:
```haskell
import Data.JSON.JSONSchema (validate, validateWithErrors, ValidationError(..))
case validate personSchema (toJSON (Person "Alice" 30)) of
Right () -> putStrLn "OK"
Left errs -> mapM_ print errs -- includes JSON Pointer-like paths
```
You can validate any `Value` against any schema, including hand-written schemas:
```haskell
let schema = object
[ "type" .= ("object" :: Text)
, "properties" .= object ["name" .= object ["type" .= ("string" :: Text)]]
, "required" .= (["name"] :: [Text])
]
in validateJSONSchema schema (object ["name" .= ("Bob" :: Text)])
```
### 3) Value-directed construction
When your "types" are runtime *values* rather than Haskell types — e.g. an
interpreter whose type AST is a single datatype — derivation via `ToJSONSchema`
does not apply. Instead, fold your value into a `Schema` and render it; the library
owns the JSON encoding:
```haskell
import Data.JSON.JSONSchema -- Schema(..), Field(..), AdditionalProperties(..), SchemaDocument(..), schemaToValue, schemaDocumentToValue
userDoc :: SchemaDocument
userDoc = SchemaDocument
{ schemaDefs = [("Code", SString (Just "^[a-z]+$"))] -- a named $def
, schemaRoot = SObject
[ Field "code" True (SRef "Code") -- required, references the $def
, Field "count" False SInteger -- optional
]
APForbidden -- additionalProperties: false
}
userSchema :: Value
userSchema = schemaDocumentToValue userDoc
ok = validateJSONSchema userSchema (object ["code" .= ("abc" :: Text), "count" .= (3 :: Int)]) -- True
bad = validateJSONSchema userSchema (object ["code" .= ("ABC" :: Text)]) -- False (off-pattern)
```
A homogeneous map/dictionary is `SObject [] (APSchema valueSchema)`
(`additionalProperties: <schema>`). The `pattern` field is matched with TDFA
regexes (POSIX ERE), not the full ECMA-262 dialect, so keep patterns simple.
`Schema` covers the data-shape subset; for any keyword outside it (e.g.
`minLength`, numeric bounds, `title`), use the `SRaw` escape hatch to embed an
arbitrary schema `Value` at any node — e.g. `Field "name" True (SRaw (object ["type" .= ("string" :: Text), "minLength" .= (3 :: Int)]))`.
### 4) Custom schemas for special types
Provide an explicit instance when you need a specific schema shape:
```haskell
newtype UUID = UUID Text
instance ToJSONSchema UUID where
toJSONSchema _ = object
[ "type" .= ("string" :: Text)
, "minLength" .= (36 :: Int)
, "maxLength" .= (36 :: Int)
]
```
## Tips
- Pretty-print schemas with `aeson-pretty` if you want human-friendly output.
- For `Maybe a`, the schema is `anyOf [schema(a), {"type":"null"}]`.
- For `[a]`, the schema is `{ "type": "array", "items": schema(a) }`.
- For `Either a b`, the schema is `anyOf` with `{ "Left": a }` and `{ "Right": b }` object encodings.
## Development
- Build and test with Cabal:
- `cabal build`
- `cabal test`
## License
Released under the Mozilla Public License 2.0 by DPella AB. See `LICENSE` for details.