{"record":{"id":"2e2f997ab4fa981c","repo":"OrchardCMS/OrchardCore","slug":"unexpected-token-type-reader-tokentype-when-deserializing","errorCode":null,"errorMessage":"Unexpected token type '{reader.TokenType}' when deserializing '{typeof(T)}'.","messagePattern":"Unexpected token type '(.+?)' when deserializing '(.+?)'\\.","errorType":"exception","errorClass":"JsonException","httpStatus":null,"severity":"error","filePath":"src/OrchardCore/OrchardCore.Abstractions/Json/Serialization/ResilientPolymorphicJsonConverter.cs","lineNumber":89,"sourceCode":"        Dictionary<string, Type> discriminatorToType,\n        Dictionary<Type, string> typeToDiscriminator,\n        Type fallbackType)\n    {\n        _discriminatorToType = discriminatorToType;\n        _typeToDiscriminator = typeToDiscriminator;\n        _fallbackType = fallbackType;\n    }\n\n    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)\n    {\n        if (reader.TokenType == JsonTokenType.Null)\n        {\n            return null;\n        }\n\n        if (reader.TokenType != JsonTokenType.StartObject)\n        {\n            throw new JsonException($\"Unexpected token type '{reader.TokenType}' when deserializing '{typeof(T)}'.\");\n        }\n\n        using var doc = JsonDocument.ParseValue(ref reader);\n        var root = doc.RootElement;\n\n        if (!root.TryGetProperty(ResilientPolymorphicJsonConverterFactory.TypeDiscriminatorPropertyName, out var typeProp))\n        {\n            return DeserializeAsFallback(root, null);\n        }\n\n        var discriminator = typeProp.GetString();\n\n        if (discriminator is null || !_discriminatorToType.TryGetValue(discriminator, out var derivedType))\n        {\n            return DeserializeAsFallback(root, discriminator);\n        }\n\n        return (T)root.Deserialize(derivedType, options);","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/OrchardCMS/OrchardCore/blob/4306c0717fe573f6fca1b4955909ddab6a192807/src/OrchardCore/OrchardCore.Abstractions/Json/Serialization/ResilientPolymorphicJsonConverter.cs#L71-L107","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nvar item = JsonSerializer.Deserialize<MyPolymorphicBase>(json, options); // json = \"[{...}]\"\n\n// after\nusing var doc = JsonDocument.Parse(json);\nif (doc.RootElement.ValueKind == JsonValueKind.Array)\n{\n    var items = JsonSerializer.Deserialize<MyPolymorphicBase[]>(json, options);\n}\nelse\n{\n    var item = JsonSerializer.Deserialize<MyPolymorphicBase>(json, options);\n}","handlingStrategy":"validation","validationCode":"using var doc = JsonDocument.Parse(json);\nif (doc.RootElement.ValueKind != JsonValueKind.Object)\n    throw new InvalidDataException($\"Polymorphic payload must be an object, got {doc.RootElement.ValueKind}\");\nif (!doc.RootElement.TryGetProperty(ResilientPolymorphicJsonConverterFactory.TypeDiscriminatorPropertyName, out _))\n    throw new InvalidDataException(\"Polymorphic payload missing type discriminator property.\");","typeGuard":"static bool IsPolymorphicObject(JsonElement e, string discriminator) =>\n    e.ValueKind == JsonValueKind.Object && e.TryGetProperty(discriminator, out _);","tryCatchPattern":"try\n{\n    var item = JsonSerializer.Deserialize<MyPolymorphicBase>(json, options);\n}\ncatch (JsonException ex) when (ex.Message.Contains(\"Unexpected token type\"))\n{\n    // payload shape drifted; inspect and route\n    using var doc = JsonDocument.Parse(json);\n    if (doc.RootElement.ValueKind == JsonValueKind.Array)\n        items = JsonSerializer.Deserialize<MyPolymorphicBase[]>(json, options);\n    else\n        logger.LogWarning(\"Polymorphic payload was {Kind}, expected Object\", doc.RootElement.ValueKind);\n}","preventionTips":["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."],"tags":["json","deserialization","polymorphism","type-mismatch","system-text-json"],"backgroundTag":"unexpected-response-shape","analyzedSha":"4306c0717fe573f6fca1b4955909ddab6a192807","analyzedAt":"2026-09-13T17:41:05.024Z","contentChangedAt":"2026-09-13T17:41:05.024Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}