Skip to content

Commit 8a1734f

Browse files
authored
Merge pull request #392 from gregsdennis/schemadata/examples
add examples for schema data usage; actually use external data registry
2 parents 7b830f6 + 0ce8ff3 commit 8a1734f

File tree

8 files changed

+171
-3
lines changed

8 files changed

+171
-3
lines changed

JsonSchema.Data/DataKeyword.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ public class DataKeyword : IJsonSchemaKeyword, IEquatable<DataKeyword>
4545
/// This property stores full JSON documents retrievable by URI. If the desired
4646
/// value exists as a sub-value of a document, a JSON Pointer URI fragment identifier
4747
/// should be used in the `data` keyword do identify the exact value location.
48+
///
49+
/// This registry will be checked before attempting to fetch the data.
4850
/// </remarks>
49-
public static ConcurrentDictionary<Uri, JsonValue?> ExternalDataRegistry { get; } = new();
51+
public static ConcurrentDictionary<Uri, JsonNode> ExternalDataRegistry { get; } = new();
5052

5153

5254
/// <summary>

JsonSchema.Data/JsonSchema.Data.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
1717
<DocumentationFile>JsonSchema.Net.Data.xml</DocumentationFile>
1818
<LangVersion>latest</LangVersion>
19-
<Version>3.1.0-beta2</Version>
19+
<Version>3.1.0-beta3</Version>
2020
<FileVersion>3.1.0.0</FileVersion>
2121
<AssemblyVersion>3.0.0.0</AssemblyVersion>
2222
<IncludeSymbols>true</IncludeSymbols>

JsonSchema.Data/UriIdentifier.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ public bool TryResolve(EvaluationContext context, out JsonNode? value)
7474

