Json Schema Patterns in .NET - Working with tensors
In this series we are cataloging common patterns with JSON Schema and the .NET code generated by Corvus.JsonSchema.
It is especially useful for developers who want to move to a schema-first approach, but are unsure how that will map to their .NET code.
We are focusing on draft 2020-12 and the dotnet8.0 code generation, but very similar patterns apply for older versions of both .NET and draft 2019-09, draft 7, and even draft 6. We will highlight the key differences as we go.
If you have no experience of JSON Schema at all, I would recommend you read the getting started step-by-step documentation provided by the JSON Schema team.
Defining numeric arrays of fixed size
We have seen how to create arrays of higher rank with fixed size.
The array could contain any item type that you can define in schema. However, our previous example used a numeric type: we constrained it using the type number
and the format double
.
Here's another example, this time of rank 3.
File: tensor-rank-3.json
{
"title": "A 4x4x4 tensor of JsonDouble",
"type": "array",
"items": { "$ref": "#/$defs/SecondRank" },
"minItems": 4,
"maxItems": 4,
"$defs": {
"SecondRank": {
"type": "array",
"items": { "$ref": "#/$defs/ThirdRank" },
"minItems": 4,
"maxItems": 4,
},
"ThirdRank": {
"type": "array",
"minItems": 4,
"maxItems": 4,
"items": { "type": "number", "format": "double" }
}
}
}
We can use this in the same way, and also convert it directly to- and from- Span<TNumeric>
for use in APIs such as System.Numerics.Tensors.
Generate the code:
generatejsonschematypes --outputPath Model --rootNamespace JsonSchemaSample.Api tensor-rank-3.json
Generating: TensorRank3
Generating: ThirdRank
Generating: SecondRank
using JsonSchemaSample.Api;
// Initialize an array of rank 3 using collection initialization expressions
TensorRank3 tensor =
[
[
[1.3, 1.4, 9.2, 8.4],
[2.4, 3.2, 6.3, 7.2],
[9.4, 6.2, 4.8, 1.4],
[4.4, 9.4, -3.2, 6.5],
],
[
[1.3, 1.4, 9.2, 8.4],
[2.4, 3.2, 6.3, 7.2],
[9.4, 6.2, 4.8, 1.4],
[4.4, 9.4, -3.2, 6.5],
],
[
[1.3, 1.4, 9.2, 8.4],
[2.4, 3.2, 6.3, 7.2],
[9.4, 6.2, 4.8, 1.4],
[4.4, 9.4, -3.2, 6.5],
],
[
[1.3, 1.4, 9.2, 8.4],
[2.4, 3.2, 6.3, 7.2],
[9.4, 6.2, 4.8, 1.4],
[4.4, 9.4, -3.2, 6.5],
]
];
// Access an element of a array of rank 3.
double tensorValue = tensor[3][0][1];
// Set the item at the given indices in the array of rank 3
TensorRank3 updatedTensor = tensor.SetItem(3, 0, 1, 3.4);
// Fill a Span<double> with the tensor values.
Span<double> tensorAsSpan = stackalloc double[TensorRank3.ValueBufferSize];
if (updatedTensor.TryGetNumericValues(tensorAsSpan, out int writtenTensor))
{
bool first = true;
// Successfully get the tensor as a flat Span<double>
foreach (double spanValue in tensorAsSpan)
{
if (first)
{
first = false;
}
else
{
Console.Write(", ");
}
Console.Write(spanValue);
}
Console.WriteLine();
}
else
{
Console.WriteLine("Unable to get values.");
}
// Construct a tensor from a ReadOnlySpan<double>
TensorRank3 fromSpan = TensorRank3.FromValues(tensorAsSpan);
// Find the rank of each array (note that the sub arrays of diminishing rank)
Console.WriteLine($"Rank: {TensorRank3.Rank}, {TensorRank3.SecondRank.Rank}, {TensorRank3.ThirdRank.Rank}");
// Find the dimension (extent) of each particular rank of the array
Console.WriteLine($"Dimension: {TensorRank3.Dimension}, {TensorRank3.SecondRank.Dimension}, {TensorRank3.ThirdRank.Dimension}");