Skip to content

Commit 6187a96

Browse files
committed
best json perf
1 parent 7d2e6f4 commit 6187a96

8 files changed

+338
-47
lines changed

src/BrazilModels/Cnpj.cs

+23-17
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace BrazilModels;
1313
[System.Text.Json.Serialization.JsonConverter(typeof(StringSystemTextJsonConverter<Cnpj>))]
1414
[TypeConverter(typeof(StringTypeConverter<Cnpj>))]
1515
[DebuggerDisplay("{DebuggerDisplay(),nq}")]
16-
public readonly record struct Cnpj : IComparable<Cnpj>
16+
public readonly record struct Cnpj : IComparable<Cnpj>, IStringValue
1717
#if NET8_0_OR_GREATER
1818
, ISpanFormattable
1919
, ISpanParsable<Cnpj>
@@ -26,7 +26,7 @@ namespace BrazilModels;
2626
/// <summary>
2727
/// CNPJ Size
2828
/// </summary>
29-
public const ushort DefaultLength = 14;
29+
public const byte DefaultLength = 14;
3030

3131
/// <summary>
3232
/// CNPJ Mask
@@ -46,7 +46,7 @@ namespace BrazilModels;
4646
/// <summary>
4747
/// Empty invalid CNPJ
4848
/// </summary>
49-
public static readonly Cnpj Empty = new();
49+
public static Cnpj Empty { get; } = new();
5050

5151
/// <summary>
5252
/// CNPJ string representation
@@ -93,6 +93,10 @@ public Cnpj(in long value) : this(value.ToString(CultureInfo.InvariantCulture))
9393
throw CnpjException(value);
9494
}
9595

96+
/// <summary>
97+
/// Returns true if is empty
98+
/// </summary>
99+
public bool IsEmpty => Value == Empty.Value;
96100

97101
/// <summary>
98102
/// Return a CNPJ string representation without special symbols
@@ -269,7 +273,7 @@ public static bool TryParse(ReadOnlySpan<char> value, out Cnpj result)
269273
/// </param>
270274
/// <returns> true if the parse operation was successful; otherwise, false.</returns>
271275
public static bool TryParse(ReadOnlySpan<byte> value, out Cnpj result) =>
272-
TryParse(Encoding.UTF8.GetString(value), out result);
276+
TryParse(Encoding.UTF8.GetString(value).AsSpan(), out result);
273277

274278
/// <summary>
275279
/// Converts the string representation of a CNPJ to the equivalent Cnpj structure.
@@ -351,20 +355,20 @@ public static bool Validate(in ReadOnlySpan<char> cnpjString)
351355
totalDigit2 += digit * multiplier2[position];
352356
break;
353357
case 12:
354-
{
355-
var dv1 = (totalDigit1 % 11);
356-
dv1 = dv1 < 2 ? 0 : 11 - dv1;
357-
if (digit != dv1) return false;
358-
totalDigit2 += dv1 * multiplier2[12];
359-
break;
360-
}
358+
{
359+
var dv1 = (totalDigit1 % 11);
360+
dv1 = dv1 < 2 ? 0 : 11 - dv1;
361+
if (digit != dv1) return false;
362+
totalDigit2 += dv1 * multiplier2[12];
363+
break;
364+
}
361365
case 13:
362-
{
363-
var dv2 = (totalDigit2 % 11);
364-
dv2 = dv2 < 2 ? 0 : 11 - dv2;
365-
if (digit != dv2) return false;
366-
break;
367-
}
366+
{
367+
var dv2 = (totalDigit2 % 11);
368+
dv2 = dv2 < 2 ? 0 : 11 - dv2;
369+
if (digit != dv2) return false;
370+
break;
371+
}
368372
}
369373

370374
position++;
@@ -440,5 +444,7 @@ static Cnpj IUtf8SpanParsable<Cnpj>.Parse(
440444

441445
static bool IUtf8SpanParsable<Cnpj>.TryParse(ReadOnlySpan<byte> utf8Text,
442446
IFormatProvider? provider, out Cnpj result) => TryParse(utf8Text, out result);
447+
448+
static int IStringValue.ValueSize => DefaultLength;
443449
#endif
444450
}

src/BrazilModels/Cpf.cs

