Skip to content

Commit 45509e5

Browse files
authored
docs: Update README/serialization docs (#235)
## Update README/serialization docs with details on `$ref`/`$defs`)deduplication, `kotlin.Any` mapping to `{}`, and sealed class discriminator behavior - Revised `JsonSchemaConfig` table in `serializable.md` to clarify property defaults and add new polymorphism details. - Explained subtype discriminator constant in generated schema examples.
1 parent c0f1a05 commit 45509e5

File tree

2 files changed

+44
-29
lines changed

2 files changed

+44
-29
lines changed

README.md

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,13 @@ Quick Links:
109109

110110
**Comprehensive Type Support:**
111111

112-
- Enums, collections, maps, nested objects, nullability, generics (with star-projection)
113-
- Sealed class hierarchies with automatic `oneOf` generation
114-
- Proper union types for nullable parameters (`["string", "null"]`)
115-
- Type constraints (min/max, patterns, formats)
112+
- **Enums, collections, maps, nested objects, nullability, generics** (with star-projection)
113+
- **Sealed class hierarchies** with automatic `oneOf` generation and discriminator field
114+
- **Union types** for nullable parameters (`["string", "null"]`)
115+
- **Type constraints** (min/max, patterns, formats)
116116
- **Default values** (compile-time: tracked but not extracted; runtime: fully extracted)
117+
- **`$ref`/`$defs` deduplication**: named types appear once in `$defs` and are referenced everywhere via `$ref`
118+
- **`kotlin.Any`**: maps to the empty schema `{}` (accepts any JSON value)
117119

118120
**Developer Experience:**
119121

@@ -323,7 +325,8 @@ Schemas follow JSON Schema Draft 2020-12 format. Example (pretty-printed):
323325
- Nullable properties are emitted as a union including `null`.
324326
- Collections: `List<T>`/`Set<T>``{ "type":"array", "items": T }`; `Map<String, V>`
325327
`{ "type":"object", "additionalProperties": V }`.
326-
- Unknown/generic type parameters resolve to `kotlin.Any` with a minimal definition in `$defs`.
328+
- `kotlin.Any` / unbound generic type parameters (e.g., `T`) map to the empty schema `{}`, which accepts any JSON value.
329+
- Named types (nested objects, enums, sealed classes) are deduplicated in a `$defs` section and referenced via `$ref` at every usage site.
327330

328331
## Examples
329332

@@ -395,16 +398,14 @@ val schemaObject = Product::class.jsonSchema
395398
},
396399
"inStock": {
397400
"type": "boolean",
398-
"description": "Whether the product is currently in stock",
399-
"default": true
401+
"description": "Whether the product is currently in stock"
400402
},
401403
"tags": {
402404
"type": "array",
403405
"items": {
404406
"type": "string"
405407
},
406-
"description": "List of tags for categorization and search",
407-
"default": []
408+
"description": "List of tags for categorization and search"
408409
}
409410
},
410411
"required": [
@@ -542,9 +543,9 @@ data class Container<T>(
542543

543544
<!--- KNIT example-knit-readme-06.kt -->
544545

545-
Generic type parameters are resolved at the usage site. When generating a schema for a generic class, unbound type
546-
parameters (like `T`) are treated as `kotlin.Any` with a minimal definition in the `$defs` section. For more specific
547-
typing, instantiate the generic class with concrete types when you need them.
546+
Generic type parameters are resolved at the usage site. Unbound type parameters (like `T`) and `kotlin.Any`-typed
547+
properties map to `{}` — the empty JSON Schema that accepts any JSON value. For more specific typing, instantiate the
548+
generic class with concrete types when you need them.
548549

549550
### Sealed class polymorphism
550551

@@ -611,19 +612,19 @@ println(schema.encodeToString(Json { prettyPrint = true }))
611612
"additionalProperties": false,
612613
"oneOf": [
613614
{
614-
"$ref": "#/$defs/Animal.Cat"
615+
"$ref": "#/$defs/kotlinx.schema.integration.type.Animal.Cat"
615616
},
616617
{
617-
"$ref": "#/$defs/Animal.Dog"
618+
"$ref": "#/$defs/kotlinx.schema.integration.type.Animal.Dog"
618619
}
619620
],
620621
"$defs": {
621-
"Animal.Cat": {
622+
"kotlinx.schema.integration.type.Animal.Cat": {
622623
"type": "object",
623624
"properties": {
624625
"type": {
625626
"type": "string",
626-
"const": "Animal.Cat"
627+
"const": "kotlinx.schema.integration.type.Animal.Cat"
627628
},
628629
"name": {
629630
"type": "string",
@@ -636,12 +637,12 @@ println(schema.encodeToString(Json { prettyPrint = true }))
636637
],
637638
"additionalProperties": false
638639
},
639-
"Animal.Dog": {
640+
"kotlinx.schema.integration.type.Animal.Dog": {
640641
"type": "object",
641642
"properties": {
642643
"type": {
643644
"type": "string",
644-
"const": "Animal.Dog"
645+
"const": "kotlinx.schema.integration.type.Animal.Dog"
645646
},
646647
"name": {
647648
"type": "string",
@@ -662,10 +663,11 @@ println(schema.encodeToString(Json { prettyPrint = true }))
662663

663664
**Key features:**
664665

665-
- **`oneOf` with `$ref`**: Each sealed subclass is referenced from a `$defs` section
666-
- **Property inheritance**: Base class properties included in each subtype
666+
- **`oneOf` with `$ref`**: Each sealed subclass is stored in `$defs` and referenced via `$ref`
667+
- **Fully qualified names**: `$defs` keys and discriminator `const` values use fully qualified class names (e.g., `com.example.Animal.Cat`) to avoid collisions across packages
668+
- **Discriminator property**: A `type` field with a `const` value is automatically added to each subtype for runtime dispatch
669+
- **Property inheritance**: Base class properties are included in each subtype
667670
- **Type safety**: Each subtype gets its own schema definition
668-
- **Default values**: Subtype properties with defaults (like `lives: Int = 9`) are included
669671

670672
## Using @Schema and @Description annotations
671673

docs/serializable.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -281,13 +281,14 @@ This code generates:
281281

282282
### JsonSchemaConfig reference
283283

284-
| Property | Type | Default | Description |
285-
|:-------------------------|:----------|:--------|:-------------------------------------------------------------------------------|
286-
| `respectDefaultPresence` | `Boolean` | `false` | Mark fields with default values as optional (requires reflection). |
287-
| `requireNullableFields` | `Boolean` | `true` | Whether nullable fields appear in the `required` array. |
288-
| `useUnionTypes` | `Boolean` | `true` | Represent nullable types as `["string", "null"]` (Draft 2020-12). |
289-
| `useNullableField` | `Boolean` | `false` | Emit `"nullable": true` instead of union types (legacy OpenAPI compatibility). |
290-
| `includeDiscriminator` | `Boolean` | `false` | Include a `discriminator` object in polymorphic schemas (OpenAPI 3.x). |
284+
| Property | Type | Default | Description |
285+
|:-----------------------------------------|:----------|:--------|:-----------------------------------------------------------------------------------------------------------------------|
286+
| `respectDefaultPresence` | `Boolean` | `true` | Mark fields with default values as optional (requires reflection; KSP tracks presence but not values). |
287+
| `requireNullableFields` | `Boolean` | `false` | Include nullable fields in the `required` array. |
288+
| `useUnionTypes` | `Boolean` | `true` | Represent nullable types as `["string", "null"]` (Draft 2020-12). |
289+
| `useNullableField` | `Boolean` | `false` | Emit `"nullable": true` instead of union types (legacy OpenAPI compatibility). |
290+
| `includePolymorphicDiscriminator` | `Boolean` | `true` | Add a `"type"` property with a constant discriminator value to each polymorphic subtype schema. |
291+
| `includeOpenAPIPolymorphicDiscriminator` | `Boolean` | `false` | Include a `discriminator` mapping object in `oneOf` schemas (OpenAPI 3.x). Requires `includePolymorphicDiscriminator`. |
291292

292293
> [!NOTE]
293294
> `useUnionTypes` and `useNullableField` are mutually exclusive — exactly one must be `true`.
@@ -337,7 +338,9 @@ println(schemaString)
337338
-->
338339
<!--- KNIT example-knit-serializable-04.kt -->
339340

340-
The generated schema uses `oneOf` with a `$defs` section for each subtype, with a required `type` discriminator field on each subtype object.
341+
The generated schema uses `oneOf` with a `$defs` section for each subtype.
342+
Each subtype gets a required `type` property containing the subtype's serial name as a constant (from `@SerialName`),
343+
enabling runtime dispatch.
341344

342345
```json
343346
{
@@ -357,18 +360,27 @@ The generated schema uses `oneOf` with a `$defs` section for each subtype, with
357360
"com.example.Shape.Circle": {
358361
"type": "object",
359362
"properties": {
363+
"type": {
364+
"type": "string",
365+
"const": "com.example.Shape.Circle"
366+
},
360367
"radius": {
361368
"type": "number"
362369
}
363370
},
364371
"required": [
372+
"type",
365373
"radius"
366374
],
367375
"additionalProperties": false
368376
},
369377
"com.example.Shape.Rectangle": {
370378
"type": "object",
371379
"properties": {
380+
"type": {
381+
"type": "string",
382+
"const": "com.example.Shape.Rectangle"
383+
},
372384
"width": {
373385
"type": "number"
374386
},
@@ -377,6 +389,7 @@ The generated schema uses `oneOf` with a `$defs` section for each subtype, with
377389
}
378390
},
379391
"required": [
392+
"type",
380393
"width",
381394
"height"
382395
],

0 commit comments

Comments
 (0)