LuckyPennySoftware/AutoMapper · error · AutoMapperMappingException
Multiple matching keys were found in the source dictionary f
Error message
Multiple matching keys were found in the source dictionary for destination member {member}. What it means
When mapping from an IDictionary<string,object> source onto a typed destination, FromStringDictionaryMapper.MatchSource matches each destination member name against dictionary keys. After an exact-key miss it falls back to comparing s.Key.Trim() == name; if two or more surviving keys collide on the same member, the mapping is ambiguous and throws AutoMapperMappingException. The match is whitespace-trim based, not case-insensitive.
Source
Thrown at src/AutoMapper/Mappers/FromStringDictionaryMapper.cs:24
private static readonly MethodInfo MapDynamicMethod = typeof(FromStringDictionaryMapper).GetStaticMethod(nameof(MapDynamic));
public bool IsMatch(TypePair context) => typeof(StringDictionary).IsAssignableFrom(context.SourceType);
public Expression MapExpression(IGlobalConfiguration configuration, ProfileMap profileMap, MemberMap memberMap,
Expression sourceExpression, Expression destExpression) =>
Call(MapDynamicMethod, sourceExpression, destExpression.ToObject(), Constant(destExpression.Type), ContextParameter, Constant(profileMap));
private static object MapDynamic(StringDictionary source, object boxedDestination, Type destinationType, ResolutionContext context, ProfileMap profileMap)
{
boxedDestination ??= ObjectFactory.CreateInstance(destinationType);
int matchedCount = 0;
foreach (var member in profileMap.CreateTypeDetails(destinationType).WriteAccessors)
{
var (value, count) = MatchSource(member.Name);
if (count == 0)
{
continue;
}
if (count > 1)
{
throw new AutoMapperMappingException($"Multiple matching keys were found in the source dictionary for destination member {member}.", null, new TypePair(typeof(StringDictionary), destinationType));
}
var mappedValue = context.MapMember(member, value, boxedDestination);
member.SetMemberValue(boxedDestination, mappedValue);
matchedCount++;
}
if (matchedCount < source.Count)
{
MapInnerProperties();
}
return boxedDestination;
(object Value, int Count) MatchSource(string name)
{
if (source.TryGetValue(name, out var value))
{
return (value, 1);
}
var matches = source.Where(s => s.Key.Trim() == name).Select(s => s.Value).ToArray();
if (matches.Length == 1)View on GitHub (pinned to dfa6dd587c)
Solutions
- Normalize the source dictionary before mapping: trim every key and remove or merge collisions so each member resolves uniquely.
- Supply exact (untrimmed) keys that equal destination member names so the fast path (count==1) is taken and the trim fallback is never reached.
- Use ConvertUsing / a custom value resolver to pick the intended value deterministically.
- Rename one of the colliding keys at the source or drop the unwanted duplicate.
Example fix
// before
var dict = new Dictionary<string,object>{ ["Name"]="a", [" Name "]="b" };
var dest = mapper.Map<Dest>(dict); // ambiguous for Name
// after
var dict = new Dictionary<string,object>(StringComparer.Ordinal);
foreach (var kv in rawDict) dict[kv.Key.Trim()] = kv.Value; // de-dup/trim first
var dest = mapper.Map<Dest>(dict); Defensive patterns
Strategy: validation
Validate before calling
var collisions = source
.GroupBy(kv => kv.Key.Trim(), StringComparer.Ordinal)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
if (collisions.Count > 0)
throw new ArgumentException($"Ambiguous source keys after trim: {string.Join(", ", collisions)}");
var dest = mapper.Map<Dest>(source); Try / catch
try { dest = mapper.Map<Dest>(source); }
catch (AutoMapperMappingException ex) when (ex.Message.Contains("Multiple matching keys"))
{
// log the failing member, normalize/dedup the dictionary, then retry
} Prevention
- Normalize and trim dictionary keys at the boundary where the dictionary enters your code.
- Use a case-sensitive ordinal comparer and disallow duplicate keys upstream.
- Add a destination member name exact-match key so the trim fallback is never exercised.
When it happens
Trigger: Source dictionary contains multiple keys that are equal after .Trim() and there is no exact match for the destination member name, e.g. {"Name": x, " Name ": y} mapping to a Name property. Also two keys like "Id " and " Id" both trimming to "Id" with no exact "Id" entry.
Common situations: Dirty/expired dictionary payloads from CSV/JSON import; manually built dictionaries with accidental leading/trailing whitespace; dedup logic that merged values but left duplicate-ish keys; integration with a source system that pads keys.
AI-assisted analysis of LuckyPennySoftware/AutoMapper@dfa6dd587c (2026-08-13).
Data as JSON: /api/errors/0e821693b672c609.
Report an issue: GitHub.