+11-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace BrazilModels;
1313
[System.Text.Json.Serialization.JsonConverter(typeof(StringSystemTextJsonConverter<Cpf>))]
1414
[TypeConverter(typeof(StringTypeConverter<Cpf>))]
1515
[DebuggerDisplay("{DebuggerDisplay(),nq}")]
16-
public readonly record struct Cpf : IComparable<Cpf>
16+
public readonly record struct Cpf : IComparable<Cpf>, IStringValue
1717
#if NET8_0_OR_GREATER
1818
, ISpanFormattable
1919
, ISpanParsable<Cpf>
@@ -26,7 +26,7 @@ namespace BrazilModels;
2626
/// <summary>
2727
/// CPF Size
2828
/// </summary>
29-
public const int DefaultLength = 11;
29+
public const byte DefaultLength = 11;
3030

3131
/// <summary>
3232
/// CPF Mask
@@ -36,7 +36,7 @@ namespace BrazilModels;
3636
/// <summary>
3737
/// Empty invalid CPF
3838
/// </summary>
39-
public static readonly Cpf Empty = new();
39+
public static Cpf Empty { get; } = new();
4040

4141
/// <summary>
4242
/// CPF string representation
@@ -83,6 +83,11 @@ public Cpf(in ReadOnlySpan<char> value) : this(value, true) { }
8383
throw CpfException(value);
8484
}
8585

86+
/// <summary>
87+
/// Returns true if is empty
88+
/// </summary>
89+
public bool IsEmpty => Value == Empty.Value;
90+
8691
static FormatException CpfException(in ReadOnlySpan<char> value) =>
8792
new($"Invalid CPF: {value}");
8893

@@ -283,7 +288,7 @@ public static bool TryParse(string? value, out Cpf result)
283288
/// </param>
284289
/// <returns> true if the parse operation was successful; otherwise, false.</returns>
285290
public static bool TryParse(ReadOnlySpan<byte> value, out Cpf result) =>
286-
TryParse(Encoding.UTF8.GetString(value), out result);
291+
TryParse(Encoding.UTF8.GetString(value).AsSpan(), out result);
287292

288293
/// <summary>
289294
/// Converts the string representation of a CPF to the equivalent Cpf structure.
@@ -434,5 +439,7 @@ static Cpf IUtf8SpanParsable<Cpf>.Parse(
434439

435440
static bool IUtf8SpanParsable<Cpf>.TryParse(ReadOnlySpan<byte> utf8Text,
436441
IFormatProvider? provider, out Cpf result) => TryParse(utf8Text, out result);
442+
443+
static int IStringValue.ValueSize => DefaultLength;
437444
#endif
438445
}

src/BrazilModels/CpfCnpj.cs

+10-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace BrazilModels;
1313
[System.Text.Json.Serialization.JsonConverter(typeof(StringSystemTextJsonConverter<CpfCnpj>))]
1414
[TypeConverter(typeof(StringTypeConverter<CpfCnpj>))]
1515
[DebuggerDisplay("{DebuggerDisplay(),nq}")]
16-
public readonly record struct CpfCnpj : IComparable<CpfCnpj>
16+
public readonly record struct CpfCnpj : IComparable<CpfCnpj>, IStringValue
1717
#if NET8_0_OR_GREATER
1818
, ISpanFormattable
1919
, ISpanParsable<CpfCnpj>
@@ -36,7 +36,7 @@ namespace BrazilModels;
3636
/// <summary>
3737
/// Empty invalid CpfCnpj
3838
/// </summary>
39-
public static readonly CpfCnpj Empty = new(string.Empty, 0);
39+
public static CpfCnpj Empty { get; } = new(string.Empty, 0);
4040

4141
/// <summary>
4242
/// Construct an Empty CPF/CNPJ
@@ -92,6 +92,11 @@ public CpfCnpj(in ReadOnlySpan<char> value)
9292
Value = Format(value, type);
9393
}
9494

95+
/// <summary>
96+
/// Returns true if is empty
97+
/// </summary>
98+
public bool IsEmpty => Value == Empty.Value;
99+
95100
/// <summary>
96101
/// Return a CPF/CNPJ string representation without special symbols
97102
/// </summary>
@@ -258,7 +263,7 @@ public static bool TryParse(ReadOnlySpan<char> value, out CpfCnpj result)
258263
/// </param>
259264
/// <returns> true if the parse operation was successful; otherwise, false.</returns>
260265
public static bool TryParse(ReadOnlySpan<byte> value, out CpfCnpj result) =>
261-
TryParse(Encoding.UTF8.GetString(value), out result);
266+
TryParse(Encoding.UTF8.GetString(value).AsSpan(), out result);
262267

