Skip to content

Commit 241316b

Browse files
quinnjclaude
andauthored
v2.0 rewrite: schema generation from Julia types (#70)
This is a complete rewrite of JSONSchema.jl that transforms it from a pure validation library to a schema generation and validation library. ## New Features - `schema(T)`: Generate JSON Schema from Julia struct definitions - `Schema{T}`: Type-parameterized schema for type-safe validation - StructUtils integration for field-level validation tags - `$ref` support for schema deduplication and recursive types - `ValidationResult` struct with detailed error messages ## Breaking Changes - `validate()` now returns `ValidationResult` instead of `nothing`/`SingleIssue` - `JSON.schema/validate/isvalid` convenience methods removed - `parent_dir` kwarg removed from `Schema` constructor - See docs/src/migration.md for full migration guide ## Backwards Compatibility - `schema.data` field access (maps to `schema.spec`) - `Schema(true)`/`Schema(false)` boolean schemas - `validate(data, schema)` inverse argument order - `isvalid(data, schema)` inverse argument order - `required` validation without `properties` - `diagnose()` function (deprecated) - `SingleIssue` type alias ## Other Changes - Removed JSON3 extension - Updated CI to modern GitHub Actions - Added comprehensive documentation - Bumped minimum Julia version to 1.10 - Version bump to 2.0.0 Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent a52321a commit 241316b

25 files changed

+4230
-1580
lines changed

.JuliaFormatter.toml

Lines changed: 0 additions & 8 deletions
This file was deleted.

.github/workflows/CI.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main, master]
6+
tags: ["*"]
7+
pull_request:
8+
release:
9+
10+
jobs:
11+
test:
12+
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
13+
runs-on: ${{ matrix.os }}
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
version:
18+
- '1' # automatically expands to the latest stable 1.x release of Julia
19+
- 'min'
20+
- 'pre'
21+
os:
22+
- ubuntu-latest
23+
- windows-latest
24+
arch:
25+
- x64
26+
include:
27+
- os: macOS-latest
28+
arch: aarch64
29+
version: 1
30+
- os: ubuntu-latest
31+
arch: x86
32+
version: 1
33+
steps:
34+
- uses: actions/checkout@v6
35+
- uses: julia-actions/setup-julia@v2
36+
with:
37+
version: ${{ matrix.version }}
38+
arch: ${{ matrix.arch }}
39+
- uses: julia-actions/cache@v2
40+
- uses: julia-actions/julia-buildpkg@v1
41+
- uses: julia-actions/julia-runtest@v1
42+
- uses: julia-actions/julia-processcoverage@v1
43+
- uses: codecov/codecov-action@v5
44+
with:
45+
files: lcov.info
46+
token: ${{ secrets.CODECOV_TOKEN }}
47+
docs:
48+
name: Documentation
49+
runs-on: ubuntu-latest
50+
permissions:
51+
contents: write
52+
steps:
53+
- uses: actions/checkout@v6
54+
- uses: julia-actions/setup-julia@v2
55+
with:
56+
version: '1'
57+
- uses: julia-actions/cache@v2
58+
- uses: julia-actions/julia-buildpkg@v1
59+
- uses: julia-actions/julia-docdeploy@v1
60+
env:
61+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
62+
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}

.github/workflows/CompatHelper.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: CompatHelper
2+
on:
3+
schedule:
4+
- cron: 0 0 * * *
5+
workflow_dispatch:
6+
permissions:
7+
contents: write
8+
pull-requests: write
9+
jobs:
10+
CompatHelper:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Check if Julia is already available in the PATH
14+
id: julia_in_path
15+
run: which julia
16+
continue-on-error: true
17+
- name: Install Julia, but only if it is not already available in the PATH
18+
uses: julia-actions/setup-julia@v2
19+
with:
20+
version: '1'
21+
# arch: ${{ runner.arch }}
22+
if: steps.julia_in_path.outcome != 'success'
23+
- name: "Add the General registry via Git"
24+
run: |
25+
import Pkg
26+
ENV["JULIA_PKG_SERVER"] = ""
27+
Pkg.Registry.add("General")
28+
shell: julia --color=yes {0}
29+
- name: "Install CompatHelper"
30+
run: |
31+
import Pkg
32+
name = "CompatHelper"
33+
uuid = "aa819f21-2bde-4658-8897-bab36330d9b7"
34+
version = "3"
35+
Pkg.add(; name, uuid, version)
36+
shell: julia --color=yes {0}
37+
- name: "Run CompatHelper"
38+
run: |
39+
import CompatHelper
40+
CompatHelper.main()
41+
shell: julia --color=yes {0}
42+
env:
43+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44+
COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }}
45+
# COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }}

