diff --git a/src/MessagePack.GeneratorCore/CodeAnalysis/TypeCollector.cs b/src/MessagePack.GeneratorCore/CodeAnalysis/TypeCollector.cs index eaf9ef1f6..5f991d514 100644 --- a/src/MessagePack.GeneratorCore/CodeAnalysis/TypeCollector.cs +++ b/src/MessagePack.GeneratorCore/CodeAnalysis/TypeCollector.cs @@ -635,7 +635,14 @@ private ObjectSerializationInfo GetObjectInfo(INamedTypeSymbol type) } var customFormatterAttr = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.MessagePackFormatterAttribute))?.ConstructorArguments[0].Value as INamedTypeSymbol; - var member = new MemberSerializationInfo(true, isWritable, isReadable, hiddenIntKey++, item.Name, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), customFormatterAttr?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + var keyAttribute = item.GetAttributes() + .FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute)); + + var stringKey = keyAttribute?.ConstructorArguments.Length > 0 + ? keyAttribute.ConstructorArguments[0].Value as string ?? item.Name + : item.Name; + + var member = new MemberSerializationInfo(true, isWritable, isReadable, hiddenIntKey++, stringKey, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), customFormatterAttr?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); stringMembers.Add(member.StringKey, member); this.CollectCore(item.Type); // recursive collect @@ -661,7 +668,14 @@ private ObjectSerializationInfo GetObjectInfo(INamedTypeSymbol type) } var customFormatterAttr = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.MessagePackFormatterAttribute))?.ConstructorArguments[0].Value as INamedTypeSymbol; - var member = new MemberSerializationInfo(false, isWritable, isReadable, hiddenIntKey++, item.Name, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), customFormatterAttr?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + var keyAttribute = item.GetAttributes() + .FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute)); + + var stringKey = keyAttribute?.ConstructorArguments.Length > 0 + ? keyAttribute.ConstructorArguments[0].Value as string ?? item.Name + : item.Name; + + var member = new MemberSerializationInfo(false, isWritable, isReadable, hiddenIntKey++, stringKey, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), customFormatterAttr?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); stringMembers.Add(member.StringKey, member); this.CollectCore(item.Type); // recursive collect } diff --git a/tests/MessagePack.Generator.Tests/GenerateStringKeyedFormatterTest.cs b/tests/MessagePack.Generator.Tests/GenerateStringKeyedFormatterTest.cs index 1719fb5fa..2815b8636 100644 --- a/tests/MessagePack.Generator.Tests/GenerateStringKeyedFormatterTest.cs +++ b/tests/MessagePack.Generator.Tests/GenerateStringKeyedFormatterTest.cs @@ -863,5 +863,67 @@ await compiler.GenerateFileAsync( ((string)result.B).Should().Be("foobar"); // default value }); } + + [Fact] + public async Task StringKeyAttributeMapModeTest() + { + using var tempWorkarea = TemporaryProjectWorkarea.Create(); + var contents = @" +using System; +using System.Collections.Generic; +using MessagePack; + +namespace TempProject +{ + [MessagePackObject] + public class MyMessagePackObject + { + [Key(""a"")] + public int A { get; set; } + [Key(""b"")] + public string B { get; set; } + } +} + "; + tempWorkarea.AddFileToTargetProject("MyMessagePackObject.cs", contents); + + var compiler = new MessagePackCompiler.CodeGenerator(testOutputHelper.WriteLine, CancellationToken.None); + await compiler.GenerateFileAsync( + tempWorkarea.GetOutputCompilation().Compilation, + tempWorkarea.OutputDirectory, + "TempProjectResolver", + "TempProject.Generated", + true, + string.Empty, + Array.Empty()); + + var compilation = tempWorkarea.GetOutputCompilation(); + compilation.Compilation.GetDiagnostics().Should().NotContain(x => x.Severity == DiagnosticSeverity.Error); + + // Run tests with the generated resolver/formatter assembly. + compilation.ExecuteWithGeneratedAssembly((ctx, assembly) => + { + var mpoType = assembly.GetType("TempProject.MyMessagePackObject"); + var options = MessagePackSerializerOptions.Standard + .WithResolver(CompositeResolver.Create( + StandardResolver.Instance, + TestUtilities.GetResolverInstance(assembly, "TempProject.Generated.Resolvers.TempProjectResolver"))); + + // Build `{ "a": -1, "b": "foo" }` + var seq = new Sequence(); + var writer = new MessagePackWriter(seq); + writer.WriteMapHeader(2); + writer.Write("a"); + writer.Write(-1); + writer.Write("b"); + writer.Write("foo"); + writer.Flush(); + + // Verify deserialization + dynamic result = MessagePackSerializer.Deserialize(mpoType, seq, options); + ((int)result.A).Should().Be(-1); // from ctor + ((string)result.B).Should().Be("foo"); // default value + }); + } } }