Sfoglia il codice sorgente

Change JSON field name formatting

This affects cases with leading capital letters.

This breaks compatibility with previous C# releases, but
fixes compatibility with other implementations.

See #2278 for details.
Jon Skeet 9 anni fa
parent
commit
50a37e0135

+ 6 - 6
csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs

@@ -229,16 +229,16 @@ namespace Google.Protobuf
         [Test]
         [TestCase("foo_bar", "fooBar")]
         [TestCase("bananaBanana", "bananaBanana")]
-        [TestCase("BANANABanana", "bananaBanana")]
+        [TestCase("BANANABanana", "BANANABanana")]
         [TestCase("simple", "simple")]
-        [TestCase("ACTION_AND_ADVENTURE", "actionAndAdventure")]
+        [TestCase("ACTION_AND_ADVENTURE", "ACTIONANDADVENTURE")]
         [TestCase("action_and_adventure", "actionAndAdventure")]
         [TestCase("kFoo", "kFoo")]
-        [TestCase("HTTPServer", "httpServer")]
-        [TestCase("CLIENT", "client")]
-        public void ToCamelCase(string original, string expected)
+        [TestCase("HTTPServer", "HTTPServer")]
+        [TestCase("CLIENT", "CLIENT")]
+        public void ToJsonName(string original, string expected)
         {
-            Assert.AreEqual(expected, JsonFormatter.ToCamelCase(original));
+            Assert.AreEqual(expected, JsonFormatter.ToJsonName(original));
         }
 
         [Test]

+ 11 - 73
csharp/src/Google.Protobuf/JsonFormatter.cs

@@ -248,87 +248,25 @@ namespace Google.Protobuf
             return !first;
         }
 
-        /// <summary>
-        /// Camel-case converter with added strictness for field mask formatting.
-        /// </summary>
-        /// <exception cref="InvalidOperationException">The field mask is invalid for JSON representation</exception>
-        private static string ToCamelCaseForFieldMask(string input)
+        // Converted from java/core/src/main/java/com/google/protobuf/Descriptors.java
+        internal static string ToJsonName(string name)
         {
-            for (int i = 0; i < input.Length; i++)
+            StringBuilder result = new StringBuilder(name.Length);
+            bool isNextUpperCase = false;
+            foreach (char ch in name)
             {
-                char c = input[i];
-                if (c >= 'A' && c <= 'Z')
+                if (ch == '_')
                 {
-                    throw new InvalidOperationException($"Invalid field mask to be converted to JSON: {input}");
+                    isNextUpperCase = true;
                 }
-                if (c == '_' && i < input.Length - 1)
-                {
-                    char next = input[i + 1];
-                    if (next < 'a' || next > 'z')
-                    {
-                        throw new InvalidOperationException($"Invalid field mask to be converted to JSON: {input}");
-                    }
-                }
-            }
-            return ToCamelCase(input);
-        }
-
-        // Converted from src/google/protobuf/util/internal/utility.cc ToCamelCase
-        internal static string ToCamelCase(string input)
-        {
-            bool capitalizeNext = false;
-            bool wasCap = true;
-            bool isCap = false;
-            bool firstWord = true;
-            StringBuilder result = new StringBuilder(input.Length);
-
-            for (int i = 0; i < input.Length; i++, wasCap = isCap)
-            {
-                isCap = char.IsUpper(input[i]);
-                if (input[i] == '_')
+                else if (isNextUpperCase)
                 {
-                    capitalizeNext = true;
-                    if (result.Length != 0)
-                    {
-                        firstWord = false;
-                    }
-                    continue;
-                }
-                else if (firstWord)
-                {
-                    // Consider when the current character B is capitalized,
-                    // first word ends when:
-                    // 1) following a lowercase:   "...aB..."
-                    // 2) followed by a lowercase: "...ABc..."
-                    if (result.Length != 0 && isCap &&
-                        (!wasCap || (i + 1 < input.Length && char.IsLower(input[i + 1]))))
-                    {
-                        firstWord = false;
-                        result.Append(input[i]);
-                    }
-                    else
-                    {
-                        result.Append(char.ToLowerInvariant(input[i]));
-                        continue;
-                    }
-                }
-                else if (capitalizeNext)
-                {
-                    capitalizeNext = false;
-                    if (char.IsLower(input[i]))
-                    {
-                        result.Append(char.ToUpperInvariant(input[i]));
-                        continue;
-                    }
-                    else
-                    {
-                        result.Append(input[i]);
-                        continue;
-                    }
+                    result.Append(char.ToUpperInvariant(ch));
+                    isNextUpperCase = false;
                 }
                 else
                 {
-                    result.Append(char.ToLowerInvariant(input[i]));
+                    result.Append(ch);
                 }
             }
             return result.ToString();

+ 1 - 1
csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs

@@ -97,7 +97,7 @@ namespace Google.Protobuf.Reflection
             // We could trust the generated code and check whether the type of the property is
             // a MapField, but that feels a tad nasty.
             this.propertyName = propertyName;
-            JsonName =  Proto.JsonName == "" ? JsonFormatter.ToCamelCase(Proto.Name) : Proto.JsonName;
+            JsonName =  Proto.JsonName == "" ? JsonFormatter.ToJsonName(Proto.Name) : Proto.JsonName;
         }
     
 

+ 5 - 5
csharp/src/Google.Protobuf/WellKnownTypes/FieldMaskPartial.cs

@@ -52,7 +52,7 @@ namespace Google.Protobuf.WellKnownTypes
         /// </remarks>
         /// <param name="paths">Paths in the field mask</param>
         /// <param name="diagnosticOnly">Determines the handling of non-normalized values</param>
-        /// <exception cref="InvalidOperationException">The represented duration is invalid, and <paramref name="diagnosticOnly"/> is <c>false</c>.</exception>
+        /// <exception cref="InvalidOperationException">The represented field mask is invalid, and <paramref name="diagnosticOnly"/> is <c>false</c>.</exception>
         internal static string ToJson(IList<string> paths, bool diagnosticOnly)
         {
             var firstInvalid = paths.FirstOrDefault(p => !ValidatePath(p));
@@ -60,10 +60,10 @@ namespace Google.Protobuf.WellKnownTypes
             {
                 var writer = new StringWriter();
 #if DOTNET35
-                var query = paths.Select(JsonFormatter.ToCamelCase);
+                var query = paths.Select(JsonFormatter.ToJsonName);
                 JsonFormatter.WriteString(writer, string.Join(",", query.ToArray()));
 #else
-                JsonFormatter.WriteString(writer, string.Join(",", paths.Select(JsonFormatter.ToCamelCase)));
+                JsonFormatter.WriteString(writer, string.Join(",", paths.Select(JsonFormatter.ToJsonName)));
 #endif
                 return writer.ToString();
             }
@@ -85,9 +85,9 @@ namespace Google.Protobuf.WellKnownTypes
         }
 
         /// <summary>
-        /// Camel-case converter with added strictness for field mask formatting.
+        /// Checks whether the given path is valid for a field mask.
         /// </summary>
-        /// <exception cref="InvalidOperationException">The field mask is invalid for JSON representation</exception>
+        /// <returns>true if the path is valid; false otherwise</returns>
         private static bool ValidatePath(string input)
         {
             for (int i = 0; i < input.Length; i++)