7575
private static bool Download(Uri uri, out JsonNode? node)
7676
{
77+
if (DataKeyword.ExternalDataRegistry.TryGetValue(uri, out node))
78+
// protect against the off-hand that someone registered a null.
79+
return node != null;
80+
7781
if (DataKeyword.Fetch == null)
7882
{
7983
node = null;

json-everything.net/Pages/Schema.razor

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@
6161
<RadzenPanelMenuItem Text="Refiners" Value="@("examples/schemagen/refiner")"
6262
Path="@(_pageName + "#" + AnchorRegistry.GetFirstFragment("examples/schemagen/refiner" ))"/>
6363
</RadzenPanelMenuItem>
64+
<RadzenPanelMenuItem Text="Data Keyword" Expanded="false">
65+
<RadzenPanelMenuItem Text="Instance References" Value="@("examples/schemadata/data-ref")"
66+
Path="@(_pageName + "#" + AnchorRegistry.GetFirstFragment("examples/schemadata/data-ref"))"/>
67+
@* <RadzenPanelMenuItem Text="Schema References" Value="@("examples/schemadata/schema-ref")"
68+
Path="@(_pageName + "#" + AnchorRegistry.GetFirstFragment("examples/schemadata/schema-ref"))" />
69+
*@ <RadzenPanelMenuItem Text="Internal References" Value="@("examples/schemadata/external-ref")"
70+
Path="@(_pageName + "#" + AnchorRegistry.GetFirstFragment("examples/schemadata/external-ref"))"/>
71+
</RadzenPanelMenuItem>
6472
</RadzenPanelMenuItem>
6573
<RadzenPanelMenuItem Text="Release Notes" Icon="format_list_numbered" Expanded="true">
6674
<RadzenPanelMenuItem Text="JsonSchema.Net" Value="@("release-notes/json-schema")"
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
Most of the questions that center around referencing data involve comparing the values of properties. This typically comes in a few flavors:
2+
3+
- How do I require that property `A` is less than property `B`?
4+
- How do I require that property `A` is one of a list of things specified by property `B`?
5+
- How do I require that...
6+
7+
## Requiring that `A < B`
8+
9+
We want to specify a minimum for `B` that is the value of `A`. Unfortunately, JSON Schema doesn't have a mechanism that allows this; it only has `minimum` which must be a static (unchanging) value. Specifically, as enforced by the meta-schema, `minimum` must be a number.
10+
11+
```json
12+
{
13+
"type": "object",
14+
"properties": {
15+
"A": { "type": "number" },
16+
"B": {
17+
"type": "number",
18+
"minimum": ???
19+
}
20+
}
21+
}
22+
```
23+
24+
We can't put a number here because we want that value to depend on the value of `A`.
25+
26+
To solve this, we remove the `minimum` keyword and add the `data` keyword. Inside `data`, we specify `minimum` with a plain JSON Pointer (_not_ URI-encoded) that points to where in the instance we want to get the value; in this case, from the `A` property.
27+
28+
```json
29+
{
30+
"$schema": "https://json-everything.net/meta/data-2022",
31+
"type": "object",
32+
"properties": {
33+
"A": { "type": "number" },
34+
"B": {
35+
"type": "number",
36+
"data": {
37+
"minimum": "/A"
38+
}
39+
}
40+
}
41+
}
42+
```
43+
44+
| Passes | Fails |
45+
| :-: | :-: |
46+
| `{ "A": 5, "B": 10 }` | `{ "A": 15, "B": 10 }` |
47+
48+
## Requiring that `A` is one of the items in `B`
49+
50+
We want to specify an enum for `A`, and the values in that enum are contained in `B`. Similarly to the numbers problem above, JSON Schema only allows for explicit lists for `enum`, so we can't source it from somewhere else.
51+
52+
```json
53+
{
54+
"type": "object",
55+
"properties": {
56+
"A": { "enum": [ ??? ] },
57+
"B": {
58+
"type": "array",
59+
"items": { "type": "string" }
60+
}
61+
}
62+
}
63+
```
64+
65+
We know that `B` must be an array of strings, but how do we define that `A` has to be one of the strings defined in `B`?
66+
67+
Again, we need to define the keyword that needs the reference (`enum`) inside a `data` keyword and provide a plain JSON Pointer that points to where in the instance we want to get the values; in this case from the `B` property.
68+
69+
```json
70+
{
71+
"$schema": "https://json-everything.net/meta/data-2022",
72+
"type": "object",
73+
"properties": {
74+
"A": {
75+
"data": {
76+
"enum": "/B"
77+
}
78+
},
79+
"B": {
80+
"type": "array",
81+
"items": { "type": "string" }
82+
}
83+
}
84+
}
85+
```
86+
87+
| Passes | Fails |
88+
| :-: | :-: |
89+
| `{ "A": "cat", "B": [ "dog", "cat", "gerbil" ] }` | `{ "A": "giraffe", "B": [ "dog", "cat", "gerbil" ] }` |
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
Sometimes the values you want to use for schemas are stored in external files. To reference these, you'll need to use a URI. This URI may be combined with a pointer to indicate a location within the file.
2+
3+
## Requiring that `A` is one of the items in _values.json_.
4+
5+
We want to specify an enum for `A`, and the values in that enum are contained in a separate file, _values.json_. As with the instance data reference case, JSON Schema only allows for explicit lists for `enum`, so we can't source it from somewhere else.
6+
7+
```json
8+
{
9+
"type": "object",
10+
"properties": {
11+
"A": { "enum": [ ??? ] }
12+
}
13+
}
14+
```
15+
16+
_values.json_
17+
```json
18+
{
19+
"values": [ "dog", "cat", "gerbil" ]
20+
}
21+
```
22+
23+
How do we define that `A` has to be one of the strings defined in _values.json_?
24+
25+
We need to define the keyword that needs the reference (`enum`) inside a `data` keyword and provide a plain JSON Pointer that points to where in the instance we want to get the values; in this case from the _values.json_ file at `/values`.
26+
27+
```json
28+
{
29+
"$schema": "https://json-everything.net/meta/data-2022",
30+
"$id": "https://json-everything.net/example/external-ref",
31+
"type": "object",
32+
"properties": {
33+
"A": {
34+
"data": {
35+
"enum": "https://data.myserver.com/values.json#/values"
36+
}
37+
}
38+
}
39+
}
40+
```
41+
However, before we can use it, we need to tell the `data` keyword how to find `_values.json`. There are two ways to do this: registration and fetching.
42+
43+
Registration is the preferred method as it doesn't require that the implementation make any web calls. The `DataKeyword` class exposes a static `ExternalDataRegistry` property that can house any `JsonNode` data.
44+
45+
```c#
46+
var valuesText = File.ReadAllText("values.json");
47+
var values = JsonNode.Parse("valuesText");
48+
DataKeyword.ExternalDataRegistry["https://data.myserver.com/values.json"] = values;
49+
```
50+
51+
Fetching the data incurs some security risk as you're calling out to the internet. To set this up, you'll need to set the `DataKeyword.Fetch` static property to a method that will retrieve your data. The `DataKeyword.SimpleDownload()` method has been provided for convenience, but it is quite basic. It supports HTTP/S and `file` protocols. If you want something more robust or different (e.g. fetching from a database), you'll need to write that method yourself.
52+
53+
```c#
54+
DataKeyword.Fetch = DataKeyword.SimpleDownload;
55+
```
56+
57+
The system will always check the registry for a matching URI before attempting to fetch it.
58+
59+
Now we can run it:
60+
61+
| Passes | Fails |
62+
| :-: | :-: |
63+
| `{ "A": "cat" }` | `{ "A": "giraffe" }` |

json-everything.net/wwwroot/md/examples/schemadata/schema-ref.md

Whitespace-only changes.

json-everything.net/wwwroot/md/release-notes/json-schema-data.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
# 3.1.0 (beta 2) ([#326](https://github.com/gregsdennis/json-everything/pull/326) / [#389](https://github.com/gregsdennis/json-everything/pull/389))
1+
# 3.1.0 (beta 3) ([#326](https://github.com/gregsdennis/json-everything/pull/326) / [#389](https://github.com/gregsdennis/json-everything/pull/389) / [#392](https://github.com/gregsdennis/json-everything/pull/392))
22

33
Updated to use JsonSchema.Net v4.
44

55
Fixed deserialization bug.
66

7+
`DataKeyword.ExternalDataRegistry` is actually used. Previously, checking this registry was skipped (an oversight) before moving on to fetching the data. Also fixed the type of the property to store `JsonNode` instead of `JsonValue` to support the full range of JSON.
8+
79
Added `JsonSchemaExtensions.GetData()` to enable easy access to `data` keyword.
810

911
# [3.0.1](https://github.com/gregsdennis/json-everything/pull/316)

0 commit comments

Comments
 (0)