Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"json-schema/schema-types/integer",
"json-schema/schema-types/boolean",
"json-schema/schema-types/union",
"json-schema/schema-types/typeless",
"json-schema/schema-types/null"
]
},
Expand Down
63 changes: 63 additions & 0 deletions docs/json-schema/advanced/definitions-refs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,69 @@ $modernSchema = Schema::object('user', SchemaVersion::Draft_2019_09)

The package automatically uses the correct keyword based on the schema version.

## Plain-Name Anchors (`$anchor`)

Anchors provide a plain-name identifier for a schema that can be targeted by a `$ref` using the `#anchor` fragment syntax, instead of a full JSON Pointer path. Use `anchor()` to set one (Draft 2019-09+):

```php
use Cortex\JsonSchema\Schema;
use Cortex\JsonSchema\Enums\SchemaVersion;

$userSchema = Schema::object('user', SchemaVersion::Draft_2019_09)
->addDefinition(
'address',
Schema::object()
->anchor('address') // Plain-name anchor
->properties(
Schema::string('street')->required(),
Schema::string('city')->required()
)
)
->properties(
Schema::string('name')->required(),
// Reference the definition by its anchor instead of a pointer
Schema::object('home_address')->ref('#address')
);
```

<Accordion title="View JSON Schema Output">
```json
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "object",
"title": "user",
"$defs": {
"address": {
"type": "object",
"$anchor": "address",
"properties": {
"street": {
"type": "string"
},
"city": {
"type": "string"
}
},
"required": ["street", "city"]
}
},
"properties": {
"name": {
"type": "string"
},
"home_address": {
"$ref": "#address"
}
},
"required": ["name"]
}
```
</Accordion>

<Note>
`$anchor` requires Draft 2019-09 or later. Using `anchor()` on an older schema version throws a `SchemaException`.
</Note>

## Complex Real-World Example

Here's a comprehensive e-commerce schema using definitions:
Expand Down
72 changes: 72 additions & 0 deletions docs/json-schema/advanced/dependent-schemas.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,78 @@ $profileSchema = Schema::object('profile', SchemaVersion::Draft_2019_09)
]);
```

## Dependent Required Properties

When you only need to require *other* properties based on the presence of a property — without applying a full sub-schema — use `dependentRequired()`. It maps directly to the `dependentRequired` keyword (Draft 2019-09+), which was split out from the legacy `dependencies` keyword.

```php
use Cortex\JsonSchema\Schema;
use Cortex\JsonSchema\Enums\SchemaVersion;

$paymentSchema = Schema::object('payment', SchemaVersion::Draft_2019_09)
->properties(
Schema::string('amount')->required(),
Schema::string('credit_card'),
Schema::string('billing_address'),
Schema::string('cvv')
)
// When 'credit_card' is present, 'billing_address' and 'cvv' become required
->dependentRequired([
'credit_card' => ['billing_address', 'cvv'],
]);

// Valid - no credit card, so no extra requirements
$paymentSchema->isValid([
'amount' => '100.00'
]); // true

// Valid - credit card with its required companions
$paymentSchema->isValid([
'amount' => '100.00',
'credit_card' => '4111111111111111',
'billing_address' => '123 Main St',
'cvv' => '123'
]); // true

