Skip to content

Commit bc3a2e4

Browse files
committed
update schemagen docs for v5
1 parent da6e852 commit bc3a2e4

File tree

1 file changed

+47
-46
lines changed

1 file changed

+47
-46
lines changed

_docs/schema/schemagen/schema-generation.md

+47-46
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ var schema = schemaBuilder.FromType<MyType>().Build();
1616

1717
Done.
1818

19-
> The validating converter described in this document requires AOT-incompatible reflection to operate, so it will not be usable in a Native AOT context.
19+
> The validating converter described in this document requires AOT-incompatible reflection to operate, so it may cause errors in a Native AOT context.
2020
{: .prompt-warning}
2121

2222
## IMPORTANT {#schema-schemagen-disclaimer}
@@ -79,15 +79,15 @@ All of these and more are supplied via a set of attributes that can be applied t
7979

8080
Simply add the attributes directly to the properties and the corresponding keywords will be added to the schema.
8181

82-
For properties typed with generic collections, like `List<T>`, the schema will automatically generate an `items` keyword and generate a schema for the indicated `T`. If your `T` is a numeric value or a string, then you can also apply the relevant attributes and they'll be applied in the `items` subschema.
82+
For properties typed with generic collections, like `List<T>`, the schema will automatically generate an `items` keyword and generate a schema for the indicated `T`. To specify that attributes should be applied to a generic parameter, use the `GenericParameter` property, and identify which parameter is to be attributed use a zero-based numeric index.
8383

8484
For example, this object:
8585