.github/workflows/TagBot.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ jobs:
1111
steps:
1212
- uses: JuliaRegistries/TagBot@v1
1313
with:
14-
token: ${{ secrets.GITHUB_TOKEN }}
14+
ssh: ${{ secrets.DOCUMENTER_KEY }}
15+
token: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/ci.yml

Lines changed: 0 additions & 38 deletions
This file was deleted.

.github/workflows/format-check.yml

Lines changed: 0 additions & 31 deletions
This file was deleted.

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,9 @@
22
*.jl.*.cov
33
*.jl.mem
44
Manifest.toml
5+
Manifest-*.toml
6+
docs/build/
7+
docs/Manifest.toml
8+
docs/Manifest-*.toml
9+
test/Manifest.toml
10+
test/Manifest-*.toml

LICENSE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The JSONSchema.jl package is licensed under the MIT "Expat" License:
22

3-
> Copyright (c) 2018: fredo.
3+
> Copyright (c) 2018-2026: fredo, quinnj.
44
>
55
> Permission is hereby granted, free of charge, to any person obtaining a copy
66
> of this software and associated documentation files (the "Software"), to deal
@@ -19,4 +19,4 @@ The JSONSchema.jl package is licensed under the MIT "Expat" License:
1919
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121
> SOFTWARE.
22-
>
22+

Project.toml

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
name = "JSONSchema"
22
uuid = "7d188eb4-7ad8-530c-ae41-71a32a6d4692"
3-
version = "1.5.0"
3+
version = "2.0.0"
44

55
[deps]
66
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
77
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
8+
StructUtils = "ec057cc2-7a8d-4b58-b3b3-92acb9f63b42"
89
URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
910

10-
[weakdeps]
11-
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
12-
13-
[extensions]
14-
JSONSchemaJSON3Ext = "JSON3"
15-
1611
[compat]
17-
JSON = "0.21, 1"
18-
JSON3 = "1"
12+
Downloads = "1"
13+
JSON = "1"
14+
StructUtils = "2"
1915
URIs = "1"
20-
julia = "1.9"
16+
julia = "1.10"

README.md

Lines changed: 56 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,79 @@
11
# JSONSchema.jl
22

