Abhishek_Kumar
Abhishek_Kumar

Reputation: 51

Swashbuckle throws StackOverFlow exception when class have circular reference

I have a controller like below having circular reference in class B.

Error

This is happening because Swashbuckle's jsonserilalizer's setting is set to ReferenceLoopHandling = ReferenceLoopHandling.Error and I didn't find any way to override this setting.

I am using Swashbuckle 5.6.0 in an ASP.NET MVC application.

public class IssueController : ApiController
{
    [HttpGet]
    [Route("A")]
    public A Get(A input)
    {
        return new A();
    }
}

public class A
{
    public virtual B prop1 { get; set; }
}

public class B
{
    public virtual B Parent { get; set; }
}

Upvotes: 2

Views: 712

Answers (1)

Schneewind
Schneewind

Reputation: 119

In my case it turned out, that the Stackoverflow exception wasn't caused by the jsonserializer, but by the previous step (when the Swashbuckle schema gets created before json gets serialized). Circle References doesn't seem to work yet with Swashbuckle (in Swashbuckle for it seems to be fixed btw). To fix this, you have to copy the HandleFromUriParamsRecurseSave and add this (with the other filters) to the operations:

private static SwaggerDocument BuildSwaggerDocument()
        {
            ...
            var operationFilters = new List<IOperationFilter> {
                new HandleFromUriParamsRecurseSave(20),
                ...
            };
        }

In the copied HandleFromUriParamsRecurseSave just add a maxrecurselength property, which fits your case and you shouldn't have the StackOverflow error any more:

private void ExtractAndAddQueryParams(
      Schema sourceSchema,
      string sourceQualifier,
      bool? sourceRequired,
      SchemaRegistry schemaRegistry,
      ICollection<Parameter> operationParams)
    {
      foreach (var property in sourceSchema.properties)
      {
        ...
          var recurseCount = sourceQualifier.Count(t => t == '.');
          if (schema.@ref != null && recurseCount < _maxRecurseLength)
          {
            ExtractAndAddQueryParams(schemaRegistry.Definitions[[email protected]("#/definitions/", "")], sourceQualifier + ToCamelCase(property.Key) + ".", flag, schemaRegistry, operationParams);
          }
          else
          {
            ...
          }
        }
      }
    }

A further workaround I tried, but unfortunately didn't solve the issue, was to add a stack and everytime I detected a loop, added the right schema definition just one time (maybe someone sees the problem):

    private void ExtractAndAddQueryParams(
      Stack<string> sourceSchemaNames,
      Schema sourceSchema,
      string sourceQualifier,
      bool? sourceRequired,
      SchemaRegistry schemaRegistry,
      ICollection<Parameter> operationParams)
    {
      if (sourceSchemaNames.Count > _maxRecurseLength) {
        return;
      }
      foreach (var property in sourceSchema.properties)
      {
        var schema = property.Value;
        var readOnly = schema.readOnly;
        if (readOnly != true)
        {
          var flag = sourceRequired == true && sourceSchema.required != null && sourceSchema.required.Contains(property.Key);
          var recursionDetected = _disableRecursion && sourceSchemaNames.Contains(schema.@ref);
          if (schema.@ref != null && !recursionDetected)
          {
            sourceSchemaNames.Push(schema.@ref);
            ExtractAndAddQueryParams(sourceSchemaNames, schemaRegistry.Definitions[[email protected]("#/definitions/", "")], 
              sourceQualifier + ToCamelCase(property.Key) + ".", flag, schemaRegistry, 
              operationParams);
            sourceSchemaNames.Pop();
          }
          else
          {
...
            if (recursionDetected) {
              partialSchema.type = "object";
              partialSchema.@ref = schema.@ref;
            }
            operationParams.Add(partialSchema);
          }
        }
      }
    }
}

Upvotes: 2

Related Questions