OpenApiProperty Nullable does not seem to work for classes
See original GitHub issueOverview
I have an existing Function using the OpenAPI library that I was able to apply Nullable to for some string properties. However other properties such as classes do not generate the expected nullable section in swagger.json
While attempting to create a reproducible, I’m unable to see any nullable/required generated in the swagger for OpenAPI V2 (default), and it doesn’t work with classes for OpenAPI V3.
Reproducible
Here is a simple Function.
csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AzureFunctionsVersion>v3</AzureFunctionsVersion>
<Nullable>Enable</Nullable>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.OpenApi" Version="0.8.1-preview" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.13" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
</Project>
Function1.cs
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
namespace FunctionApp2
{
public class ChildModel
{
[OpenApiProperty(Nullable = false, Description = "required!")]
public string RequiredData { get; set; } = string.Empty;
[OpenApiProperty(Nullable = true, Description = "optional!")]
public string? OptionalData { get; set; }
}
public class FooModel
{
[OpenApiProperty(Nullable = false)]
public ChildModel RequiredChild { get; set; } = new ChildModel();
[OpenApiProperty(Nullable = true)]
public ChildModel? OptionalChild { get; set; }
[OpenApiProperty(Nullable = false, Description = "required!")]
public string RequiredData { get; set; } = string.Empty;
[OpenApiProperty(Nullable = true, Description = "optional!")]
public string? OptionalData { get; set; }
}
public class OpenApiConfigurationOptions : IOpenApiConfigurationOptions
{
public OpenApiInfo Info { get; set; } = new OpenApiInfo
{
Title = "Test API",
Version = "1.0"
};
public List<OpenApiServer> Servers { get; set; } = new();
public OpenApiVersionType OpenApiVersion { get; set; } = OpenApiVersionType.V3;
public bool IncludeRequestingHostName { get; set; } = false;
}
public class FooFunction
{
[FunctionName("foo")]
[OpenApiOperation("foo")]
[OpenApiResponseWithBody(HttpStatusCode.OK, "application/json", typeof(FooModel))]
public async Task<ActionResult<FooModel>> GetFoo(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
ILogger log)
{
var model = new FooModel();
return new OkObjectResult(model);
}
}
}
swagger.json
{
"openapi": "3.0.1",
"info": {
"title": "Test API",
"version": "1.0"
},
"servers": [
{
"url": "http://localhost:7071/api"
}
],
"paths": {
"/foo": {
"get": {
"operationId": "foo",
"responses": {
"200": {
"description": "Payload of FooModel",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/fooModel"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"childModel": {
"type": "object",
"properties": {
"requiredData": {
"type": "string",
"description": "required!"
},
"optionalData": {
"type": "string",
"description": "optional!",
"nullable": true
}
}
},
"fooModel": {
"type": "object",
"properties": {
"requiredChild": {
"$ref": "#/components/schemas/childModel"
},
"optionalChild": {
"$ref": "#/components/schemas/childModel"
},
"requiredData": {
"type": "string",
"description": "required!"
},
"optionalData": {
"type": "string",
"description": "optional!",
"nullable": true
}
}
}
}
}
}
Generated FooModel using NSwag.
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v12.0.0.0)")]
public partial class FooModel
{
[Newtonsoft.Json.JsonProperty("requiredChild", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public ChildModel? RequiredChild { get; set; }= default!;
[Newtonsoft.Json.JsonProperty("optionalChild", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public ChildModel? OptionalChild { get; set; }= default!;
/// <summary>required!</summary>
[Newtonsoft.Json.JsonProperty("requiredData", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string? RequiredData { get; set; }= default!;
/// <summary>optional!</summary>
[Newtonsoft.Json.JsonProperty("optionalData", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string? OptionalData { get; set; }= default!;
private System.Collections.Generic.IDictionary<string, object> _additionalProperties = new System.Collections.Generic.Dictionary<string, object>();
[Newtonsoft.Json.JsonExtensionData]
public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
{
get { return _additionalProperties; }
set { _additionalProperties = value; }
}
}
Expected
I would expect “nullable”: true in the “components.schemas.fooModel.optionalChild” node. And the generated code to have the same Required value as the “optionalData” property.
In the docs it’s not clear that this will not do anything unless V3 is specified, or if this is intentional or not.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:3
- Comments:10 (3 by maintainers)
Top GitHub Comments
@justinyoo Something I’m encountering which seems similar. In the code sample above, is it possible to make
OptionalChild
nullable?Doesn’t seem to have any effect. And the code samples above show NSWag is generating code that forbids null (so the property is not optional).
If I manually edited the generated yaml, I can add a
nullable: true
under theChildModel
definition. Not clear how to do that with the attributes though.I think have the same problem. I have a class with a Dictionary property, which I annotated with the Nullable OpenApiProperty attribute:
But the generated yaml (v3) file shows this:
So the description part works, but not the part that should make it nullable (which does work for string and int properties). I expected something like:
I can add the missing part manually, but I have a lot of objects with these kind of properties…
Ah I see there is already a PR that fixes this! https://github.com/Azure/azure-functions-openapi-extension/pull/504
– update –
@justinyoo just released a new version (1.5.1) and this version indeed produces the expected result. Very nice!