263268
/// <summary>
264269
/// Converts the string representation of a brazilian document to the equivalent CpfCnpj structure.
@@ -388,5 +393,7 @@ static CpfCnpj IUtf8SpanParsable<CpfCnpj>.Parse(
388393

389394
static bool IUtf8SpanParsable<CpfCnpj>.TryParse(ReadOnlySpan<byte> utf8Text,
390395
IFormatProvider? provider, out CpfCnpj result) => TryParse(utf8Text, out result);
396+
397+
static int IStringValue.ValueSize { get; } = Math.Max(Cpf.DefaultLength, Cnpj.DefaultLength);
391398
#endif
392399
}

src/BrazilModels/Email.cs

+121-8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Text;
12
using BrazilModels.Json;
23

34
namespace BrazilModels;
@@ -12,7 +13,15 @@ namespace BrazilModels;
1213
[System.Text.Json.Serialization.JsonConverter(typeof(StringSystemTextJsonConverter<Email>))]
1314
[TypeConverter(typeof(StringTypeConverter<Email>))]
1415
[DebuggerDisplay("{DebuggerDisplay(),nq}")]
15-
public readonly record struct Email : IComparable<Email>, IFormattable
16+
public readonly record struct Email : IComparable<Email>, IStringValue
17+
#if NET8_0_OR_GREATER
18+
, ISpanFormattable
19+
, ISpanParsable<Email>
20+
, IUtf8SpanFormattable
21+
, IUtf8SpanParsable<Email>
22+
#else
23+
, IFormattable
24+
#endif
1625
{
1726
/// <summary>
1827
/// String representation of the Email
@@ -30,6 +39,24 @@ public Email(string email)
3039
this.Value = email.ToLowerInvariant();
3140
}
3241

42+
/// <summary>
43+
/// Create a new email instance
44+
/// </summary>
45+
/// <exception cref="InvalidOperationException"></exception>
46+
/// <exception cref="ArgumentNullException"></exception>
47+
public Email(ReadOnlySpan<char> email)
48+
{
49+
if (email.IsEmptyOrWhiteSpace())
50+
throw new ArgumentException("Invalid value argument");
51+
52+
this.Value = email.ToString().ToLowerInvariant();
53+
}
54+
55+
/// <summary>
56+
/// Returns true if is empty
57+
/// </summary>
58+
public bool IsEmpty => string.IsNullOrWhiteSpace(Value);
59+
3360
/// <inheritdoc />
3461
public override string ToString() => Value;
3562

@@ -38,7 +65,7 @@ string IFormattable.ToString(string? format, IFormatProvider? formatProvider) =>
3865
Value.ToString(formatProvider);
3966

4067
/// <summary>
41-
/// Get Email instance of an Value string
68+
/// Get Email instance of a Value string
4269
/// </summary>
4370
/// <exception cref="InvalidOperationException"></exception>
4471
/// <exception cref="ArgumentNullException"></exception>
@@ -52,20 +79,35 @@ public static implicit operator string(Email email)
5279
=> email.Value;
5380

5481
/// <summary>
55-
/// Try parse an Value string to an Email instance
82+
/// Try parse a char span to an Email instance
5683
/// </summary>
57-
public static bool TryParse(string? value, out Email email)
84+
public static bool TryParse(ReadOnlySpan<char> value, out Email email)
5885
{
5986
email = default;
60-
if (value is null || !IsValid(value))
87+
if (value.IsEmpty || !IsValid(value))
6188
return false;
6289

6390
email = new(value);
6491
return true;
6592
}
6693

6794
/// <summary>
68-
/// Parse an Value string to an Email instance
95+
/// Try parse a Value string to an Email instance
96+
/// </summary>
97+
public static bool TryParse(string? value, out Email email)
98+
{
99+
email = default;
100+
return value is not null && TryParse(value.AsSpan(), out email);
101+
}
102+
103+
/// <summary>
104+
/// Try parse a UTF8 byte span to an Email instance
105+
/// </summary>
106+
public static bool TryParse(ReadOnlySpan<byte> value, out Email result) =>
107+
TryParse(Encoding.UTF8.GetString(value).AsSpan(), out result);
108+
109+
/// <summary>
110+
/// Parse a Value string to an Email instance
69111
/// </summary>
70112
/// <exception cref="InvalidOperationException"></exception>
71113
/// <exception cref="ArgumentNullException"></exception>
@@ -74,15 +116,30 @@ public static Email Parse(string value) =>
74116
? valid
75117
: throw new InvalidOperationException($"Invalid E-mail {value}");
76118