3-
[![Build Status](https://github.com/fredo-dedup/JSONSchema.jl/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/fredo-dedup/JSONSchema.jl/actions?query=workflow%3ACI)
4-
[![codecov](https://codecov.io/gh/fredo-dedup/JSONSchema.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/fredo-dedup/JSONSchema.jl)
3+
[![CI](https://github.com/JuliaServices/JSONSchema.jl/actions/workflows/CI.yml/badge.svg?branch=master)](https://github.com/JuliaServices/JSONSchema.jl/actions?query=workflow%3ACI)
4+
[![codecov](https://codecov.io/gh/JuliaServices/JSONSchema.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaServices/JSONSchema.jl)
5+
[![Docs](https://img.shields.io/badge/docs-stable-blue.svg)](https://juliaservices.github.io/JSONSchema.jl/stable)
56

67
## Overview
78

8-
[JSONSchema.jl](https://github.com/fredo-dedup/JSONSchema.jl) is a JSON
9-
validation package for the [Julia](https://julialang.org/) programming language.
10-
Given a [validation schema](http://json-schema.org/specification.html), this
11-
package can verify if a JSON instance meets all the assertions that define a
12-
valid document.
9+
JSONSchema.jl generates JSON Schema (draft-07) from Julia types and validates
10+
instances against those schemas. It also supports validating data against
11+
hand-written JSON Schema objects. Field-level validation rules are provided via
12+
`StructUtils` tags.
1313

14-
This package has been tested with the
14+
> **Upgrading from v1.x?** See the [v2.0 Migration Guide](https://juliaservices.github.io/JSONSchema.jl/stable/migration/) for breaking changes and upgrade instructions.
15+
16+
The test harness is wired to the
1517
[JSON Schema Test Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite)
16-
for draft v4 and v6.
18+
for draft4, draft6, and draft7.
1719

18-
## API
20+
## Installation
1921

20-
Create a `Schema` object by passing a string:
21-
```julia
22-
julia> my_schema = Schema("""{
23-
"properties": {
24-
"foo": {},
25-
"bar": {}
26-
},
27-
"required": ["foo"]
28-
}""")
29-
```
30-
passing a dictionary with the same structure as a schema:
3122
```julia
32-
julia> my_schema = Schema(
33-
Dict(
34-
"properties" => Dict(
35-
"foo" => Dict(),
36-
"bar" => Dict()
37-
),
38-
"required" => ["foo"]
39-
)
40-
)
41-
```
42-
or by passing a parsed JSON file containing the schema:
43-
```julia
44-
julia> my_schema = Schema(JSON.parsefile(filename))
23+
using Pkg
24+
Pkg.add("JSONSchema")
4525
```
4626

47-
Check the validity of a parsed JSON instance by calling `validate` with the JSON
48-
instance `x` to be tested and the `schema`.
27+
## Usage
28+
29+
### Generate a schema from a Julia type
4930

50-
If the validation succeeds, `validate` returns `nothing`:
5131
```julia
52-
julia> document = """{"foo": true}""";
32+
using JSONSchema, StructUtils
5333

54-
julia> data_pass = JSON.parse(document)
55-
Dict{String,Bool} with 1 entry:
56-
"foo" => true
34+
@defaults struct User
35+
id::Int = 0 &(json=(minimum=1,),)
36+
name::String = "" &(json=(minLength=1,),)
37+
email::String = "" &(json=(format="email",),)
38+
end
5739

58-
julia> validate(my_schema, data_pass)
40+
schema = JSONSchema.schema(User)
41+
user = User(1, "Alice", "alice@example.com")
42+
result = JSONSchema.validate(schema, user)
5943

44+
result.is_valid # true
6045
```
6146

62-
If the validation fails, a struct is returned that, when printed, explains the
63-
reason for the failure:
47+
### Validate JSON data against a schema object
48+
6449
```julia
65-
julia> data_fail = Dict("bar" => 12.5)
66-
Dict{String,Float64} with 1 entry:
67-
"bar" => 12.5
68-
69-
julia> validate(my_schema, data_fail)
70-
Validation failed:
71-
path: top-level
72-
instance: Dict("bar"=>12.5)
73-
schema key: required
74-
schema value: ["foo"]
50+
using JSON, JSONSchema
51+
52+
schema = JSONSchema.Schema(JSON.parse("""
53+
{
54+
"type": "object",
55+
"properties": {"foo": {"type": "integer"}},
56+
"required": ["foo"]
57+
}
58+
"""))
59+
60+
data = JSON.parse("""{"foo": 1}""")
61+
JSONSchema.isvalid(schema, data) # true
7562
```
7663

77-
As a short-hand for `validate(schema, x) === nothing`, use
78-
`Base.isvalid(schema, x)`
64+
## Features
65+
66+
- **Schema Generation**: Automatically generate JSON Schema from Julia struct definitions
67+
- **Type-Safe Validation**: Validate Julia instances against generated schemas
68+
- **StructUtils Integration**: Use field tags for validation rules (min/max, patterns, formats, etc.)
69+
- **Composition Support**: `oneOf`, `anyOf`, `allOf`, `not` combinators
70+
- **Reference Support**: `$ref` with `definitions` for complex/recursive types
71+
- **Format Validation**: Built-in validators for `email`, `uri`, `uuid`, `date-time`
72+
73+
## Documentation
7974

80-
Note that if `x` is a `String` in JSON format, you must use `JSON.parse(x)`
81-
before passing to `validate`, that is, JSONSchema operates on the parsed
82-
representation, not on the underlying `String` representation of the JSON data.
75+
See the [documentation](https://juliaservices.github.io/JSONSchema.jl/stable) for:
76+
- Complete API reference
77+
- Validation rules and field tags
78+
- Type mapping reference
79+
- Advanced usage with `$ref` and composition

0 commit comments

Comments
 (0)