question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

OpenApiProperty Nullable does not seem to work for classes

See original GitHub issue

Overview

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:open
  • Created 2 years ago
  • Reactions:3
  • Comments:10 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
flcdrgcommented, Sep 22, 2021

@justinyoo Something I’m encountering which seems similar. In the code sample above, is it possible to make OptionalChild nullable?

        [OpenApiProperty(Nullable = true)]
        public ChildModel? OptionalChild { get; set; }

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 the ChildModel definition. Not clear how to do that with the attributes though.

0reactions
Joost1982commented, Jan 27, 2023

@justinyoo Something I’m encountering which seems similar. In the code sample above, is it possible to make OptionalChild nullable?

        [OpenApiProperty(Nullable = true)]
        public ChildModel? OptionalChild { get; set; }

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).

I think have the same problem. I have a class with a Dictionary property, which I annotated with the Nullable OpenApiProperty attribute:

    [JsonPropertyName("data")]
    [OpenApiProperty(Nullable = true, Description = "optional data")] 
    public Dictionary<string, object>? Data { get; set; }

But the generated yaml (v3) file shows this:

        data:
          type: object
          additionalProperties:
            type: object
          description: optional data

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:

        data:
          type: object
          additionalProperties:
            type: object
          description: optional data
          nullable: true

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!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Nullable strings are not marked nullable in openapi json
When I use a nullable property, they get marked as "nullable": true in the json output, ... But it seems this is not...
Read more >
How to define a property that can be string or null in ...
OpenAPI 2.0​​ OAS2 does not support 'null' as the data type, so you are out of luck. You can only use type: string...
Read more >
Using Optional and Nullable Properties in API Requests
OpenAPI supports values of data types to be null. To specify, one can use the “nullable: true” property while defining a data type...
Read more >
Differentiating between undefined and null - tapir
It seems to work fine, but I can't get the OpenAPI specification right. In OpenAPI 3.0.3 there's "nullable": true as well.
Read more >
Describing Parameters
In OpenAPI 3.0, parameters are defined in the parameters section of an operation or path. To describe a parameter, you specify its name...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found