Skip to content

Commit d899346

Browse files
authored
Optimize camel-casing logic (#1850)
1 parent 610781b commit d899346

File tree

4 files changed

+119
-82
lines changed

4 files changed

+119
-82
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using BenchmarkDotNet.Attributes;
2+
3+
namespace NJsonSchema.Benchmark;
4+
5+
[MemoryDiagnoser]
6+
public class ConversionUtilitiesBenchmark
7+
{
8+
[Params("example_string", "1another_example", "ConversionUtilities", "ConvertToUpperCamelCase", "/foo/bar/baz", "")]
9+
public string Input { get; set; }
10+
11+
[Benchmark]
12+
public string ConvertToUpperCamelCase()
13+
{
14+
return ConversionUtilities.ConvertToUpperCamelCase(Input, firstCharacterMustBeAlpha: true);
15+
}
16+
17+
[Benchmark]
18+
public string ConvertToLowerCamelCase()
19+
{
20+
return ConversionUtilities.ConvertToLowerCamelCase(Input, firstCharacterMustBeAlpha: true);
21+
}
22+
}

src/NJsonSchema.Benchmark/NJsonSchema.Benchmark.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<Nullable>disable</Nullable>
99
<EnableNETAnalyzers>false</EnableNETAnalyzers>
1010
<ImplicitUsings>enable</ImplicitUsings>
11+
<OutputType>exe</OutputType>
1112
</PropertyGroup>
1213

1314
<ItemGroup>

src/NJsonSchema.Benchmark/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public static class Program
77
public static void Main(string[] args)
88
{
99
// RunCsharpBenchmark();
10-
BenchmarkDotNet.Running.BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).RunAllJoined();
10+
BenchmarkDotNet.Running.BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run();
1111
}
1212

1313
#pragma warning disable IDE0051

src/NJsonSchema/ConversionUtilities.cs

