Skip to content

Values of additionalProperties not transformed #59616

Closed
@JTeeuwissen

Description

@JTeeuwissen

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

When an endpoint returns a type containing a Dictionary containing another type, the schema of this last type is not processed by schema transformers (see the missing "a": "b" in Bar2 below, causing a new schema to be created).

{
  "schemas": {
    "Bar": {
      "type": "object",
      "properties": {
        "value": {
          "type": "integer",
          "format": "int32",
          "a": "b"
        }
      },
      "a": "b"
    },
    "Bar2": {
      "type": "object",
      "properties": {
        "value": {
          "type": "integer",
          "format": "int32"
        }
      }
    },
    "Foo": {
      "type": "object",
      "properties": {
        "bar": {
          "$ref": "#/components/schemas/Bar"
        },
        "bars": {
          "type": "object",
          "additionalProperties": {
            "$ref": "#/components/schemas/Bar2"
          },
          "a": "b"
        }
      },
      "a": "b"
    }
  }
}

My guess is that additionalProperties should be processed somewhere here:

private async Task InnerApplySchemaTransformersAsync(OpenApiSchema schema,
JsonTypeInfo jsonTypeInfo,
JsonPropertyInfo? jsonPropertyInfo,
OpenApiSchemaTransformerContext context,
IOpenApiSchemaTransformer transformer,
CancellationToken cancellationToken = default)
{
context.UpdateJsonTypeInfo(jsonTypeInfo, jsonPropertyInfo);
await transformer.TransformAsync(schema, context, cancellationToken);
// Only apply transformers on polymorphic schemas where we can resolve the derived
// types associated with the base type.
if (schema.AnyOf is { Count: > 0 } && jsonTypeInfo.PolymorphismOptions is not null)
{
var anyOfIndex = 0;
foreach (var derivedType in jsonTypeInfo.PolymorphismOptions.DerivedTypes)
{
var derivedJsonTypeInfo = _jsonSerializerOptions.GetTypeInfo(derivedType.DerivedType);
if (schema.AnyOf.Count <= anyOfIndex)
{
break;
}
await InnerApplySchemaTransformersAsync(schema.AnyOf[anyOfIndex], derivedJsonTypeInfo, null, context, transformer, cancellationToken);
anyOfIndex++;
}
}
if (schema.Items is not null)
{
var elementTypeInfo = _jsonSerializerOptions.GetTypeInfo(jsonTypeInfo.ElementType!);
await InnerApplySchemaTransformersAsync(schema.Items, elementTypeInfo, null, context, transformer, cancellationToken);
}
if (schema.Properties is { Count: > 0 })
{
foreach (var propertyInfo in jsonTypeInfo.Properties)
{
if (schema.Properties.TryGetValue(propertyInfo.Name, out var propertySchema))
{
await InnerApplySchemaTransformersAsync(propertySchema, _jsonSerializerOptions.GetTypeInfo(propertyInfo.PropertyType), propertyInfo, context, transformer, cancellationToken);
}
}
}
}

Expected Behavior

I expected to see

{
  "schemas": {
    "Bar": {
      "type": "object",
      "properties": {
        "value": {
          "type": "integer",
          "format": "int32",
          "a": "b"
        }
      },
      "a": "b"
    },
    "Foo": {
      "type": "object",
      "properties": {
        "bar": {
          "$ref": "#/components/schemas/Bar"
        },
        "bars": {
          "type": "object",
          "additionalProperties": {
            "$ref": "#/components/schemas/Bar"
          },
          "a": "b"
        }
      },
      "a": "b"
    }
  }
}

Steps To Reproduce

Add a schema transformer like so:

options.AddSchemaTransformer((schema, _, _)
	=>
{
	schema.Extensions.Add("a", new OpenApiString("b"));
	return Task.CompletedTask;
});

And add an endpoint that returns a type (Foo) which contains a dictionary with another type (Bar).

app.MapGet("/", () => new Foo());

class Foo
{
	public Bar Bar { get; set; }
	public Dictionary<string, Bar> Bars { get; set; }
}

class Bar
{
	public int Value { get; set; }
}

Exceptions (if any)

No response

.NET Version

9.0.200-preview.0.24575.35

Anything else?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-minimalIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etcfeature-openapihelp wantedUp for grabs. We would accept a PR to help resolve this issue

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions