Skip to content

Commit

Permalink
best json perf
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasteles committed Apr 3, 2024
1 parent 7d2e6f4 commit 6187a96
Show file tree
Hide file tree
Showing 8 changed files with 338 additions and 47 deletions.
40 changes: 23 additions & 17 deletions src/BrazilModels/Cnpj.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace BrazilModels;
[System.Text.Json.Serialization.JsonConverter(typeof(StringSystemTextJsonConverter<Cnpj>))]
[TypeConverter(typeof(StringTypeConverter<Cnpj>))]
[DebuggerDisplay("{DebuggerDisplay(),nq}")]
public readonly record struct Cnpj : IComparable<Cnpj>
public readonly record struct Cnpj : IComparable<Cnpj>, IStringValue
#if NET8_0_OR_GREATER
, ISpanFormattable
, ISpanParsable<Cnpj>
Expand All @@ -26,7 +26,7 @@ namespace BrazilModels;
/// <summary>
/// CNPJ Size
/// </summary>
public const ushort DefaultLength = 14;
public const byte DefaultLength = 14;

/// <summary>
/// CNPJ Mask
Expand All @@ -46,7 +46,7 @@ namespace BrazilModels;
/// <summary>
/// Empty invalid CNPJ
/// </summary>
public static readonly Cnpj Empty = new();
public static Cnpj Empty { get; } = new();

/// <summary>
/// CNPJ string representation
Expand Down Expand Up @@ -93,6 +93,10 @@ public Cnpj(in long value) : this(value.ToString(CultureInfo.InvariantCulture))
throw CnpjException(value);
}

/// <summary>
/// Returns true if is empty
/// </summary>
public bool IsEmpty => Value == Empty.Value;

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

/// <summary>
/// Converts the string representation of a CNPJ to the equivalent Cnpj structure.
Expand Down Expand Up @@ -351,20 +355,20 @@ public static bool Validate(in ReadOnlySpan<char> cnpjString)
totalDigit2 += digit * multiplier2[position];
break;
case 12:
{
var dv1 = (totalDigit1 % 11);
dv1 = dv1 < 2 ? 0 : 11 - dv1;
if (digit != dv1) return false;
totalDigit2 += dv1 * multiplier2[12];
break;
}
{
var dv1 = (totalDigit1 % 11);
dv1 = dv1 < 2 ? 0 : 11 - dv1;
if (digit != dv1) return false;
totalDigit2 += dv1 * multiplier2[12];
break;
}
case 13:
{
var dv2 = (totalDigit2 % 11);
dv2 = dv2 < 2 ? 0 : 11 - dv2;
if (digit != dv2) return false;
break;
}
{
var dv2 = (totalDigit2 % 11);
dv2 = dv2 < 2 ? 0 : 11 - dv2;
if (digit != dv2) return false;
break;
}
}

position++;
Expand Down Expand Up @@ -440,5 +444,7 @@ static Cnpj IUtf8SpanParsable<Cnpj>.Parse(

static bool IUtf8SpanParsable<Cnpj>.TryParse(ReadOnlySpan<byte> utf8Text,
IFormatProvider? provider, out Cnpj result) => TryParse(utf8Text, out result);

static int IStringValue.ValueSize => DefaultLength;
#endif
}
15 changes: 11 additions & 4 deletions src/BrazilModels/Cpf.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace BrazilModels;
[System.Text.Json.Serialization.JsonConverter(typeof(StringSystemTextJsonConverter<Cpf>))]
[TypeConverter(typeof(StringTypeConverter<Cpf>))]
[DebuggerDisplay("{DebuggerDisplay(),nq}")]
public readonly record struct Cpf : IComparable<Cpf>
public readonly record struct Cpf : IComparable<Cpf>, IStringValue
#if NET8_0_OR_GREATER
, ISpanFormattable
, ISpanParsable<Cpf>
Expand All @@ -26,7 +26,7 @@ namespace BrazilModels;
/// <summary>
/// CPF Size
/// </summary>
public const int DefaultLength = 11;
public const byte DefaultLength = 11;

/// <summary>
/// CPF Mask
Expand All @@ -36,7 +36,7 @@ namespace BrazilModels;
/// <summary>
/// Empty invalid CPF
/// </summary>
public static readonly Cpf Empty = new();
public static Cpf Empty { get; } = new();

/// <summary>
/// CPF string representation
Expand Down Expand Up @@ -83,6 +83,11 @@ public Cpf(in ReadOnlySpan<char> value) : this(value, true) { }
throw CpfException(value);
}