Lines changed: 95 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
using System.Globalization;
1010
using System.Linq;
11-
using System.Runtime.CompilerServices;
1211
using System.Text;
1312
using System.Text.RegularExpressions;
1413
using System.Xml.Linq;
@@ -18,68 +17,128 @@ namespace NJsonSchema
1817
/// <summary>Provides name conversion utility methods.</summary>
1918
public class ConversionUtilities
2019
{
20+
private static readonly char[] _camelCaseCleanupChars = [' ', '/'];
21+
22+
#if NET8_0_OR_GREATER
23+
private static readonly System.Buffers.SearchValues<char> CamelCaseCleanupChars = System.Buffers.SearchValues.Create(_camelCaseCleanupChars);
24+
#else
25+
private static readonly char[] CamelCaseCleanupChars = _camelCaseCleanupChars;
26+
#endif
27+
28+
/// <summary>Converts the input to a camel case identifier.</summary>
29+
/// <param name="input">The input.</param>
30+
/// <returns>The converted input. </returns>
31+
public static string ConvertToCamelCase(string input)
32+
{
33+
return ConvertToCamelCase(input, firstCharacterMustBeAlpha: false, CamelCaseMode.None);
34+
}
35+
2136
/// <summary>Converts the first letter to lower case and dashes to camel case.</summary>
2237
/// <param name="input">The input.</param>
2338
/// <param name="firstCharacterMustBeAlpha">Specifies whether to add an _ when the first character is not alpha.</param>
2439
/// <returns>The converted input.</returns>
2540
public static string ConvertToLowerCamelCase(string input, bool firstCharacterMustBeAlpha)
2641
{
27-
if (string.IsNullOrEmpty(input))
28-
{
29-
return string.Empty;
30-
}
31-
32-
var lowered = char.ToLowerInvariant(input[0]) + (input.Length > 1 ? input.Substring(1) : "");
33-
var cleaned = lowered.Replace(' ', '_').Replace('/', '_');
34-
input = ConvertDashesToCamelCase(cleaned);
35-
36-
if (string.IsNullOrEmpty(input))
37-
{
38-
return string.Empty;
39-
}
40-
41-
if (firstCharacterMustBeAlpha && char.IsNumber(input[0]))
42-
{
43-
return "_" + input;
44-
}
45-
46-
return input;
42+
return ConvertToCamelCase(input, firstCharacterMustBeAlpha, CamelCaseMode.FirstLower);
4743
}
4844

4945
/// <summary>Converts the first letter to upper case and dashes to camel case.</summary>
5046
/// <param name="input">The input.</param>
5147
/// <param name="firstCharacterMustBeAlpha">Specifies whether to add an _ when the first character is not alpha.</param>
5248
/// <returns>The converted input.</returns>
5349
public static string ConvertToUpperCamelCase(string input, bool firstCharacterMustBeAlpha)
50+
{
51+
return ConvertToCamelCase(input, firstCharacterMustBeAlpha, CamelCaseMode.FirstUpper);
52+
}
53+
54+
private static string ConvertToCamelCase(string input, bool firstCharacterMustBeAlpha, CamelCaseMode mode)
5455
{
5556
if (string.IsNullOrEmpty(input))
5657
{
5758
return string.Empty;
5859
}
5960

60-
var cleaned = Capitalize(input).Replace(' ', '_').Replace('/', '_');
61-
input = ConvertDashesToCamelCase(cleaned);
61+
if (input.AsSpan().IndexOfAny(CamelCaseCleanupChars) != -1)
62+
{
63+
input = input.Replace(' ', '_').Replace('/', '_');
64+
}
6265

63-
if (firstCharacterMustBeAlpha && char.IsNumber(input[0]))
66+
if (input.AsSpan().IndexOf('-') == -1)
6467
{
65-
return "_" + input;
68+
// no need for expensive conversion
69+
var c = input[0];
70+
if (char.IsNumber(c))
71+
{
72+
return firstCharacterMustBeAlpha ? "_" + input : input;
73+
}
74+
75+
var newFirst = mode switch
76+
{
77+
CamelCaseMode.FirstUpper => char.ToUpperInvariant(c),
78+
CamelCaseMode.FirstLower => char.ToLowerInvariant(c),
79+
_ => c
80+
};
81+
82+
if (newFirst != c)
83+
{
84+
return newFirst + input[1..];
85+
}
86+
87+
return input;
6688
}
6789

68-
return input;
90+
return DoFullCamelCaseConversion(input, firstCharacterMustBeAlpha, mode);
6991
}
7092

71-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
72-
private static string Capitalize(string input)
93+
private static string DoFullCamelCaseConversion(string input, bool firstCharacterMustBeAlpha, CamelCaseMode mode)
7394
{
74-
if (char.IsUpper(input[0]))
75-
{
76-
return input;
77-
}
78-
if (input.Length == 1)
95+
var capacity = input.Length + (firstCharacterMustBeAlpha ? 1 : 0);
96+
var buffer = capacity < 2 ? stackalloc char[capacity] : new char[capacity];
97+
98+
var sb = new ValueStringBuilder(buffer);
99+
100+
var caseFlag = false;
101+
for (var i = 0; i < input.Length; i++)
79102
{
80-
return char.ToUpperInvariant(input[0]).ToString();
103+
var c = input[i];
104+
if (c == '-')
105+
{
106+
caseFlag = true;
107+
}
108+
else if (caseFlag)
109+
{
110+
sb.Append(char.ToUpperInvariant(c));
111+
caseFlag = false;
112+
}
113+
else
114+
{
115+
if (i == 0)
116+
{
117+
if (firstCharacterMustBeAlpha && char.IsNumber(c))
118+
{
119+
sb.Append('_');
120+
}
121+
else if (mode == CamelCaseMode.FirstUpper)
122+
{
123+
c = char.ToUpperInvariant(c);
124+
}
125+
else if (mode == CamelCaseMode.FirstLower)
126+
{
127+
c = char.ToLowerInvariant(c);
128+
}
129+
}
130+
sb.Append(c);
131+
}
81132
}
82-
return char.ToUpperInvariant(input[0]) + input.Substring(1);
133+
134+
return sb.ToString();
135+
}
136+
137+
private enum CamelCaseMode
138+
{
139+
None,
140+
FirstLower,
141+
FirstUpper,
83142
}
84143

85144
/// <summary>Converts the string to a string literal which can be used in C# or TypeScript code.</summary>
@@ -135,7 +194,7 @@ internal static string ConvertToStringLiteral(string input, string? prefix, stri
135194
break;
136195
default:
137196
// ASCII printable character
138-
if (c is >= (char)0x20 and <= (char)0x7e)
197+
if (c is >= (char) 0x20 and <= (char) 0x7e)
139198
{
140199
literal.Append(c);
141200
// As UTF16 escaped character
@@ -158,20 +217,6 @@ internal static string ConvertToStringLiteral(string input, string? prefix, stri
158217
return literal.ToString();
159218
}
160219

161-
/// <summary>Converts the input to a camel case identifier.</summary>
162-
/// <param name="input">The input.</param>
163-
/// <returns>The converted input. </returns>
164-
public static string ConvertToCamelCase(string input)
165-
{
166-
if (string.IsNullOrEmpty(input))
167-
{
168-
return string.Empty;
169-
}
170-
171-
return ConvertDashesToCamelCase(input.Replace(" ", "_").Replace("/", "_"));
172-
}
173-
174-
175220
private static readonly char[] _whiteSpaceChars = ['\n', '\r', '\t', ' '];
176221

177222
/// <summary>Trims white spaces from the text.</summary>
@@ -354,36 +399,5 @@ private static string CreateTabString(int tabCount)
354399
var tabString = new string(' ', 4 * tabCount);
355400
return tabString;
356401
}
357-
358-
private static string ConvertDashesToCamelCase(string input)
359-
{
360-
if (!input.Contains('-'))
361-
{
362-
// no conversion necessary
363-
return input;
364-
}
365-
366-
// we are removing at least one character
367-
var sb = new ValueStringBuilder(input.Length - 1);
368-
var caseFlag = false;
369-
foreach (var c in input)
370-
{
371-
if (c == '-')
372-
{
373-
caseFlag = true;
374-
}
375-
else if (caseFlag)
376-
{
377-
sb.Append(char.ToUpperInvariant(c));
378-
caseFlag = false;
379-
}
380-
else
381-
{
382-
sb.Append(c);
383-
}
384-
}
385-
386-
return sb.ToString();
387-
}
388402
}
389403
}

0 commit comments

Comments
 (0)