migrating-json-schemas

Migrates JSON Schemas between draft versions for use with z-schema. Use when the user wants to upgrade schemas from draft-04 to draft-2020-12, convert between draft formats, update deprecated keywords, replace id with $id, convert definitions to $defs, migrate items to prefixItems, replace dependencies with dependentRequired or dependentSchemas, adopt unevaluatedProperties or unevaluatedItems, or adapt schemas to newer JSON Schema features.

zaggino/z-schema143 installsMITSynced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI

Agent Skills format with YAML frontmatter. Claude Code reads it as-is.

---
name: "migrating-json-schemas"
description: "Migrates JSON Schemas between draft versions for use with z-schema. Use when the user wants to upgrade schemas from draft-04 to draft-2020-12, convert between draft formats, update deprecated keywords, replace id with $id, convert definitions to $defs, migrate items to prefixItems, replace dependencies with dependentRequired or dependentSchemas, adopt unevaluatedProperties or unevaluatedItems, or adapt schemas to newer JSON Schema features."
license: "MIT"
---

# Migrating JSON Schemas Between Drafts

z-schema supports draft-04, draft-06, draft-07, draft-2019-09, and draft-2020-12. This skill covers migrating schemas between drafts and verifying them with z-schema.

## Migration workflow

1. Identify the source draft (check `$schema` or `id`/`$id` usage).
2. Set the target version on the validator:
   ```typescript
   import ZSchema from 'z-schema';
   const validator = ZSchema.create({ version: 'draft2020-12' });
   ```
3. Run `validator.validateSchema(schema)` to surface incompatibilities.
4. Fix each reported error using the keyword mapping below.
5. Re-validate until the schema passes.

## Quick reference: keyword changes

| Old keyword (draft-04)               | New keyword (draft-2020-12)          | Introduced in |
| ------------------------------------ | ------------------------------------ | ------------- |
| `id`                                 | `$id`                                | draft-06      |
| `definitions`                        | `$defs`                              | draft-2019-09 |
| Array-form `items` (tuple)           | `prefixItems`                        | draft-2020-12 |
| `additionalItems`                    | `items` (when `prefixItems` present) | draft-2020-12 |
| `exclusiveMinimum: true` (boolean)   | `exclusiveMinimum: <number>`         | draft-06      |
| `exclusiveMaximum: true` (boolean)   | `exclusiveMaximum: <number>`         | draft-06      |
| `dependencies` (string arrays)       | `dependentRequired`                  | draft-2019-09 |
| `dependencies` (schema values)       | `dependentSchemas`                   | draft-2019-09 |
| `$recursiveRef` / `$recursiveAnchor` | `$dynamicRef` / `$dynamicAnchor`     | draft-2020-12 |

For the complete keyword mapping with examples, see [references/keyword-mapping.md](references/keyword-mapping.md).

## Common migration paths

### Draft-04 → Draft-2020-12

This is the largest jump. Apply changes in order:

**1. Rename `id` to `$id`**

```json
// Before (draft-04)
{ "id": "http://example.com/person.json", "type": "object" }

// After (draft-2020-12)
{ "$id": "http://example.com/person.json", "type": "object" }
```

**2. Convert boolean `exclusiveMinimum`/`exclusiveMaximum` to numeric**

```json
// Before (draft-04)
{ "type": "number", "minimum": 0, "exclusiveMinimum": true }

// After (draft-2020-12)
{ "type": "number", "exclusiveMinimum": 0 }
```

Note: the `minimum` keyword is removed when converting to `exclusiveMinimum` as a number, since `exclusiveMinimum: 0` means "greater than 0".

**3. Rename `definitions` to `$defs`**

```json
// Before
{ "definitions": { "address": { "type": "object" } } }

// After
{ "$defs": { "address": { "type": "object" } } }
```

Update all `$ref` values that point to `#/definitions/...` → `#/$defs/...`.

**4. Split `dependencies`**

```json
// Before (draft-04) — mixed dependencies
{
  "dependencies": {
    "billing_address": ["credit_card"],
    "credit_card": { "type": "object", "properties": { "cvv": { "type": "string" } } }
  }
}

// After (draft-2020-12) — split into two keywords
{
  "dependentRequired": {
    "billing_address": ["credit_card"]
  },
  "dependentSchemas": {
    "credit_card": { "type": "object", "properties": { "cvv": { "type": "string" } } }
  }
}
```

**5. Convert tuple `items` to `prefixItems`**

```json
// Before (draft-04)
{
  "type": "array",
  "items": [{ "type": "string" }, { "type": "number" }],
  "additionalItems": false
}

// After (draft-2020-12)
{
  "type": "array",
  "prefixItems": [{ "type": "string" }, { "type": "number" }],
  "items": false
}
```

When `items` was an array (tuple validation), it becomes `prefixItems`. The old `additionalItems` becomes `items`.

**6. Add `$schema` declaration**

```json
{ "$schema": "https://json-schema.org/draft/2020-12/schema" }
```

### Draft-07 → Draft-2020-12

Smaller jump. Main changes:

1. `definitions` → `$defs` (and update `$ref` paths)
2. Array-form `items` → `prefixItems`
3. `additionalItems` → `items` (when `prefixItems` present)
4. `dependencies` → `dependentRequired` / `dependentSchemas`
5. Consider adopting `unevaluatedProperties` / `unevaluatedItems` for stricter validation of combined schemas

### Draft-2019-09 → Draft-2020-12

Minimal changes:

1. Array-form `items` → `prefixItems`, `additionalItems` → `items`
2. `$recursiveRef`/`$recursiveAnchor` → `$dynamicRef`/`$dynamicAnchor`

## Verifying a migrated schema

After migration, validate the schema itself against the target draft's meta-schema:

```typescript
import ZSchema from 'z-schema';

const validator = ZSchema.create({ version: 'draft2020-12' });

try {
  validator.validateSchema(migratedSchema);
  console.log('Schema is valid for draft-2020-12');
} catch (err) {
  console.log('Schema issues:', err.details);
}
```

Then test data validation to confirm behavior is unchanged:

```typescript
// Test with known-good data
validator.validate(knownGoodData, migratedSchema);

// Test with known-bad data
const { valid } = validator.validateSafe(knownBadData, migratedSchema);
if (valid) {
  console.warn('Migration issue: previously invalid data now passes');
}
```

## Backward compatibility

If schemas must work across multiple draft versions, use `version: 'none'` and set `$schema` in each schema to declare its own draft:

```typescript
const validator = ZSchema.create({ version: 'none' });
```

## Reference files

- [references/keyword-mapping.md](references/keyword-mapping.md) — Complete keyword mapping across all drafts with before/after examples

More General & Other skills

← All General & Other skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY