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
- Validate the payload's top-level token is StartObject before deserializing (JsonDocument.Parse and check RootElement.ValueKind == JsonValueKind.Object).
- If the payload is an array of items, deserialize to a wrapper type or T[] and let each element go through the converter individually.
- Check for schema changes in the producing side and restore the object shape including the type-discriminator property.
- Ensure a null value is not being fed to the converter in a context where it must be an object.
- 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
- Contract-test payloads from producers so object/discriminator shape changes are caught early.
- Always include the type-discriminator property in serialized polymorphic values.
- Wrap polymorphic collections in an object envelope rather than a bare array.
- Pin serializer settings on both producer and consumer sides.
- Validate ValueKind at trust boundaries before deserializing.
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Unexpected token parsing TimeSpan. Expected a string, got
- Unknown token type
- Deserializing a is not supported.
- Unable to convert ' ' to TimeSpan.
- Cannot convert to
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)