// Invalid - credit card present but missing dependent fields
$paymentSchema->isValid([
'amount' => '100.00',
'credit_card' => '4111111111111111'
]); // false (billing_address and cvv are required)
```

<Accordion title="View JSON Schema Output">
```json
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "object",
"title": "payment",
"properties": {
"amount": {
"type": "string"
},
"credit_card": {
"type": "string"
},
"billing_address": {
"type": "string"
},
"cvv": {
"type": "string"
}
},
"required": ["amount"],
"dependentRequired": {
"credit_card": ["billing_address", "cvv"]
}
}
```
</Accordion>

<Tip>
Reach for `dependentRequired()` when the dependency is purely about *requiring other properties*. Use `dependentSchema()` / `dependentSchemas()` when you need richer conditional validation (types, patterns, `if`/`then`/`else`, etc.).
</Tip>

## API Configuration Example

Real-world example for API endpoint configuration:
Expand Down
46 changes: 44 additions & 2 deletions docs/json-schema/code-generation/from-json.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ $schema->isValid($userData); // true

## Import from Array

Import a schema from a PHP array representation:
Import a schema from a PHP array representation using `Schema::fromArray()`:

```php
$schemaArray = [
Expand Down Expand Up @@ -93,9 +93,13 @@ $schemaArray = [
];

// Import from array
$productSchema = Schema::fromJson($schemaArray);
$productSchema = Schema::fromArray($schemaArray);
```

<Note>
`Schema::fromArray()` is a convenience wrapper around `Schema::fromJson()` for when you already have a decoded array. Both accept an optional `SchemaVersion` as the second argument; `Schema::fromJson()` additionally accepts a raw JSON string.
</Note>

<Accordion title="Imported Schema Usage">
```php
// Validate product data
Expand Down Expand Up @@ -237,6 +241,44 @@ $singleUserResponse = [
$apiSchema->isValid($singleUserResponse); // true
```

## Supported Keywords

The converter performs a full round-trip of the JSON Schema vocabulary, so importing and then re-exporting a schema preserves its keywords. Every schema type receives coverage for the following:

- **Metadata** - `title`, `description`, `$comment`, `examples`, `default`, `deprecated`, `readOnly`, `writeOnly`
- **Validation** - `enum`, `const` (including boolean and array/object values), `format`
- **Composition** - `allOf`, `anyOf`, `oneOf`, `not`
- **Conditionals** - `if` / `then` / `else`
- **References & identity** - `$ref`, `$anchor`, `$defs`, and draft-07 `definitions`
- **Object keywords** - `properties`, `required`, `additionalProperties`, `patternProperties`, `propertyNames`, `minProperties`, `maxProperties`, `unevaluatedProperties`, `dependentSchemas`, `dependentRequired`
- **Array keywords** - `items`, `prefixItems`, draft-07 tuple-style `items` arrays, `additionalItems`, `unevaluatedItems`, `contains`, `minContains`, `maxContains`, `minItems`, `maxItems`, `uniqueItems`

<Tip>
Version-specific keywords are imported using the detected (or supplied) `SchemaVersion`. For example, `unevaluatedProperties`, `dependentSchemas`, `dependentRequired`, and `$anchor` require Draft 2019-09+, while `prefixItems` requires Draft 2020-12.
</Tip>

### Typeless Schemas

Schemas without a `type` keyword that carry structural keywords (such as `$ref`, `$defs`, `allOf`, `properties`, or `required`) are imported as a `TypelessSchema` — the converter does **not** expand them into a union of all types. This keeps composition-only and definition-only documents intact:

```php
// A definition-only document with no top-level "type"
$schema = Schema::fromArray([
'$defs' => [
'product' => [
'type' => 'object',
'properties' => [
'name' => ['type' => 'string'],
],
],
],
]);

$schema->toArray(); // No "type" key is emitted
```

See [Typeless Schema](/json-schema/schema-types/typeless) for building typeless schemas with the fluent builder via `Schema::typeless()`.

## Common Use Cases

<CardGroup cols={2}>
Expand Down
10 changes: 7 additions & 3 deletions docs/json-schema/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ All schema types support common properties like title, description, examples, de
- **Boolean Schema** - True/false validation with default values
- **Null Schema** - Null value validation and nullable type support
- **Union Schema** - Multi-type schemas with nullable support and discriminated unions
- **Typeless Schema** - Composition-only and definition-only documents with no `type` keyword

## Advanced Features

Expand All @@ -137,10 +138,11 @@ All schema types support common properties like title, description, examples, de
- **not** - Schema must not match

### Modern JSON Schema Features
- **Definitions & References** - Reusable schema components with `$ref`
- **Unevaluated Properties** - Advanced property validation (Draft 2019-09+)
- **Dependent Schemas** - Property-dependent validation rules
- **Definitions & References** - Reusable schema components with `$ref`, `$defs`, and plain-name `$anchor`
- **Unevaluated Properties** - Advanced property/item validation (Draft 2019-09+)
- **Dependent Schemas** - Property-dependent validation rules, including `dependentRequired`
- **Pattern Properties** - Regex-based property validation
- **Tuple Validation** - `prefixItems` (Draft 2020-12) and legacy `items`/`additionalItems` tuples (Draft-07)
- **Contains Validation** - Array item existence validation

### Code Generation
Expand Down Expand Up @@ -170,4 +172,6 @@ The package defaults to Draft 2020-12 for the latest features. Use Draft-06 for
| `minContains`/`maxContains` | ❌ | ❌ | ✅ | ✅ |
| `unevaluatedProperties` | ❌ | ❌ | ✅ | ✅ |
| `dependentSchemas` | ❌ | ❌ | ✅ | ✅ |
| `dependentRequired` | ❌ | ❌ | ✅ | ✅ |
| `$anchor` | ❌ | ❌ | ✅ | ✅ |
| `prefixItems` | ❌ | ❌ | ❌ | ✅ |
60 changes: 60 additions & 0 deletions docs/json-schema/schema-types/array.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,66 @@ $flexibleTupleSchema->isValid(['John', 30, 123]); // false (additi
- **Use `prefixItems()`** when you need positional validation (tuples) or when the first N items have specific schemas
- **Combine both** when you need a fixed prefix followed by additional items with a different schema

### Legacy Tuples with tupleItems (Draft-07 and earlier)

In Draft-07 and Draft-06, tuple validation is expressed by setting `items` to an *array* of schemas, with `additionalItems` controlling whatever follows the tuple. Use `tupleItems()` and `additionalItems()` for this legacy style:

```php
use Cortex\JsonSchema\Enums\SchemaVersion;

$coordinateSchema = Schema::array('coordinates', SchemaVersion::Draft_07)
->tupleItems([
Schema::number()->description('latitude'),
Schema::number()->description('longitude'),
])
->additionalItems(false) // No items beyond the tuple
->description('Geographic coordinates [lat, lng]');

$coordinateSchema->isValid([51.5074, -0.1278]); // true
$coordinateSchema->isValid([51.5074, -0.1278, 100]); // false (additional items not allowed)
```

<Accordion title="View JSON Schema Output">
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"title": "coordinates",
"items": [
{
"type": "number",
"description": "latitude"
},
{
"type": "number",
"description": "longitude"
}
],
"additionalItems": false,
"description": "Geographic coordinates [lat, lng]"
}
```
</Accordion>

You can also pass a schema to `additionalItems()` so that any items after the tuple must match it:

```php
$recordSchema = Schema::array('record', SchemaVersion::Draft_07)
->tupleItems([
Schema::string()->description('name'),
Schema::integer()->description('age'),
])
->additionalItems(Schema::string()); // Extra items must be strings

$recordSchema->isValid(['John', 30]); // true
$recordSchema->isValid(['John', 30, 'admin', 'staff']); // true
$recordSchema->isValid(['John', 30, 99]); // false (extra item not a string)
```

<Note>
`tupleItems()` / `additionalItems()` map to the Draft-07 array-form `items` + `additionalItems` keywords. For Draft 2020-12, prefer `prefixItems()` combined with `items()`, which supersede them.
</Note>

## Contains Validation

Validate that an array contains specific items:
Expand Down
16 changes: 16 additions & 0 deletions docs/json-schema/schema-types/object.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -503,8 +503,24 @@ $conditionalSchema = Schema::object('payment', SchemaVersion::Draft_2019_09)
Schema::string('card_number')->required()
))
);

// Dependent required properties (Draft 2019-09+)
$cardSchema = Schema::object('payment', SchemaVersion::Draft_2019_09)
->properties(
Schema::string('credit_card'),
Schema::string('billing_address'),
Schema::string('cvv')
)
// If 'credit_card' is present, these properties become required
->dependentRequired([
'credit_card' => ['billing_address', 'cvv'],
]);
```

<Note>
See [Dependent Schemas](/json-schema/advanced/dependent-schemas) for a deeper look at `dependentSchema()`, `dependentSchemas()`, and `dependentRequired()`.
</Note>

## Validation Examples

```php
Expand Down
Loading
Loading