Skip to content

Commit 34b7637

Browse files
committed
Utf8JsonReaderHelper has an overload with IUtf8JsonFactory
1 parent 7f2aeda commit 34b7637

3 files changed

Lines changed: 85 additions & 15 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#if NET9_0_OR_GREATER
2+
namespace Thinktecture.Internal;
3+
4+
/// <summary>
5+
/// This is an internal API that supports the Thinktecture.Runtime.Extensions infrastructure and not subject to
6+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
7+
/// any release. You should only use it directly in your code with extreme caution and knowing that
8+
/// doing so can result in application failures when updating to a new Thinktecture.Runtime.Extensions release.
9+
/// </summary>
10+
public interface IUtf8JsonFactory<T, TValidationError>
11+
where T : notnull
12+
where TValidationError : class, IValidationError<TValidationError>
13+
{
14+
/// <summary>
15+
/// Validates the provided <paramref name="value"/> and attempts to convert it.
16+
/// </summary>
17+
/// <param name="value">The value to validate.</param>
18+
/// <param name="provider">An optional format provider for culture-specific formatting.</param>
19+
/// <param name="result">The converted object if validation succeeds; otherwise, <c>null</c>.</param>
20+
/// <returns>A validation error if the validation fails; otherwise, <c>null</c>.</returns>
21+
TValidationError? Validate(ReadOnlySpan<char> value, IFormatProvider? provider, out T? result);
22+
}
23+
#endif
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#if NET9_0_OR_GREATER
2+
using System.Runtime.CompilerServices;
3+
4+
namespace Thinktecture.Internal;
5+
6+
/// <summary>
7+
/// This is an internal API that supports the Thinktecture.Runtime.Extensions infrastructure and not subject to
8+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
9+
/// any release. You should only use it directly in your code with extreme caution and knowing that
10+
/// doing so can result in application failures when updating to a new Thinktecture.Runtime.Extensions release.
11+
/// </summary>
12+
public readonly struct ObjectFactoryAdapter<T, TValidationError> : IUtf8JsonFactory<T, TValidationError>
13+
where T : IObjectFactory<T, ReadOnlySpan<char>, TValidationError>
14+
where TValidationError : class, IValidationError<TValidationError>
15+
{
16+
/// <inheritdoc />
17+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
18+
public TValidationError? Validate(ReadOnlySpan<char> value, IFormatProvider? provider, out T? result)
19+
=> T.Validate(value, provider, out result);
20+
}
21+
#endif

src/Thinktecture.Runtime.Extensions.Json/Internal/Utf8JsonReaderHelper.cs

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,34 @@ public static class Utf8JsonReaderHelper
3232
out T? result)
3333
where T : IObjectFactory<T, ReadOnlySpan<char>, TValidationError>
3434
where TValidationError : class, IValidationError<TValidationError>
35+
{
36+
return ValidateFromUtf8<T, TValidationError, ObjectFactoryAdapter<T, TValidationError>>(
37+
ref reader, provider, default, out result);
38+
}
39+
40+
/// <summary>
41+
/// This is an internal API that supports the Thinktecture.Runtime.Extensions infrastructure and not subject to
42+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
43+
/// any release. You should only use it directly in your code with extreme caution and knowing that
44+
/// doing so can result in application failures when updating to a new Thinktecture.Runtime.Extensions release.
45+
/// </summary>
46+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
47+
public static TValidationError? ValidateFromUtf8<T, TValidationError, TFactory>(
48+
ref Utf8JsonReader reader,
49+
IFormatProvider? provider,
50+
TFactory factory,
51+
out T? result)
52+
where T : notnull
53+
where TValidationError : class, IValidationError<TValidationError>
54+
where TFactory : struct, IUtf8JsonFactory<T, TValidationError>
3555
{
3656
// Escaped values (rarest case): CopyString handles unescaping and reassembly
3757
if (reader.ValueIsEscaped)
38-
return ValidateEscaped<T, TValidationError>(ref reader, provider, out result);
58+
return ValidateEscaped<T, TValidationError, TFactory>(ref reader, provider, factory, out result);
3959

4060
// Fragmented but not escaped: assemble bytes, then transcode
4161
if (reader.HasValueSequence)
42-
return ValidateFragmentedUnescaped<T, TValidationError>(ref reader, provider, out result);
62+
return ValidateFragmentedUnescaped<T, TValidationError, TFactory>(ref reader, provider, factory, out result);
4363

4464
// Fast path: contiguous, unescaped, short value (most common case)
4565
var utf8Bytes = reader.ValueSpan;
@@ -49,29 +69,31 @@ public static class Utf8JsonReaderHelper
4969
// Constant size enables JIT to emit a simple stack bump instead of localloc
5070
Span<char> charBuf = stackalloc char[_STACKALLOC_CHAR_THRESHOLD];
5171
var charsWritten = Encoding.UTF8.GetChars(utf8Bytes, charBuf);
52-
return T.Validate(charBuf[..charsWritten], provider, out result);
72+
return factory.Validate(charBuf[..charsWritten], provider, out result);
5373
}
5474

5575
// Large contiguous unescaped value
56-
return ValidateLargeContiguousValue<T, TValidationError>(utf8Bytes, provider, out result);
76+
return ValidateLargeContiguousValue<T, TValidationError, TFactory>(utf8Bytes, provider, factory, out result);
5777
}
5878

