Skip to content

A null default is omitted since Microsoft.OpenApi 2x #2554

@C0nquistadore

Description

@C0nquistadore

Describe the bug
After upgrading from the Microsoft.OpenApi nuget package 1.6.28 to 2.0.0, having a nullable schema with a default of 'null' is no longer serialized.
This is somewhat related to #2510, but this particular issue targets guidance, on how to force writing default: null from scratch, instead of reading an existing yaml file.

Behavior in 1.6.28

OpenApiDocument document = new OpenApiDocument
{
    Info = new OpenApiInfo
    {
        Title = "Test",
        Version = "1.0.0"
    },
    Paths = new OpenApiPaths
    {
        ["/"] = new OpenApiPathItem
        {
            Operations =
            {
                [OperationType.Get] = new OpenApiOperation
                {
                    Parameters =
                    {
                        new OpenApiParameter
                        {
                            In = ParameterLocation.Query,
                            Name = "param1",
                            Schema = new OpenApiSchema
                            {
                                Type = "string",
                                Nullable = true,
                                Default = new OpenApiNull()
                            }
                        }
                    },
                    Responses = { ["204"] = new OpenApiResponse { Description = "No response" } }
                }
            }
        }
    }
};
string yaml = document.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0);

Generated YAML

openapi: 3.0.4
info:
  title: Test
  version: 1.0.0
paths:
  /:
    get:
      parameters:
        - name: param1
          in: query
          schema:
            type: string
            default: 
            nullable: true
      responses:
        '204':
          description: No response

Behavior in 2.0.0

OpenApiDocument document = new OpenApiDocument
{
    Info = new OpenApiInfo
    {
        Title = "Test",
        Version = "1.0.0"
    },
    Paths = new OpenApiPaths
    {
        ["/"] = new OpenApiPathItem
        {
            Operations = new Dictionary<HttpMethod, OpenApiOperation>
            {
                [HttpMethod.Get] = new OpenApiOperation
                {
                    Parameters =
                    [
                        new OpenApiParameter
                        {
                            In = ParameterLocation.Query,
                            Name = "param1",
                            Schema = new OpenApiSchema
                            {
                                Type = JsonSchemaType.String | JsonSchemaType.Null,
                                Default = JsonNode.Parse("\"null\"")
                            }
                        }
                    ],
                    Responses = new OpenApiResponses { ["204"] = new OpenApiResponse { Description = "No response" } }
                }
            }
        }
    }
};
string yaml = await document.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0);

Generated YAML

openapi: 3.0.4
info:
  title: Test
  version: 1.0.0
paths:
  /:
    get:
      parameters:
        - name: param1
          in: query
          schema:
            type: string
            nullable: true
      responses:
        '204':
          description: No response

Actual behavior
The generated YAML in 2.0.0 is missing the default: null.

Expected behavior
The generated YAML in 2.0.0 contains the default: null, as it was in previous versions.

Additional context
The major change from v1 to v2 here is that the OpenApiSchema.Default property no longer accepts an IOpenApiAny, which previously was set using an instance of OpenApiNull, but now expects an instance of JsonNode. The problem with that is, when trying to pass an instance of JsonNode with JsonValueKind.Null, you always end up with null instead of an existing instance. I tried JsonValue.Create<string>(null) and JsonNode.Parse("\"null\"").
Looking at the Microsoft.OpenApi source, the extension method WriteOptionalObject is used, which skips null values:

// default
writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d));

//...

public static void WriteOptionalObject<T>(
    this IOpenApiWriter writer,
    string name,
    T? value,
    Action<IOpenApiWriter, T> action)
{
    if (value != null)
    {
        if (value is IEnumerable values && !values.GetEnumerator().MoveNext())
        {
            return; // Don't render optional empty collections
        }

        writer.WriteRequiredObject(name, value, action);
    }
}

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions