{"record":{"id":"523f740bb9e5c688","repo":"elsa-workflows/elsa-core","slug":"workflow-json-type-alias-resolved-to-open-generic-type","errorCode":null,"errorMessage":"Workflow JSON type alias resolved to open generic type '{targetType}'.","messagePattern":"Workflow JSON type alias resolved to open generic type '(.+?)'\\.","errorType":"exception","errorClass":"JsonException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.Workflows.Core/Serialization/Converters/PolymorphicObjectConverter.cs","lineNumber":398,"sourceCode":"    private void WarnAboutUnaliasedType(Type type)\n    {\n        // Check the level before claiming the once-per-type slot: claiming it first would spend the type's single\n        // report on a call that logs nothing, and the type would then stay silent if the level is raised later.\n        if (_logger == null || !_logger.IsEnabled(LogLevel.Warning) || !ReportedUnaliasedTypes.TryAdd(type, 0))\n            return;\n\n        _logger.LogWarning(\n            \"Value of type {PayloadType} has no registered serialization alias, so it is stored without a type discriminator and will be read back as a property bag with camel-cased keys instead of as {PayloadType}. \" +\n            \"Register it during startup with AddTypeAlias<{PayloadTypeName}>(), or use a Dictionary<string, object> if a property bag is what you intend.\",\n            type,\n            type,\n            type.Name);\n    }\n\n    private static Type GetInstantiableTargetType(Type targetType)\n    {\n        if (targetType.ContainsGenericParameters)\n            throw new JsonException($\"Workflow JSON type alias resolved to open generic type '{targetType}'.\");\n\n        if (!targetType.IsInterface && !targetType.IsAbstract)\n            return targetType;\n\n        if (SerializationTypeResolver.TryGetInstantiableCollectionType(targetType, out var instantiableCollectionType))\n            return instantiableCollectionType;\n\n        throw new JsonException($\"Workflow JSON type alias resolved to non-instantiable type '{targetType}'.\");\n    }\n\n    private static object ReadPrimitive(ref Utf8JsonReader reader, JsonSerializerOptions options)\n    {\n        return (reader.TokenType switch\n        {\n            JsonTokenType.True => true,\n            JsonTokenType.False => false,\n            JsonTokenType.Number when reader.TryGetInt64(out var l) => l,\n            JsonTokenType.Number => reader.GetDouble(),","sourceCodeStart":380,"sourceCodeEnd":416,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.Workflows.Core/Serialization/Converters/PolymorphicObjectConverter.cs#L380-L416","documentation":"PolymorphicObjectConverter.GetInstantiableTargetType maps a JSON type alias to a concrete .NET type for instantiation. If the resolved type still contains open generic parameters (e.g. List<> or a generic interface), it cannot be constructed and this JsonException is thrown. It prevents attempts to create open generic instances.","triggerScenarios":"Deserializing JSON with a type alias ($type / type mapping) that resolves to an open generic type such as 'List&lt;&gt;' or 'Dictionary`2' without closed type arguments, via GetInstantiableTargetType during Read (src/modules/Elsa.Workflows.Core/Serialization/Converters/PolymorphicObjectConverter.cs:398).","commonSituations":"Hand-crafted type aliases in workflow JSON; custom SerializationTypeResolver mappings that map aliases to open generics; serialization of generic base types without concrete discriminators.","solutions":["Ensure the type alias in the JSON resolves to a closed generic (e.g. List`1[[MyNamespace.MyItem, MyAssembly]]).","Fix custom type-resolver alias registrations to map to closed, instantiable types.","Use concrete non-generic or closed-generic property types in the model.","If targeting interfaces, register an instantiable implementation in SerializationTypeResolver."],"exampleFix":"// before (open generic alias)\n\"$type\": \"System.Collections.Generic.List`1\"\n\n// after (closed generic)\n\"$type\": \"System.Collections.Generic.List`1[[Elsa.Workflows.Bookmark, Elsa.Workflows.Core]]\"","handlingStrategy":"validation","validationCode":"var t = SerializationTypeResolver.Resolve(alias); // or however aliases map\nif (t.ContainsGenericParameters) throw new InvalidOperationException($\"Type alias '{alias}' maps to open generic {t}\");","typeGuard":"static bool IsInstantiable(Type t) => !t.ContainsGenericParameters && !t.IsAbstract && !t.IsInterface;","tryCatchPattern":"try { var obj = JsonSerializer.Deserialize<object>(json, polymorphicOptions); } catch (JsonException ex) when (ex.Message.Contains(\"open generic type\")) { log.LogError(ex, \"Fix type alias to a closed generic\"); }","preventionTips":["Register aliases only for closed, instantiable types in your SerializationTypeResolver","Never hand-write $type/alias values for generic types without full closed-generic names","Prefer concrete or closed-generic property types in workflow models"],"tags":["csharp","json","serialization","generics","polymorphism"],"backgroundTag":"type-mismatch","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}