Skip to content

JsonInheritanceConverter<TBase> (STJ) still StackOverflows when the discriminator resolves to the base type itself (residual case after #1728) #1933

Description

@QzLP2P

Description

The System.Text.Json JsonInheritanceConverter<TBase> still throws an uncatchable StackOverflowException in a case that is not covered by the #1728 fix.

#1728 fixed GetObjectSubtype to return null for unknown discriminators, so a completely unknown discriminator now correctly throws InvalidOperationException. However, GetDiscriminatorType still has two branches that can return a type which re-triggers the same converter on the same document, causing infinite recursion:

if (objectType.Name == discriminatorValue)
    return objectType;                 // base type -> re-enters this converter

var typeName = objectType.Namespace + "." + discriminatorValue;
var subtype = ...Assembly.GetType(typeName);
if (subtype != null)
    return subtype;                    // may be a type carrying the same converter

Since the converter is registered on the base type via [JsonConverter], JsonSerializer.Deserialize(bytes, subtype, options) re-invokes Read with the same payload whenever subtype is the base type itself (or another type carrying the same converter) → infinite recursion → StackOverflowException (which cannot be caught by a try/catch, so the whole process crashes).

Repro

[JsonInheritanceConverter(typeof(Note), "@type")]
[JsonInheritanceAttribute("NoteDeployment", typeof(NoteDeployment))]
public partial class Note { }

public partial class NoteDeployment : Note { }

Payload where @type equals the base type's own name (e.g. a malformed producer):

{ "@type": "Note" }

Deserializing to Note:

Expected

A catchable exception (e.g. JsonException / InvalidOperationException), consistent with the unknown-discriminator case.

Suggested fix

In GetDiscriminatorType, never return a type equal to objectType (the type currently being converted): fall through to the final throw instead. As an additional safety net, a per-thread re-entrancy / recursion-depth guard in Read would turn any remaining infinite recursion into a catchable JsonException.

Versions

  • NJsonSchema 11.6.1 (via NSwag 14.7.1)
  • System.Text.Json, .NET 8
  • The JsonInheritanceConverter.liquid template on master still exhibits this behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions