Search code examples
asp.net-coreopenapi.net-9.0

nested IEnumerable<int> generates "invalid" OpenAPI json doc


We're using the new OpenAPI out of the box support from MS for ASP.NET Core API apps and we're facing some issues with the way the json gets generated. We have the following types:

public class MsgMovimentacaoLocaisTrabalho {

    public IList<InfoGeral>? LocaisRemover { get; set; }

    public InfoGeral? LocaisAssociar { get; set; }
}

public class InfoGeral {

    public Guid GuidDirecao { get; set; }

    public IEnumerable<int> Locais { get; set; } = Enumerable.Empty<int>();
}

MsgMovimentacaoLocaisTrabalho is used as a parameter type from one of the controller's methods:

public async Task<IActionResult> MovimentaLocaisTrabalhoAsync(
  [Description("Mensagem que ....")]MsgMovimentacaoLocaisTrabalho msg, 
  CancellationToken cancellationToken) {
  ...

The problem lies on the output of the types:

...
"MsgMovimentacaoLocaisTrabalho": {
        "type": "object",
        "properties": {
          "locaisRemover": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InfoGeral"
            },
            "nullable": true
          },
          "locaisAssociar": {
            "$ref": "#/components/schemas/InfoGeral2"
          }
        }
      },
"InfoGeral": {
        "type": "object",
        "properties": {
          "guidDirecao": {
            "type": "string",
            "format": "uuid"
          },
          "locais": {
            "type": "array",
            "items": {
              "type": "integer",
              "format": "int32"
            }
          }
        }
      },
....
 "InfoGeral2": {
        "type": "object",
        "properties": {
          "guidDirecao": {
            "type": "string",
            "format": "uuid"
          },
          "locais": {
            "$ref": "#/components/schemas/#/properties/locaisRemover/items/properties/locais"
          }
        },
        "nullable": true
      },

First, looking at the InfoGeral schema, it looks good (locais is represented as an array of int), but I'm not sure on why there's an InfoGeral2 type. I'm assuming that's because the type MsgMovimentacaoLocaisTrabalho has 2 properties which reference the type...

First question: is there a way to make the schema reuse InfoGeral for both properties?

Question 2: why do I end up getting a wrong value for locais instead of getting an array? Shoudn't IEnumerable end up generating an array on the schema?

btw, here's the error:

Semantic error at components.schemas.InfoGeral2.properties.locais.$ref
$ref values must be RFC3986-compliant percent-encoded URIs
Jump to line 11623

Solution

  • Check this issue: .NET 9 OpenAPI produces lots of duplicate schemas for the same object

    This bug has been fixed in .NET 10 and back-ported to .NET 9. You'll find it in the next servicing release for .NET 9.
    Why did this bug happen? The crux of the issue comes from the incompatibility between schemas generated by System.Text.Json, which comply strictly with the JSON Schema specification, and those expected by the OpenAPI.NET package which are a superset of JSON Schema. STJ uses relative references to capture recursive or duplicate type references. OpenAPI.NET does not recognize these as equivalent which results in duplicate schemas.
    In .NET 9, we fix this by introducing logic to our custom comparers to treat relative references as equivalent to any generated type.
    In .NET 10, we're upgrading to the new version of the OpenAPI.NET package which adds in built-in support for being able to resolve these relative references and detect "loop-backs" in the schema model.

    So, let's waiting for the next .NET servicing release ships.