/// <summary>
/// Returns true if is empty
/// </summary>
public bool IsEmpty => Value == Empty.Value;

static FormatException CpfException(in ReadOnlySpan<char> value) =>
new($"Invalid CPF: {value}");

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

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

static bool IUtf8SpanParsable<Cpf>.TryParse(ReadOnlySpan<byte> utf8Text,
IFormatProvider? provider, out Cpf result) => TryParse(utf8Text, out result);

static int IStringValue.ValueSize => DefaultLength;
#endif
}
13 changes: 10 additions & 3 deletions src/BrazilModels/CpfCnpj.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace BrazilModels;
[System.Text.Json.Serialization.JsonConverter(typeof(StringSystemTextJsonConverter<CpfCnpj>))]
[TypeConverter(typeof(StringTypeConverter<CpfCnpj>))]
[DebuggerDisplay("{DebuggerDisplay(),nq}")]
public readonly record struct CpfCnpj : IComparable<CpfCnpj>
public readonly record struct CpfCnpj : IComparable<CpfCnpj>, IStringValue
#if NET8_0_OR_GREATER
, ISpanFormattable
, ISpanParsable<CpfCnpj>
Expand All @@ -36,7 +36,7 @@ namespace BrazilModels;
/// <summary>
/// Empty invalid CpfCnpj
/// </summary>
public static readonly CpfCnpj Empty = new(string.Empty, 0);
public static CpfCnpj Empty { get; } = new(string.Empty, 0);

/// <summary>
/// Construct an Empty CPF/CNPJ
Expand Down Expand Up @@ -92,6 +92,11 @@ public CpfCnpj(in ReadOnlySpan<char> value)
Value = Format(value, type);
}

/// <summary>
/// Returns true if is empty
/// </summary>
public bool IsEmpty => Value == Empty.Value;

/// <summary>
/// Return a CPF/CNPJ string representation without special symbols
/// </summary>
Expand Down Expand Up @@ -258,7 +263,7 @@ public static bool TryParse(ReadOnlySpan<char> value, out CpfCnpj result)
/// </param>
/// <returns> true if the parse operation was successful; otherwise, false.</returns>
public static bool TryParse(ReadOnlySpan<byte> value, out CpfCnpj result) =>
TryParse(Encoding.UTF8.GetString(value), out result);
TryParse(Encoding.UTF8.GetString(value).AsSpan(), out result);

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

static bool IUtf8SpanParsable<CpfCnpj>.TryParse(ReadOnlySpan<byte> utf8Text,
IFormatProvider? provider, out CpfCnpj result) => TryParse(utf8Text, out result);

static int IStringValue.ValueSize { get; } = Math.Max(Cpf.DefaultLength, Cnpj.DefaultLength);
#endif
}
129 changes: 121 additions & 8 deletions src/BrazilModels/Email.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Text;
using BrazilModels.Json;

namespace BrazilModels;
Expand All @@ -12,7 +13,15 @@ namespace BrazilModels;
[System.Text.Json.Serialization.JsonConverter(typeof(StringSystemTextJsonConverter<Email>))]
[TypeConverter(typeof(StringTypeConverter<Email>))]
[DebuggerDisplay("{DebuggerDisplay(),nq}")]
public readonly record struct Email : IComparable<Email>, IFormattable
public readonly record struct Email : IComparable<Email>, IStringValue
#if NET8_0_OR_GREATER
, ISpanFormattable
, ISpanParsable<Email>
, IUtf8SpanFormattable
, IUtf8SpanParsable<Email>
#else
, IFormattable
#endif
{
/// <summary>
/// String representation of the Email
Expand All @@ -30,6 +39,24 @@ public Email(string email)
this.Value = email.ToLowerInvariant();
}

/// <summary>
/// Create a new email instance
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="ArgumentNullException"></exception>
public Email(ReadOnlySpan<char> email)
{
if (email.IsEmptyOrWhiteSpace())
throw new ArgumentException("Invalid value argument");

this.Value = email.ToString().ToLowerInvariant();
}

/// <summary>
/// Returns true if is empty
/// </summary>
public bool IsEmpty => string.IsNullOrWhiteSpace(Value);

/// <inheritdoc />
public override string ToString() => Value;

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

/// <summary>
/// Get Email instance of an Value string
/// Get Email instance of a Value string
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="ArgumentNullException"></exception>
Expand All @@ -52,20 +79,35 @@ public static implicit operator string(Email email)
=> email.Value;

