OrchardCMS/OrchardCore · error · JsonException

Unexpected token type

Error message

Unexpected token type '{reader.TokenType}' when deserializing '{typeof(T)}'.

What it means

ResilientPolymorphicJsonConverter.Read expects the payload to be a JSON object (StartObject) so it can inspect the type-discriminator property and pick the concrete subtype. When reader.TokenType is anything else — an array, string, number, or null at the value position — it throws a JsonException describing the mismatch between the actual token and the expected object shape. The 'resilient' aspect handles unknown discriminators, but the fundamental envelope must still be an object.

Solutions

  1. Validate the payload's top-level token is StartObject before deserializing (JsonDocument.Parse and check RootElement.ValueKind == JsonValueKind.Object).
  2. If the payload is an array of items, deserialize to a wrapper type or T[] and let each element go through the converter individually.
  3. Check for schema changes in the producing side and restore the object shape including the type-discriminator property.
  4. Ensure a null value is not being fed to the converter in a context where it must be an object.
  5. If you own the model, add a discriminated wrapper so the converter always receives { "$type": ..., ... } objects.

Example fix

// before
var item = JsonSerializer.Deserialize<MyPolymorphicBase>(json, options); // json = "[{...}]"

// after
using var doc = JsonDocument.Parse(json);
if (doc.RootElement.ValueKind == JsonValueKind.Array)
{
    var items = JsonSerializer.Deserialize<MyPolymorphicBase[]>(json, options);
}
else
{
    var item = JsonSerializer.Deserialize<MyPolymorphicBase>(json, options);
}
Defensive patterns

Strategy: validation

Validate before calling

using var doc = JsonDocument.Parse(json);
if (doc.RootElement.ValueKind != JsonValueKind.Object)
    throw new InvalidDataException($"Polymorphic payload must be an object, got {doc.RootElement.ValueKind}");
if (!doc.RootElement.TryGetProperty(ResilientPolymorphicJsonConverterFactory.TypeDiscriminatorPropertyName, out _))
    throw new InvalidDataException("Polymorphic payload missing type discriminator property.");

Type guard

static bool IsPolymorphicObject(JsonElement e, string discriminator) =>
    e.ValueKind == JsonValueKind.Object && e.TryGetProperty(discriminator, out _);

Try / catch

try
{
    var item = JsonSerializer.Deserialize<MyPolymorphicBase>(json, options);
}
catch (JsonException ex) when (ex.Message.Contains("Unexpected token type"))
{
    // payload shape drifted; inspect and route
    using var doc = JsonDocument.Parse(json);
    if (doc.RootElement.ValueKind == JsonValueKind.Array)
        items = JsonSerializer.Deserialize<MyPolymorphicBase[]>(json, options);
    else
        logger.LogWarning("Polymorphic payload was {Kind}, expected Object", doc.RootElement.ValueKind);
}

Prevention

When it happens

Trigger: Deserializing a polymorphic type via ResilientPolymorphicJsonConverter<T> when the JSON value at that position is not an object: e.g. the payload contains a bare array, a scalar, a null (when null-check was bypassed), or a previously serialized value was changed from an object to an array.

Common situations: Schema drift: an API or stored document now returns an array of items where an object was expected; calling Deserialize<T> on a JSON fragment that is a list of polymorphic items instead of a single item; misconfigured serializer writing the discriminator wrapper incorrectly so the converter sees the inner value directly.

Understand the failure class

Related errors


AI-assisted analysis of OrchardCMS/OrchardCore@4306c0717f (2026-09-13). Data as JSON: /api/errors/2e2f997ab4fa981c. Report an issue: GitHub.

Appendix: source

Thrown at src/OrchardCore/OrchardCore.Abstractions/Json/Serialization/ResilientPolymorphicJsonConverter.cs:89

        Dictionary<string, Type> discriminatorToType,
        Dictionary<Type, string> typeToDiscriminator,
        Type fallbackType)
    {
        _discriminatorToType = discriminatorToType;
        _typeToDiscriminator = typeToDiscriminator;
        _fallbackType = fallbackType;
    }

    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.Null)
        {
            return null;
        }

        if (reader.TokenType != JsonTokenType.StartObject)
        {
            throw new JsonException($"Unexpected token type '{reader.TokenType}' when deserializing '{typeof(T)}'.");
        }

        using var doc = JsonDocument.ParseValue(ref reader);
        var root = doc.RootElement;

        if (!root.TryGetProperty(ResilientPolymorphicJsonConverterFactory.TypeDiscriminatorPropertyName, out var typeProp))
        {
            return DeserializeAsFallback(root, null);
        }

        var discriminator = typeProp.GetString();

        if (discriminator is null || !_discriminatorToType.TryGetValue(discriminator, out var derivedType))
        {
            return DeserializeAsFallback(root, discriminator);
        }

        return (T)root.Deserialize(derivedType, options);

View on GitHub (pinned to 4306c0717f)