8686
```c#
8787
class MyClass
8888
{
8989
[UniqueItems(true)]
90-
[Minimum(10)]
90+
[Minimum(10, GenericParameter = 0)]
9191
public List<int> MyList{ get; set; }
9292
}
9393
```
@@ -110,11 +110,6 @@ will be translated to this schema:
110110
}
111111
```
112112

113-
The `minimum` is applied to the `items` because that keyword is not relevant for an array.
114-
115-
> This means that the generator will have trouble determining where to apply keywords to properties like `List<List<T>>` because the attributes could be relevant for both the outer and inner lists.
116-
{: .prompt-info }
117-
118113
The generator also supports these .Net-defined attributes:
119114

120115
- `JsonPropertyName` - supports custom property naming (more on naming below)
@@ -253,7 +248,11 @@ Those familiar with .Net validation will recognize that having `[Required]` on y
253248
254249
To this end, the `[Required]` attribute will only be represented in generated schemas in the `required` keyword.
255250
256-
However, for nullable types, it may or may not be appropriate to include `null` in the `type` keyword. JsonSchema.Net.Generation controls this behavior via the `SchemaGeneratorConfiguration.Nullability` option with individual properties being overrideable via the `[Nullable(bool)]` attribute.
251+
As of v5 of this library, nullability follows the code as closely as possible, using the `[Nullable]` attribute as an override. If a property is declared as nullable (either value or reference type), it will be generated as such. Applying `[Nullable(false)]` to a nullable property will disable this behavior, while applying `[Nullable(true)]` (or just `[Nullable]`) to a non-nullable property will force nullability in the schema.
252+
253+
#### Prior to v5
254+
255+
For nullable types, it may or may not be appropriate to include `null` in the `type` keyword. JsonSchema.Net.Generation controls this behavior via the `SchemaGeneratorConfiguration.Nullability` option with individual properties being overrideable via the `[Nullable(bool)]` attribute.
257256
258257
There are four options:
259258
@@ -262,7 +261,7 @@ There are four options:
262261
- `AllowForReferenceTypes` - This will add `null` to the `type` keyword for reference types unless `[Nullable(false)]` is used.
263262
- `AllowForAllTypes` - This is a combination of the previous two and will add `null` to the type keyword for any type unless `[Nullable(false)]` is used.
264263
265-
> This library [cannot detect](https://stackoverflow.com/a/62186551/878701) whether the consuming code has nullable reference types enabled. Therefore all reference types are considered nullable.
264+
> This library was unable to [detect](https://stackoverflow.com/a/62186551/878701) whether the consuming code has nullable reference types enabled. Therefore all reference types are considered nullable.
266265
{: .prompt-info }
267266
268267
> The library makes a distinction between nullable value types and reference types because value types must be explicitly nullable. This differs from reference types which are implicitly nullable, and there's not a way (via the type itself) to make a reference type non-nullable.
@@ -308,9 +307,38 @@ There are a couple advanced features that bear mentioning.
308307

309308
First, the system does have some loop detection logic in order to support self-referencing or loop-referencing types.
310309

311-
Secondly, the system will collect common subschemas into a `$defs` keyword at the root. Identification of a subschema is by its type and the collection of attributes it is processed with. The locations with these common subschemas will be replaced by a `$ref` that points to the appropriate entry in `$defs`.
310+
Secondly, the system will collect type subschemas into a `$defs` keyword at the root. The locations with these common subschemas will be replaced by a `$ref` that points to the appropriate entry in `$defs`. When a definition is only referenced in one location, that definition will be re-integrated into the reference location. For example, instead of
311+
312+
```json
313+
{
314+
"type": "object",
315+
"properties": {
316+
"foo": { "$ref": "#/$defs/listOfString" }
317+
},
318+
"$defs": {
319+
"listOfString": {
320+
"type": "array",
321+
"items": { "type": "string" }
322+
}
323+
}
324+
}
325+
```
326+
327+
you'll get
328+
329+
```json
330+
{
331+
"type": "object",
332+
"properties": {
333+
"foo": {
334+
"type": "array",
335+
"items": { "type": "string" }
336+
}
337+
}
338+
}
339+
```
312340

313-
Generating a properly descriptive-while-terse name is hard. This library makes a fair attempt at it, generating names like `myType` for `MyType` and `arrayOfInteger` for `int[]` or `List<int>`. If this proves insufficient for your needs, implement your own naming as an `ITypeNameGenerator` and assign it to `SchemaGenerationContextOptimizer.TypeNameGenerator`.
341+
Generating a properly descriptive-while-terse name is generally hard. This library makes a fair attempt at it, generating names like `myType` for `MyType` and `arrayOfInteger` for `int[]` or `List<int>`. If this proves insufficient for your needs, implement your own naming as an `ITypeNameGenerator` and assign it to `SchemaGeneratorConfiguration.TypeNameGenerator`.
314342

315343
> If you only want to handle specific types in your generator and are happy with the library's generation for others, simply return null from your generator and the library's generation will be used.
316344
{: .prompt-tip }
@@ -407,40 +435,6 @@ public class TypeIntent : ISchemaKeywordIntent
407435

408436
See? The `Apply()` method just takes the builder, and adds a keyword with the data that it already collected. Pretty easy.
409437

410-
> In v1.x of the library, implementing the equality methods (`Equals()` and `GetHashCode()`) was required. As of v2.0, this is unnecessary.
411-
{: .prompt-info }
412-
413-
This will work for most intents, but some keywords contain subschemas. For these, we don't want to hold a subschema because, as mentioned before, they can't be edited. Instead, we'll hold a context object that represents the subschema: its type, attribute set, and the intents required to build it. For these intents, we *also* want to implement `IContextContainer`. Here's the `ItemsIntent`:
414-
415-
```c#
416-
public class ItemsIntent : ISchemaKeywordIntent, IContextContainer
417-
{
418-
public SchemaGeneratorContextBase Context { get; private set; }
419-
420-
public ItemsIntent(SchemaGeneratorContextBase context)
421-
{
422-
Context = context;
423-
}
424-
425-
public void Replace(int hashCode, SchemaGeneratorContextBase newContext)
426-
{
427-
if (Context.Hash == hashCode)
428-
Context = newContext;
429-
}
430-
431-
public void Apply(JsonSchemaBuilder builder) => builder.Items(Context.Apply());
432-
}
433-
```
434-
435-
As of v3, `IContextContainer` requires only a single method: `Replace()`.
436-
437-
> Another method `GetContexts()` was used in v1.x but was no longer used as of v2.0 and was marked obsolete with v2.0.1.
438-
{: .prompt-info }
439-
440-
`Replace()` replaces a context with a given hash code with a new context. This is called when the system is creating `$ref` intents that point to the new `$defs` intent it's building and distributing them throughout the context tree. Once all the `$ref`s are distributed, the system will add the `$defs` intent to the root context to be applied at the last step.
441-
442-
Generally intents for applicator keywords, which are keywords that have subschemas (`anyOf`, `allOf`, etc.), will need to implement this second interface. In most cases, you can just copy this code.
443-
444438
### Attributes {#schema-schemagen-attributes}
445439

446440
The other source for intents are attributes. These are handled once the generator has completed adding the intents it needs to.
@@ -483,6 +477,13 @@ The occasion may arise where you want to handle an attribute that's defined in s
483477
> The confusing bit is that these also have a `params` overload that appears to just take `ISchemaKeywordIntent[]`. However, it works the same as the non-`params` overload in that each array represents a subschema.
484478
{: .prompt-warning }
485479

480+
#### Generic parameter support
481+
482+
In order for an attribute to be applicable to a generic parameter (as mentioned [above](#schema-schemagen-best-practices)), your attribute will need to implement the `INestableAttribute` interface, which adds a `GenericParameter` property.
483+
484+
> This property MUST default to -1. The index is zero-based, so a value of -1 indicates the root type.
485+
{: .prompt-warning }
486+
486487
### Refiners {#schema-schemagen-refiners}
487488

488489
Sometimes you may need to make minor adjustments to the generated schemas dynamically. For this you'll need to create an implementation of `ISchemaRefiner`.

0 commit comments

Comments
 (0)