Skip to content

Commit

Permalink
[v2] Add multi platform compatible json naming policy
Browse files Browse the repository at this point in the history
  • Loading branch information
henrikfroehling committed Mar 20, 2024
1 parent 29ede6e commit c31c1b6
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 6 deletions.
10 changes: 10 additions & 0 deletions src/libs/Trakt.NET/Internal/Constants.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net;
using System.Text.Json;

namespace TraktNET
{
Expand Down Expand Up @@ -75,5 +76,14 @@ internal static class StatusCodes

internal const HttpStatusCode ServiceUnavailableCloudflareError522 = (HttpStatusCode)522;
}

internal static class Json
{
#if NET8_0_OR_GREATER
internal static readonly JsonNamingPolicy NamingPolicy = JsonNamingPolicy.SnakeCaseLower;
#else
internal static readonly JsonNamingPolicy NamingPolicy = new LowerSnakeCaseJsonNamingPolicy();
#endif
}
}
}
6 changes: 3 additions & 3 deletions src/libs/Trakt.NET/Internal/Helper/ArgumentValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ internal static void ThrowIfNullOrWhiteSpace(string? value, string message, bool
}

internal static void ThrowIfNull(object? argument)
{
#if NETSTANDARD2_0 || NETSTANDARD2_1 || NET5_0
{
if (argument == null)
throw new ArgumentNullException(nameof(argument));
}
#else
ArgumentNullException.ThrowIfNull(argument);
=> ArgumentNullException.ThrowIfNull(argument);
#endif
}
}
}
181 changes: 181 additions & 0 deletions src/libs/Trakt.NET/Internal/Json/LowerSnakeCaseJsonNamingPolicy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
using System.Buffers;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text.Json;

namespace TraktNET
{
internal sealed class LowerSnakeCaseJsonNamingPolicy : JsonNamingPolicy
{
// NOTE: Content copied from
// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Text.Json/Common/JsonSeparatorNamingPolicy.cs
// and slightly modified for simplicity.

private const char separator = '_';
private const int StackallocCharThreshold = 128;

public override string ConvertName(string name)
{
ArgumentValidator.ThrowIfNull(name);

char[]? rentedBuffer = null;
ReadOnlySpan<char> chars = name.AsSpan();

// While we can't predict the expansion factor of the resultant string,
// start with a buffer that is at least 20% larger than the input.
int initialBufferLength = (int)(1.2 * chars.Length);

Span<char> destination = initialBufferLength <= StackallocCharThreshold
? stackalloc char[StackallocCharThreshold]
: (rentedBuffer = ArrayPool<char>.Shared.Rent(initialBufferLength));

SeparatorState state = SeparatorState.NotStarted;
int charsWritten = 0;

for (int i = 0; i < chars.Length; i++)
{
// NB this implementation does not handle surrogate pair letters
// cf. https://github.com/dotnet/runtime/issues/90352

char current = chars[i];
UnicodeCategory category = char.GetUnicodeCategory(current);

switch (category)
{
case UnicodeCategory.UppercaseLetter:
{
switch (state)
{
case SeparatorState.NotStarted:
break;
case SeparatorState.LowercaseLetterOrDigit:
case SeparatorState.SpaceSeparator:
{
// An uppercase letter following a sequence of lowercase letters or spaces
// denotes the start of a new grouping: emit a separator character.
WriteChar(separator, ref destination);
break;
}
case SeparatorState.UppercaseLetter:
{
// We are reading through a sequence of two or more uppercase letters.
// Uppercase letters are grouped together with the exception of the
// final letter, assuming it is followed by lowercase letters.
// For example, the value 'XMLReader' should render as 'xml_reader',
// however 'SHA512Hash' should render as 'sha512-hash'.
if (i + 1 < chars.Length && char.IsLower(chars[i + 1]))
{
WriteChar(separator, ref destination);
}

break;
}
default:
Debug.Fail($"Unexpected state {state}");
break;
}

current = char.ToLowerInvariant(current);

WriteChar(current, ref destination);
state = SeparatorState.UppercaseLetter;
break;
}
case UnicodeCategory.LowercaseLetter:
case UnicodeCategory.DecimalDigitNumber:
{

if (state is SeparatorState.SpaceSeparator)
{
// Normalize preceding spaces to one separator.
WriteChar(separator, ref destination);
}

WriteChar(current, ref destination);
state = SeparatorState.LowercaseLetterOrDigit;
break;
}
case UnicodeCategory.SpaceSeparator:
{
// Space characters are trimmed from the start and end of the input string
// but are normalized to separator characters if between letters.
if (state != SeparatorState.NotStarted)
{
state = SeparatorState.SpaceSeparator;
}

break;
}
default:
{
// Non-alphanumeric characters (including the separator character and surrogates)
// are written as-is to the output and reset the separator state.
// E.g. 'ABC???def' maps to 'abc???def' in snake_case.

WriteChar(current, ref destination);
state = SeparatorState.NotStarted;
break;
}
}
}

#if NETSTANDARD2_0
string result = destination.Slice(0, charsWritten).ToString();
#else
string result = destination[..charsWritten].ToString();
#endif

if (rentedBuffer is not null)
{
#if NETSTANDARD2_0
destination.Slice(0, charsWritten).Clear();
#else
destination[..charsWritten].Clear();
#endif
ArrayPool<char>.Shared.Return(rentedBuffer);
}

return result;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
void WriteChar(char value, ref Span<char> destination)
{
if (charsWritten == destination.Length)
{
ExpandBuffer(ref destination);
}

destination[charsWritten++] = value;
}

void ExpandBuffer(ref Span<char> destination)
{
int newSize = checked(destination.Length * 2);
char[] newBuffer = ArrayPool<char>.Shared.Rent(newSize);
destination.CopyTo(newBuffer);

if (rentedBuffer is not null)
{
#if NETSTANDARD2_0
destination.Slice(0, charsWritten).Clear();
#else
destination[..charsWritten].Clear();
#endif
ArrayPool<char>.Shared.Return(rentedBuffer);
}

rentedBuffer = newBuffer;
destination = rentedBuffer;
}
}

private enum SeparatorState
{
NotStarted,
UppercaseLetter,
LowercaseLetterOrDigit,
SpaceSeparator,
}
}
}
4 changes: 1 addition & 3 deletions src/libs/Trakt.NET/Trakt.NET.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\tools\Trakt.NET.SourceGenerators\Trakt.NET.SourceGenerators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\tools\Trakt.NET.SourceGenerators\Trakt.NET.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

</Project>

0 comments on commit c31c1b6

Please sign in to comment.