/// <summary>
/// Try parse an Value string to an Email instance
/// Try parse a char span to an Email instance
/// </summary>
public static bool TryParse(string? value, out Email email)
public static bool TryParse(ReadOnlySpan<char> value, out Email email)
{
email = default;
if (value is null || !IsValid(value))
if (value.IsEmpty || !IsValid(value))
return false;

email = new(value);
return true;
}

/// <summary>
/// Parse an Value string to an Email instance
/// Try parse a Value string to an Email instance
/// </summary>
public static bool TryParse(string? value, out Email email)
{
email = default;
return value is not null && TryParse(value.AsSpan(), out email);
}

/// <summary>
/// Try parse a UTF8 byte span to an Email instance
/// </summary>
public static bool TryParse(ReadOnlySpan<byte> value, out Email result) =>
TryParse(Encoding.UTF8.GetString(value).AsSpan(), out result);

/// <summary>
/// Parse a Value string to an Email instance
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="ArgumentNullException"></exception>
Expand All @@ -74,15 +116,30 @@ public static Email Parse(string value) =>
? valid
: throw new InvalidOperationException($"Invalid E-mail {value}");

/// <summary>
/// Parse a Value string to an Email instance
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="ArgumentNullException"></exception>
public static Email Parse(ReadOnlySpan<char> value) =>
TryParse(value, out var valid)
? valid
: throw new InvalidOperationException($"Invalid E-mail {value}");

/// <summary>
/// Parse an UTF8 byte span to an Email instance
/// </summary>
public static Email Parse(ReadOnlySpan<byte> value) => Parse(Encoding.UTF8.GetString(value));

/// <summary>
/// Validate Email string
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool IsValid(string? value)
public static bool IsValid(ReadOnlySpan<char> value)
{
const string at = "@";
if (value is null)
if (value.IsEmptyOrWhiteSpace())
return false;

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

/// <summary>
/// Validate Email string
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool IsValid(string? value) => value is not null && IsValid(value.AsSpan());

/// <inheritdoc />
public int CompareTo(Email other) =>
string.Compare(Value, other.Value, StringComparison.OrdinalIgnoreCase);

string DebuggerDisplay() => $"EMAIL{{{Value}}}";


#if NET8_0_OR_GREATER
bool ISpanFormattable.TryFormat(
Span<char> destination, out int charsWritten,
ReadOnlySpan<char> format, IFormatProvider? provider
)
{
charsWritten = 0;
if (destination.IsEmpty) return false;

if (destination.Length < Value.Length)
return false;

charsWritten = Value.Length;
Value.CopyTo(destination);
return true;
}

bool IUtf8SpanFormattable.TryFormat(
Span<byte> utf8Destination, out int bytesWritten,
ReadOnlySpan<char> format, IFormatProvider? provider
)
{
bytesWritten = 0;
if (utf8Destination.IsEmpty) return false;
return Encoding.UTF8.TryGetBytes(Value, utf8Destination, out bytesWritten);
}

static Email IParsable<Email>.Parse(string s, IFormatProvider? provider) => Parse(s);

static bool IParsable<Email>.TryParse(string? s, IFormatProvider? provider, out Email result) =>
TryParse(s, out result);

static Email ISpanParsable<Email>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider) =>
Parse(s);

static bool ISpanParsable<Email>.TryParse(
ReadOnlySpan<char> s, IFormatProvider? provider, out Email result) =>
TryParse(s, out result);

static Email IUtf8SpanParsable<Email>.Parse(
ReadOnlySpan<byte> utf8Text, IFormatProvider? provider) => Parse(utf8Text);

static bool IUtf8SpanParsable<Email>.TryParse(ReadOnlySpan<byte> utf8Text,
IFormatProvider? provider, out Email result) => TryParse(utf8Text, out result);

static int IStringValue.ValueSize { get; } = 255;
#endif
}
4 changes: 4 additions & 0 deletions src/BrazilModels/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ public static string ToBrazilMoneyString(this decimal value, bool moneySuffix =

static class Extensions
{
public static void RemoveNonDigits(
in this Span<char> input, Span<char> result, out int written
) => RemoveNonDigits((ReadOnlySpan<char>)input, result, out written);

public static void RemoveNonDigits(
in this ReadOnlySpan<char> input,
Span<char> result, out int written
Expand Down
Loading

0 comments on commit 6187a96

Please sign in to comment.