microsoft/aspire · error · InvalidOperationException
Aspire.Hosting/Dict.toObject only supports string-key…
Error message
Aspire.Hosting/Dict.toObject only supports string-key dictionaries, but found key type '{key?.GetType().FullName ?? "null"}'. What it means
Dict.toObject in the Aspire.Hosting ATS exports only supports dictionaries whose keys are strings; GetStringKey casts the key via `key as string` and throws InvalidOperationException when a non-string (or null) key is encountered. This guard exists because the scripting layer cannot represent non-string dictionary keys.
Solutions
- Convert the dictionary to Dictionary<string, TValue> (e.g., with ToDictionary(k => k.Key.ToString()!, ...)) before calling toObject.
- If keys are enums, use the enum name or ToString as the key.
- If keys are complex objects, replace them with a string identifier property.
Example fix
// before Dict.toObject(myIntKeyedDict); // after Dict.toObject(myIntKeyedDict.ToDictionary(kv => kv.Key.ToString(), kv => kv.Value));
Defensive patterns
Strategy: type-guard
Validate before calling
if (dict is not IDictionary<string, object> stringKeyed)
{
throw new InvalidOperationException($"Dict.toObject requires string keys; got {dict?.GetType().FullName ?? "null"}.");
} Type guard
static bool HasStringKeys<TValue>(Dictionary<TKey, TValue> d) where TKey : notnull => typeof(TKey) == typeof(string);
Try / catch
try
{
Dict.toObject(dict);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("only supports string-key"))
{
// fall back to projecting keys with ToString()
} Prevention
- Prefer Dictionary<string, T> at model boundaries that feed the ATS exports.
- Convert int/Guid/enum keys to strings when building dictionaries destined for scripting.
When it happens
Trigger: Calling Dict.toObject on an IDictionary<TKey,TValue> where keys are not strings — e.g., Dictionary<int, ...>, Dictionary<Guid, ...>, Dictionary<MyEnum, ...> — or a dictionary containing a null key.
Common situations: Passing a numerically-keyed dictionary (config lookups keyed by int, cache keyed by Guid or enum) into the ATS Dict export; localization or lookup maps keyed by enum values.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- A of type cannot be assigned to a BicepValue< >.
- aspire: returned unexpected type %T
- aspire: dict getter returned unexpected type %T
- aspire: dict.toObject: unexpected result type %T
- aspire: list getter returned unexpected type %T
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/b255d042fd57e762.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Hosting/Ats/CollectionExports.cs:202
private static Type? GetDictionaryKeyType(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>))
{
return type.GetGenericArguments()[0];
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
return type.GetGenericArguments()[0];
}
return type.GetInterfaces()
.FirstOrDefault(static iface => iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(IDictionary<,>))
?.GetGenericArguments()[0];
}
private static string GetStringKey(object? key)
=> key as string ?? throw new InvalidOperationException($"Aspire.Hosting/Dict.toObject only supports string-key dictionaries, but found key type '{key?.GetType().FullName ?? "null"}'.");
#endregion
#region List Operations
/// <summary>
/// Gets an item from a list by index.
/// </summary>
/// <param name="list">The list handle.</param>
/// <param name="index">The zero-based index.</param>
/// <returns>The item at the specified index.</returns>
[AspireExport("List.get")]
public static object? ListGet(this IList list, int index)
=> index >= 0 && index < list.Count ? list[index] : null;
/// <summary>
/// Sets an item in a list at a specific index.
/// </summary>View on GitHub (pinned to 25830f84bd)