diff --git a/src/Nerdbank.MessagePack/Converters/CommonRecords.cs b/src/Nerdbank.MessagePack/Converters/CommonRecords.cs index b526fb13..2dbb22b2 100644 --- a/src/Nerdbank.MessagePack/Converters/CommonRecords.cs +++ b/src/Nerdbank.MessagePack/Converters/CommonRecords.cs @@ -111,7 +111,8 @@ internal record struct PropertyAccessors( /// The data type whose constructor is to be visited. /// Serializable properties on the data type. /// Deserializable properties on the data type. -internal record MapConstructorVisitorInputs(MapSerializableProperties Serializers, MapDeserializableProperties Deserializers); +/// A collection of constructor parameters, with any conflicting names removed. +internal record MapConstructorVisitorInputs(MapSerializableProperties Serializers, MapDeserializableProperties Deserializers, Dictionary ParametersByName); /// /// Encapsulates the data passed through state arguments diff --git a/src/Nerdbank.MessagePack/StandardVisitor.cs b/src/Nerdbank.MessagePack/StandardVisitor.cs index 588df951..c3be80b9 100644 --- a/src/Nerdbank.MessagePack/StandardVisitor.cs +++ b/src/Nerdbank.MessagePack/StandardVisitor.cs @@ -80,8 +80,23 @@ internal StandardVisitor(ConverterCache owner, TypeGenerationContext context) IConstructorShape? ctorShape = objectShape.Constructor; - Dictionary? ctorParametersByName = - ctorShape?.Parameters.ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase); + Dictionary? ctorParametersByName = null; + if (ctorShape is not null) + { + ctorParametersByName = new(StringComparer.Ordinal); + foreach (IConstructorParameterShape ctorParameter in ctorShape.Parameters) + { + // Keep the one with the Kind that we prefer. + if (ctorParameter.Kind == ConstructorParameterKind.ConstructorParameter) + { + ctorParametersByName[ctorParameter.Name] = ctorParameter; + } + else if (!ctorParametersByName.ContainsKey(ctorParameter.Name)) + { + ctorParametersByName.Add(ctorParameter.Name, ctorParameter); + } + } + } List>? serializable = null; List>? deserializable = null; @@ -147,9 +162,9 @@ internal StandardVisitor(ConverterCache owner, TypeGenerationContext context) MapSerializableProperties serializableMap = new(serializable?.ToArray()); MapDeserializableProperties deserializableMap = new(propertyReaders); - MapConstructorVisitorInputs inputs = new(serializableMap, deserializableMap); if (ctorShape is not null) { + MapConstructorVisitorInputs inputs = new(serializableMap, deserializableMap, ctorParametersByName!); converter = (MessagePackConverter)ctorShape.Accept(this, inputs)!; } else @@ -279,7 +294,7 @@ internal StandardVisitor(ConverterCache owner, TypeGenerationContext context) List> propertySerializers = inputs.Serializers.Properties.Span.ToList(); - SpanDictionary> parameters = constructorShape.Parameters + SpanDictionary> parameters = inputs.ParametersByName.Values .SelectMany Deserialize)>(p => { var prop = (DeserializableProperty)p.Accept(this)!; @@ -316,7 +331,7 @@ internal StandardVisitor(ConverterCache owner, TypeGenerationContext context) return new ObjectArrayConverter(inputs.GetJustAccessors(), constructorShape.GetDefaultConstructor(), !this.owner.SerializeDefaultValues); } - Dictionary propertyIndexesByName = new(StringComparer.OrdinalIgnoreCase); + Dictionary propertyIndexesByName = new(StringComparer.Ordinal); for (int i = 0; i < inputs.Properties.Count; i++) { if (inputs.Properties[i] is { } property) diff --git a/test/Nerdbank.MessagePack.Tests/MessagePackSerializerTests.cs b/test/Nerdbank.MessagePack.Tests/MessagePackSerializerTests.cs index 3dc89db9..c805a5fc 100644 --- a/test/Nerdbank.MessagePack.Tests/MessagePackSerializerTests.cs +++ b/test/Nerdbank.MessagePack.Tests/MessagePackSerializerTests.cs @@ -294,6 +294,26 @@ public void SerializeObject_DeserializeObject() Assert.Equal(value, deserialized); } + [Fact] + public void CtorParameterNameMatchesSerializedInsteadOfDeclaredName_Roundtrips() + { + this.AssertRoundtrip(new TypeWithConstructorParameterMatchingSerializedPropertyName(2)); + } + + [Fact] + public void CtorParameterNameMatchesSerializedInsteadOfDeclaredName_DefaultValueWorks() + { + Sequence seq = new(); + MessagePackWriter writer = new(seq); + writer.WriteMapHeader(0); + writer.Flush(); + + TypeWithConstructorParameterMatchingSerializedPropertyName? deserialized = + this.Serializer.Deserialize(seq, TestContext.Current.CancellationToken); + Assert.NotNull(deserialized); + Assert.Equal(8, deserialized.Marshaled); + } + /// /// Carefully writes a msgpack-encoded array of bytes. /// @@ -496,6 +516,16 @@ internal partial record RecordWithReadOnlyPropertiesKeyed([property: Key(0)] int public int Sum => this.A + this.B; } + [GenerateShape] + internal partial record TypeWithConstructorParameterMatchingSerializedPropertyName + { + public TypeWithConstructorParameterMatchingSerializedPropertyName(int otherName = 8) + => this.Marshaled = otherName; + + [PropertyShape(Name = "otherName")] + public int Marshaled { get; set; } + } + [GenerateShape] [GenerateShape] [GenerateShape]