1

I have a class MyClass containing a property int[,] MyProperty.

When generating a json schema with new JSchemaGenerator().Generate(typeof(MyClass)), the schema contains

"MyProperty": {
  "type": "array",
  "items": {
    "type": "integer"
  }
}

whereas I expect the schema to contain an array of arrays (i.e., a multidimensional array) as follows

"MyProperty": {
  "type": "array",
  "items": {
    "type": [
      "array",
      "null"
    ],
    "items": {
      "type": "integer"
    }
  }
}

Note that JSchemaGenerator does generate the desired output for jagged arrays T[][].

  • Why does the schema generation differ for jagged vs multidimensional arrays?
  • Is there an attribute I can place on top of my multidimensional array property to correct the schema generation?
Blake
  • 986
  • 9
  • 24
  • 2
    This sounds like an issue to raise with the maintainer of the library you're using. – gregsdennis Apr 23 '21 at 22:05
  • What if you generate the schema with what you would expect vs what you have listed above? – Zonus Apr 26 '21 at 22:20
  • I don't believe [tag:jsonschema] supports 2d rectangular arrays, see [How to define a two-dimensional rectangular array in JSON Schema?](https://stackoverflow.com/q/66384925/3744182). However you could automatically generate a schema for a jagged 2d array for a 2d multidimensional array, see https://dotnetfiddle.net/pcjxfG. Would that meet your needs? – dbc Apr 27 '21 at 01:23
  • @Zonus That's actually how I generated the above snippets – Blake Apr 29 '21 at 00:36

1 Answers1

2

I don't believe the JSON Schema standard supports a "rectangular array" constraint, see How to define a two-dimensional rectangular array in JSON Schema? for confirmation. The fact that Json.NET schema generates a 1d array schema for a 2d array seems like a pure bug.

As a workaround, using a custom JSchemaGenerationProvider, you could generate an N-dimensional jagged array schema for an N-dimensional multidimensional array. Such a schema would not capture the constraint that the rows all have the same length, but it would at least capture the correct array nesting depth.

First, define the following JSchemaGenerationProvider:

public class MultidimensionalArraySchemaProvider : JSchemaGenerationProvider
{
    public override JSchema GetSchema(JSchemaTypeGenerationContext context)
    {
        if (CanGenerateSchema(context))
        {
            // Create a jagged N-d array type.
            var type = context.ObjectType.GetElementType().MakeArrayType();
            for (int i = context.ObjectType.GetArrayRank(); i > 1; i--)
                // Disallow null array items for outer arrays
                // context.Generator.DefaultRequired controls whether null is allowed for innermost array items.
                type = typeof(ArrayRow<>).MakeGenericType(type);
            // Return a schema for the jagged N-d array type.
            return context.Generator.Generate(type);
        }
        else
            throw new NotImplementedException();
    }
    
    public override bool CanGenerateSchema(JSchemaTypeGenerationContext context) => 
        context.ObjectType.IsArray && context.ObjectType.GetArrayRank() > 1;
}

[JsonArray(AllowNullItems = false)]
class ArrayRow<T> : List<T> { }

Then, if your class looks like:

public class MyClass 
{
    public int[,] MyProperty { get; set; }

    public string[,,,] MyProperty4D { get; set; }
}

You can do:

var generator = new JSchemaGenerator();
generator.GenerationProviders.Add(new MultidimensionalArraySchemaProvider());
var schema = generator.Generate(typeof(MyClass));           

With the result:

{
  "type": "object",
  "properties": {
    "MyProperty": {
      "type": "array",
      "items": {
        "type": "array",
        "items": {
          "type": "integer"
        }
      }
    },
    "MyProperty4D": {
      "type": "array",
      "items": {
        "type": "array",
        "items": {
          "type": "array",
          "items": {
            "type": "array",
            "items": {
              "type": [
                "string",
                "null"
              ]
            }
          }
        }
      }
    }
  },
  "required": [
    "MyProperty",
    "MyProperty4D"
  ]
}

Demo fiddle here.

Related questions:

dbc
  • 104,963
  • 20
  • 228
  • 340