diff --git a/csharp/protos/unittest_issues.proto b/csharp/protos/unittest_issues.proto index 1b5562f5c8..8d93d8970a 100644 --- a/csharp/protos/unittest_issues.proto +++ b/csharp/protos/unittest_issues.proto @@ -218,3 +218,9 @@ message DisambiguateCommonMembers { int32 on_construction = 11; int32 parser = 12; } + +message Issue11987Message { + int32 a = 1 [json_name = 'b']; + int32 b = 2 [json_name = 'a']; + int32 c = 3 [json_name = 'd']; +} diff --git a/csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssues.pb.cs b/csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssues.pb.cs index 82e935e126..0009ece321 100644 --- a/csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssues.pb.cs +++ b/csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssues.pb.cs @@ -63,11 +63,12 @@ namespace UnitTest.Issues.TestProtos { "EQoJdG9fc3RyaW5nGAUgASgFEhUKDWdldF9oYXNoX2NvZGUYBiABKAUSEAoI", "d3JpdGVfdG8YByABKAUSDQoFY2xvbmUYCCABKAUSFgoOY2FsY3VsYXRlX3Np", "emUYCSABKAUSEgoKbWVyZ2VfZnJvbRgKIAEoBRIXCg9vbl9jb25zdHJ1Y3Rp", - "b24YCyABKAUSDgoGcGFyc2VyGAwgASgFKlUKDE5lZ2F0aXZlRW51bRIWChJO", - "RUdBVElWRV9FTlVNX1pFUk8QABIWCglGaXZlQmVsb3cQ+///////////ARIV", - "CghNaW51c09uZRD///////////8BKjYKDkRlcHJlY2F0ZWRFbnVtEhcKD0RF", - "UFJFQ0FURURfWkVSTxAAGgIIARIHCgNvbmUQARoCGAFCHaoCGlVuaXRUZXN0", - "Lklzc3Vlcy5UZXN0UHJvdG9zYgZwcm90bzM=")); + "b24YCyABKAUSDgoGcGFyc2VyGAwgASgFIj0KEUlzc3VlMTE5ODdNZXNzYWdl", + "EgwKAWEYASABKAVSAWISDAoBYhgCIAEoBVIBYRIMCgFjGAMgASgFUgFkKlUK", + "DE5lZ2F0aXZlRW51bRIWChJORUdBVElWRV9FTlVNX1pFUk8QABIWCglGaXZl", + "QmVsb3cQ+///////////ARIVCghNaW51c09uZRD///////////8BKjYKDkRl", + "cHJlY2F0ZWRFbnVtEhcKD0RFUFJFQ0FURURfWkVSTxAAGgIIARIHCgNvbmUQ", + "ARoCGAFCHaoCGlVuaXRUZXN0Lklzc3Vlcy5UZXN0UHJvdG9zYgZwcm90bzM=")); descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, new pbr::FileDescriptor[] { global::Google.Protobuf.WellKnownTypes.StructReflection.Descriptor, }, new pbr::GeneratedClrTypeInfo(new[] {typeof(global::UnitTest.Issues.TestProtos.NegativeEnum), typeof(global::UnitTest.Issues.TestProtos.DeprecatedEnum), }, null, new pbr::GeneratedClrTypeInfo[] { @@ -85,7 +86,8 @@ namespace UnitTest.Issues.TestProtos { new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.MixedRegularAndOptional), global::UnitTest.Issues.TestProtos.MixedRegularAndOptional.Parser, new[]{ "RegularField", "OptionalField" }, new[]{ "OptionalField" }, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.OneofWithNoneField), global::UnitTest.Issues.TestProtos.OneofWithNoneField.Parser, new[]{ "X", "None" }, new[]{ "Test" }, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.OneofWithNoneName), global::UnitTest.Issues.TestProtos.OneofWithNoneName.Parser, new[]{ "X", "Y" }, new[]{ "None" }, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.DisambiguateCommonMembers), global::UnitTest.Issues.TestProtos.DisambiguateCommonMembers.Parser, new[]{ "DisambiguateCommonMembers_", "Types_", "Descriptor_", "Equals_", "ToString_", "GetHashCode_", "WriteTo_", "Clone_", "CalculateSize_", "MergeFrom_", "OnConstruction_", "Parser_" }, null, null, null, null) + new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.DisambiguateCommonMembers), global::UnitTest.Issues.TestProtos.DisambiguateCommonMembers.Parser, new[]{ "DisambiguateCommonMembers_", "Types_", "Descriptor_", "Equals_", "ToString_", "GetHashCode_", "WriteTo_", "Clone_", "CalculateSize_", "MergeFrom_", "OnConstruction_", "Parser_" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.Issue11987Message), global::UnitTest.Issues.TestProtos.Issue11987Message.Parser, new[]{ "A", "B", "C" }, null, null, null, null) })); } #endregion @@ -5113,6 +5115,269 @@ namespace UnitTest.Issues.TestProtos { } + public sealed partial class Issue11987Message : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Issue11987Message()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::UnitTest.Issues.TestProtos.UnittestIssuesReflection.Descriptor.MessageTypes[15]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public Issue11987Message() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public Issue11987Message(Issue11987Message other) : this() { + a_ = other.a_; + b_ = other.b_; + c_ = other.c_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public Issue11987Message Clone() { + return new Issue11987Message(this); + } + + /// Field number for the "a" field. + public const int AFieldNumber = 1; + private int a_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int A { + get { return a_; } + set { + a_ = value; + } + } + + /// Field number for the "b" field. + public const int BFieldNumber = 2; + private int b_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int B { + get { return b_; } + set { + b_ = value; + } + } + + /// Field number for the "c" field. + public const int CFieldNumber = 3; + private int c_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int C { + get { return c_; } + set { + c_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as Issue11987Message); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(Issue11987Message other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (A != other.A) return false; + if (B != other.B) return false; + if (C != other.C) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (A != 0) hash ^= A.GetHashCode(); + if (B != 0) hash ^= B.GetHashCode(); + if (C != 0) hash ^= C.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (A != 0) { + output.WriteRawTag(8); + output.WriteInt32(A); + } + if (B != 0) { + output.WriteRawTag(16); + output.WriteInt32(B); + } + if (C != 0) { + output.WriteRawTag(24); + output.WriteInt32(C); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (A != 0) { + output.WriteRawTag(8); + output.WriteInt32(A); + } + if (B != 0) { + output.WriteRawTag(16); + output.WriteInt32(B); + } + if (C != 0) { + output.WriteRawTag(24); + output.WriteInt32(C); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (A != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(A); + } + if (B != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(B); + } + if (C != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(C); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(Issue11987Message other) { + if (other == null) { + return; + } + if (other.A != 0) { + A = other.A; + } + if (other.B != 0) { + B = other.B; + } + if (other.C != 0) { + C = other.C; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + A = input.ReadInt32(); + break; + } + case 16: { + B = input.ReadInt32(); + break; + } + case 24: { + C = input.ReadInt32(); + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 8: { + A = input.ReadInt32(); + break; + } + case 16: { + B = input.ReadInt32(); + break; + } + case 24: { + C = input.ReadInt32(); + break; + } + } + } + } + #endif + + } + #endregion } diff --git a/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs b/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs index f4dfde2435..963faedd4f 100644 --- a/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs +++ b/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs @@ -630,6 +630,18 @@ namespace Google.Protobuf Assert.AreEqual(expectedJson, JsonFormatter.Default.Format(populated)); } + // See See https://github.com/protocolbuffers/protobuf/issues/11987 + [Test] + public void JsonNamePriority() + { + // This tests both the formatter and the parser, but the issue was when parsing. + var original = new Issue11987Message { A = 10, B = 20, C = 30 }; + var json = JsonFormatter.Default.Format(original); + AssertJson("{ 'b': 10, 'a': 20, 'd': 30 }", json); + var parsed = Issue11987Message.Parser.ParseJson(json); + Assert.AreEqual(original, parsed); + } + // Sanity tests for WriteValue. Not particularly comprehensive, as it's all covered above already, // as FormatMessage uses WriteValue. diff --git a/csharp/src/Google.Protobuf.Test/testprotos.pb b/csharp/src/Google.Protobuf.Test/testprotos.pb index d399cd7572..56576578fa 100644 Binary files a/csharp/src/Google.Protobuf.Test/testprotos.pb and b/csharp/src/Google.Protobuf.Test/testprotos.pb differ diff --git a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs index f2bb61c2cd..d9944f6026 100644 --- a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs @@ -117,9 +117,15 @@ namespace Google.Protobuf.Reflection private static ReadOnlyDictionary CreateJsonFieldMap(IList fields) { var map = new Dictionary(); + // The ordering is important here: JsonName takes priority over Name, + // which means we need to put JsonName values in the map after *all* + // Name keys have been added. See https://github.com/protocolbuffers/protobuf/issues/11987 foreach (var field in fields) { map[field.Name] = field; + } + foreach (var field in fields) + { map[field.JsonName] = field; } return new ReadOnlyDictionary(map);