diff --git a/src/Elastic.Clients.Elasticsearch.Shared/Core/Extensions/StringExtensions.cs b/src/Elastic.Clients.Elasticsearch.Shared/Core/Extensions/StringExtensions.cs index 68b0b6290c6..b2493d99503 100644 --- a/src/Elastic.Clients.Elasticsearch.Shared/Core/Extensions/StringExtensions.cs +++ b/src/Elastic.Clients.Elasticsearch.Shared/Core/Extensions/StringExtensions.cs @@ -2,6 +2,8 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System; + #if ELASTICSEARCH_SERVERLESS namespace Elastic.Clients.Elasticsearch.Serverless; #else @@ -10,18 +12,53 @@ namespace Elastic.Clients.Elasticsearch; internal static class StringExtensions { - internal static string ToCamelCase(this string s) + // Taken from: + // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Text.Json/Common/JsonCamelCaseNamingPolicy.cs + + internal static string ToCamelCase(this string name) { - if (string.IsNullOrEmpty(s)) - return s; + if (string.IsNullOrEmpty(name) || !char.IsUpper(name[0])) + { + return name; + } + +#if NET + return string.Create(name.Length, name, (chars, name) => + { + name.CopyTo(chars); + FixCasing(chars); + }); +#else + var chars = name.ToCharArray(); + FixCasing(chars); + return new string(chars); +#endif + } + + private static void FixCasing(Span chars) + { + for (var i = 0; i < chars.Length; i++) + { + if (i == 1 && !char.IsUpper(chars[i])) + { + break; + } + + var hasNext = (i + 1 < chars.Length); - if (!char.IsUpper(s[0])) - return s; + // Stop when next char is already lowercase. + if (i > 0 && hasNext && !char.IsUpper(chars[i + 1])) + { + // If the next char is a space, lowercase current char before exiting. + if (chars[i + 1] == ' ') + { + chars[i] = char.ToLowerInvariant(chars[i]); + } - var camelCase = char.ToLowerInvariant(s[0]).ToString(); - if (s.Length > 1) - camelCase += s.Substring(1); + break; + } - return camelCase; + chars[i] = char.ToLowerInvariant(chars[i]); + } } }