5979
// NoInlining: keeps the try/finally and ArrayPool machinery out of ValidateFromUtf8's inlined body,
6080
// ensuring the JIT emits compact native code for the hot stackalloc path.
6181
[MethodImpl(MethodImplOptions.NoInlining)]
62-
private static TValidationError? ValidateLargeContiguousValue<T, TValidationError>(
82+
private static TValidationError? ValidateLargeContiguousValue<T, TValidationError, TFactory>(
6383
ReadOnlySpan<byte> utf8Bytes,
6484
IFormatProvider? provider,
85+
TFactory factory,
6586
out T? result)
66-
where T : IObjectFactory<T, ReadOnlySpan<char>, TValidationError>
87+
where T : notnull
6788
where TValidationError : class, IValidationError<TValidationError>
89+
where TFactory : struct, IUtf8JsonFactory<T, TValidationError>
6890
{
6991
var rentedChars = ArrayPool<char>.Shared.Rent(utf8Bytes.Length);
7092

7193
try
7294
{
7395
var charsWritten = Encoding.UTF8.GetChars(utf8Bytes, rentedChars);
74-
return T.Validate(rentedChars.AsSpan(0, charsWritten), provider, out result);
96+
return factory.Validate(rentedChars.AsSpan(0, charsWritten), provider, out result);
7597
}
7698
finally
7799
{
@@ -82,12 +104,14 @@ public static class Utf8JsonReaderHelper
82104
// NoInlining: fragmented values are rare; isolating this keeps ValidateFromUtf8's
83105
// inlined body compact and avoids polluting the caller with ArrayPool<byte> machinery.
84106
[MethodImpl(MethodImplOptions.NoInlining)]
85-
private static TValidationError? ValidateFragmentedUnescaped<T, TValidationError>(
107+
private static TValidationError? ValidateFragmentedUnescaped<T, TValidationError, TFactory>(
86108
ref Utf8JsonReader reader,
87109
IFormatProvider? provider,
110+
TFactory factory,
88111
out T? result)
89-
where T : IObjectFactory<T, ReadOnlySpan<char>, TValidationError>
112+
where T : notnull
90113
where TValidationError : class, IValidationError<TValidationError>
114+
where TFactory : struct, IUtf8JsonFactory<T, TValidationError>
91115
{
92116
var sequence = reader.ValueSequence;
93117
var byteLength = checked((int)sequence.Length);
@@ -104,15 +128,15 @@ public static class Utf8JsonReaderHelper
104128
{
105129
Span<char> charBuf = stackalloc char[_STACKALLOC_CHAR_THRESHOLD];
106130
var charsWritten = Encoding.UTF8.GetChars(utf8Bytes, charBuf);
107-
return T.Validate(charBuf[..charsWritten], provider, out result);
131+
return factory.Validate(charBuf[..charsWritten], provider, out result);
108132
}
109133

110134
var rentedChars = ArrayPool<char>.Shared.Rent(byteLength);
111135

112136
try
113137
{
114138
var charsWritten = Encoding.UTF8.GetChars(utf8Bytes, rentedChars);
115-
return T.Validate(rentedChars.AsSpan(0, charsWritten), provider, out result);
139+
return factory.Validate(rentedChars.AsSpan(0, charsWritten), provider, out result);
116140
}
117141
finally
118142
{
@@ -127,12 +151,14 @@ public static class Utf8JsonReaderHelper
127151

128152
// NoInlining: escaped values are the rarest case; isolating this keeps the dispatch compact.
129153
[MethodImpl(MethodImplOptions.NoInlining)]
130-
private static TValidationError? ValidateEscaped<T, TValidationError>(
154+
private static TValidationError? ValidateEscaped<T, TValidationError, TFactory>(
131155
ref Utf8JsonReader reader,
132156
IFormatProvider? provider,
157+
TFactory factory,
133158
out T? result)
134-
where T : IObjectFactory<T, ReadOnlySpan<char>, TValidationError>
159+
where T : notnull
135160
where TValidationError : class, IValidationError<TValidationError>
161+
where TFactory : struct, IUtf8JsonFactory<T, TValidationError>
136162
{
137163
// CopyString handles both unescaping and reassembly of fragmented sequences
138164
var byteLength = reader.HasValueSequence
@@ -143,15 +169,15 @@ public static class Utf8JsonReaderHelper
143169
{
144170
Span<char> charBuf = stackalloc char[_STACKALLOC_CHAR_THRESHOLD];
145171
var charsWritten = reader.CopyString(charBuf);
146-
return T.Validate(charBuf[..charsWritten], provider, out result);
172+
return factory.Validate(charBuf[..charsWritten], provider, out result);
147173
}
148174

149175
var rentedChars = ArrayPool<char>.Shared.Rent(byteLength);
150176

151177
try
152178
{
153179
var charsWritten = reader.CopyString(rentedChars);
154-
return T.Validate(rentedChars.AsSpan(0, charsWritten), provider, out result);
180+
return factory.Validate(rentedChars.AsSpan(0, charsWritten), provider, out result);
155181
}
156182
finally
157183
{

0 commit comments

Comments
 (0)