119+
/// <summary>
120+
/// Parse a Value string to an Email instance
121+
/// </summary>
122+
/// <exception cref="InvalidOperationException"></exception>
123+
/// <exception cref="ArgumentNullException"></exception>
124+
public static Email Parse(ReadOnlySpan<char> value) =>
125+
TryParse(value, out var valid)
126+
? valid
127+
: throw new InvalidOperationException($"Invalid E-mail {value}");
128+
129+
/// <summary>
130+
/// Parse an UTF8 byte span to an Email instance
131+
/// </summary>
132+
public static Email Parse(ReadOnlySpan<byte> value) => Parse(Encoding.UTF8.GetString(value));
133+
77134
/// <summary>
78135
/// Validate Email string
79136
/// </summary>
80137
/// <param name="value"></param>
81138
/// <returns></returns>
82-
public static bool IsValid(string? value)
139+
public static bool IsValid(ReadOnlySpan<char> value)
83140
{
84141
const string at = "@";
85-
if (value is null)
142+
if (value.IsEmptyOrWhiteSpace())
86143
return false;
87144

88145
var index = value.IndexOf(at, StringComparison.OrdinalIgnoreCase);
@@ -92,9 +149,65 @@ public static bool IsValid(string? value)
92149
index == value.LastIndexOf(at, StringComparison.OrdinalIgnoreCase);
93150
}
94151

152+
/// <summary>
153+
/// Validate Email string
154+
/// </summary>
155+
/// <param name="value"></param>
156+
/// <returns></returns>
157+
public static bool IsValid(string? value) => value is not null && IsValid(value.AsSpan());
158+
95159
/// <inheritdoc />
96160
public int CompareTo(Email other) =>
97161
string.Compare(Value, other.Value, StringComparison.OrdinalIgnoreCase);
98162

99163
string DebuggerDisplay() => $"EMAIL{{{Value}}}";
164+
165+
166+
#if NET8_0_OR_GREATER
167+
bool ISpanFormattable.TryFormat(
168+
Span<char> destination, out int charsWritten,
169+
ReadOnlySpan<char> format, IFormatProvider? provider
170+
)
171+
{
172+
charsWritten = 0;
173+
if (destination.IsEmpty) return false;
174+
175+
if (destination.Length < Value.Length)
176+
return false;
177+
178+
charsWritten = Value.Length;
179+
Value.CopyTo(destination);
180+
return true;
181+
}
182+
183+
bool IUtf8SpanFormattable.TryFormat(
184+
Span<byte> utf8Destination, out int bytesWritten,
185+
ReadOnlySpan<char> format, IFormatProvider? provider
186+
)
187+
{
188+
bytesWritten = 0;
189+
if (utf8Destination.IsEmpty) return false;
190+
return Encoding.UTF8.TryGetBytes(Value, utf8Destination, out bytesWritten);
191+
}
192+
193+
static Email IParsable<Email>.Parse(string s, IFormatProvider? provider) => Parse(s);
194+
195+
static bool IParsable<Email>.TryParse(string? s, IFormatProvider? provider, out Email result) =>
196+
TryParse(s, out result);
197+
198+
static Email ISpanParsable<Email>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider) =>
199+
Parse(s);
200+
201+
static bool ISpanParsable<Email>.TryParse(
202+
ReadOnlySpan<char> s, IFormatProvider? provider, out Email result) =>
203+
TryParse(s, out result);
204+
205+
static Email IUtf8SpanParsable<Email>.Parse(
206+
ReadOnlySpan<byte> utf8Text, IFormatProvider? provider) => Parse(utf8Text);
207+
208+
static bool IUtf8SpanParsable<Email>.TryParse(ReadOnlySpan<byte> utf8Text,
209+
IFormatProvider? provider, out Email result) => TryParse(utf8Text, out result);
210+
211+
static int IStringValue.ValueSize { get; } = 255;
212+
#endif
100213
}

src/BrazilModels/Extensions.cs

+4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ public static string ToBrazilMoneyString(this decimal value, bool moneySuffix =
3838

3939
static class Extensions
4040
{
41+
public static void RemoveNonDigits(
42+
in this Span<char> input, Span<char> result, out int written
43+
) => RemoveNonDigits((ReadOnlySpan<char>)input, result, out written);
44+
4145
public static void RemoveNonDigits(
4246
in this ReadOnlySpan<char> input,
4347
Span<char> result, out int written

0 commit comments

Comments
 (0)