Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@ public KnownTypeSymbols(Compilation compilation)
public INamedTypeSymbol? TimeOnlyType => GetOrResolveType("System.TimeOnly", ref _TimeOnlyType);
private Option<INamedTypeSymbol?> _TimeOnlyType;

public INamedTypeSymbol? Int128Type => GetOrResolveType("System.Int128", ref _Int128Type);
private Option<INamedTypeSymbol?> _Int128Type;

public INamedTypeSymbol? UInt128Type => GetOrResolveType("System.UInt128", ref _UInt128Type);
private Option<INamedTypeSymbol?> _UInt128Type;

public INamedTypeSymbol? HalfType => GetOrResolveType("System.Half", ref _HalfType);
private Option<INamedTypeSymbol?> _HalfType;

public IArrayTypeSymbol? ByteArrayType => _ByteArrayType.HasValue
? _ByteArrayType.Value
: (_ByteArrayType = new(Compilation.CreateArrayTypeSymbol(Compilation.GetSpecialType(SpecialType.System_Byte), rank: 1))).Value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1494,6 +1494,9 @@ private static HashSet<ITypeSymbol> CreateBuiltInSupportTypeSet(KnownTypeSymbols
AddTypeIfNotNull(knownSymbols.DateTimeOffsetType);
AddTypeIfNotNull(knownSymbols.DateOnlyType);
AddTypeIfNotNull(knownSymbols.TimeOnlyType);
AddTypeIfNotNull(knownSymbols.Int128Type);
AddTypeIfNotNull(knownSymbols.UInt128Type);
AddTypeIfNotNull(knownSymbols.HalfType);
AddTypeIfNotNull(knownSymbols.GuidType);
AddTypeIfNotNull(knownSymbols.UriType);
AddTypeIfNotNull(knownSymbols.VersionType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ namespace System.Text.Json.Serialization.Metadata
public static partial class JsonMetadataServices
{
public static System.Text.Json.Serialization.JsonConverter<System.DateOnly> DateOnlyConverter { get { throw null; } }
public static System.Text.Json.Serialization.JsonConverter<System.Half> HalfConverter { get { throw null; } }
public static System.Text.Json.Serialization.JsonConverter<System.TimeOnly> TimeOnlyConverter { get { throw null; } }

#if NET7_0_OR_GREATER
public static System.Text.Json.Serialization.JsonConverter<System.Int128> Int128Converter { get { throw null; } }
[System.CLSCompliantAttribute(false)]
public static System.Text.Json.Serialization.JsonConverter<System.UInt128> UInt128Converter { get { throw null; } }
#endif
}
}
9 changes: 9 additions & 0 deletions src/libraries/System.Text.Json/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -690,4 +690,13 @@
<data name="ObjectCreationHandlingPropertyCannotAllowReferenceHandling" xml:space="preserve">
<value>JsonObjectCreationHandling.Populate is incompatible with reference handling.</value>
</data>
<data name="FormatInt128" xml:space="preserve">
<value>Either the JSON value is not in a supported format, or is out of bounds for an Int128.</value>
</data>
<data name="FormatUInt128" xml:space="preserve">
<value>Either the JSON value is not in a supported format, or is out of bounds for an UInt128.</value>
</data>
<data name="FormatHalf" xml:space="preserve">
<value>Either the JSON value is not in a supported format, or is out of bounds for a Half.</value>
</data>
</root>
6 changes: 6 additions & 0 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -345,11 +345,17 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\UnconditionalSuppressMessageAttribute.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' and $([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '7.0'))">
<Compile Include="System\Text\Json\Serialization\Converters\Value\Int128Converter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\UInt128Converter.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
<Compile Include="System.Text.Json.Typeforwards.netcoreapp.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializerOptionsUpdateHandler.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\DateOnlyConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\TimeOnlyConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\HalfConverter.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,39 @@ public static bool TryGetEscapedGuid(ReadOnlySpan<byte> source, out Guid value)
return false;
}

#if NETCOREAPP
public static bool TryGetFloatingPointConstant(ReadOnlySpan<byte> span, out Half value)
{
if (span.Length == 3)
{
if (span.SequenceEqual(JsonConstants.NaNValue))
{
value = Half.NaN;
return true;
}
}
else if (span.Length == 8)
{
if (span.SequenceEqual(JsonConstants.PositiveInfinityValue))
{
value = Half.PositiveInfinity;
return true;
}
}
else if (span.Length == 9)
{
if (span.SequenceEqual(JsonConstants.NegativeInfinityValue))
{
value = Half.NegativeInfinity;
return true;
}
}

value = default;
return false;
}
#endif

public static bool TryGetFloatingPointConstant(ReadOnlySpan<byte> span, out float value)
{
if (span.Length == 3)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ public readonly int CopyString(Span<byte> utf8Destination)
ThrowHelper.ThrowInvalidOperationException_ExpectedString(_tokenType);
}

return CopyValue(utf8Destination);
}

internal readonly int CopyValue(Span<byte> utf8Destination)
{
Debug.Assert(_tokenType is JsonTokenType.String or JsonTokenType.PropertyName or JsonTokenType.Number);
Debug.Assert(_tokenType != JsonTokenType.Number || !ValueIsEscaped, "Numbers can't contain escape characters.");

int bytesWritten;

if (ValueIsEscaped)
Expand Down Expand Up @@ -129,6 +137,14 @@ public readonly int CopyString(Span<char> destination)
ThrowHelper.ThrowInvalidOperationException_ExpectedString(_tokenType);
}

return CopyValue(destination);
}

internal readonly int CopyValue(Span<char> destination)
{
Debug.Assert(_tokenType is JsonTokenType.String or JsonTokenType.PropertyName or JsonTokenType.Number);
Debug.Assert(_tokenType != JsonTokenType.Number || !ValueIsEscaped, "Numbers can't contain escape characters.");

scoped ReadOnlySpan<byte> unescapedSource;
byte[]? rentedBuffer = null;
int valueLength;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ internal sealed class CharConverter : JsonPrimitiveConverter<char>

public override char Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType is not (JsonTokenType.String or JsonTokenType.PropertyName))
{
ThrowHelper.ThrowInvalidOperationException_ExpectedString(reader.TokenType);
}

if (!JsonHelpers.IsInRangeInclusive(reader.ValueLength, 1, MaxEscapedCharacterLength))
{
ThrowHelper.ThrowInvalidOperationException_ExpectedChar(reader.TokenType);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Diagnostics;
using System.Globalization;

namespace System.Text.Json.Serialization.Converters
{
internal sealed class HalfConverter : JsonPrimitiveConverter<Half>
{
private const int MaxFormatLength = 20;

public HalfConverter()
{
IsInternalConverterForNumberType = true;
}

public override Half Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.Number)
{
ThrowHelper.ThrowInvalidOperationException_ExpectedNumber(reader.TokenType);
}

return ReadCore(ref reader);
}

public override void Write(Utf8JsonWriter writer, Half value, JsonSerializerOptions options)
{
WriteCore(writer, value);
}

private static Half ReadCore(ref Utf8JsonReader reader)
{
Half result;

byte[]? rentedByteBuffer = null;
int bufferLength = reader.ValueLength;

Span<byte> byteBuffer = bufferLength <= JsonConstants.StackallocByteThreshold
? stackalloc byte[JsonConstants.StackallocByteThreshold]
: (rentedByteBuffer = ArrayPool<byte>.Shared.Rent(bufferLength));

int written = reader.CopyValue(byteBuffer);
byteBuffer = byteBuffer.Slice(0, written);

bool success = TryParse(byteBuffer, out result);
if (rentedByteBuffer != null)
{
ArrayPool<byte>.Shared.Return(rentedByteBuffer);
}

if (!success)
{
ThrowHelper.ThrowFormatException(NumericType.Half);
}

Debug.Assert(!Half.IsNaN(result) && !Half.IsInfinity(result));
return result;
}

private static void WriteCore(Utf8JsonWriter writer, Half value)
{
#if NET8_0_OR_GREATER
Span<byte> buffer = stackalloc byte[MaxFormatLength];
#else
Span<char> buffer = stackalloc char[MaxFormatLength];
#endif
Format(buffer, value, out int written);
writer.WriteRawValue(buffer.Slice(0, written));
}

internal override Half ReadAsPropertyNameCore(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);
return ReadCore(ref reader);
}

internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, Half value, JsonSerializerOptions options, bool isWritingExtensionDataProperty)
{
#if NET8_0_OR_GREATER
Span<byte> buffer = stackalloc byte[MaxFormatLength];
#else
Span<char> buffer = stackalloc char[MaxFormatLength];
#endif
Format(buffer, value, out int written);
writer.WritePropertyName(buffer.Slice(0, written));
}

internal override Half ReadNumberWithCustomHandling(ref Utf8JsonReader reader, JsonNumberHandling handling, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
if ((JsonNumberHandling.AllowReadingFromString & handling) != 0)
{
if (TryGetFloatingPointConstant(ref reader, out Half value))
{
return value;
}

return ReadCore(ref reader);
}
else if ((JsonNumberHandling.AllowNamedFloatingPointLiterals & handling) != 0)
{
if (!TryGetFloatingPointConstant(ref reader, out Half value))
{
ThrowHelper.ThrowFormatException(NumericType.Half);
}

return value;
}
}

return Read(ref reader, Type, options);
}

internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, Half value, JsonNumberHandling handling)
{
if ((JsonNumberHandling.WriteAsString & handling) != 0)
{
#if NET8_0_OR_GREATER
const byte Quote = JsonConstants.Quote;
Span<byte> buffer = stackalloc byte[MaxFormatLength + 2];
#else
const char Quote = (char)JsonConstants.Quote;
Span<char> buffer = stackalloc char[MaxFormatLength + 2];
#endif
buffer[0] = Quote;
Format(buffer.Slice(1), value, out int written);

int length = written + 2;
buffer[length - 1] = Quote;
writer.WriteRawValue(buffer.Slice(0, length));
}
else if ((JsonNumberHandling.AllowNamedFloatingPointLiterals & handling) != 0)
{
WriteFloatingPointConstant(writer, value);
}
else
{
WriteCore(writer, value);
}
}

private static bool TryGetFloatingPointConstant(ref Utf8JsonReader reader, out Half value)
{
Span<byte> buffer = stackalloc byte[MaxFormatLength];
int written = reader.CopyValue(buffer);

return JsonReaderHelper.TryGetFloatingPointConstant(buffer.Slice(0, written), out value);
}

private static void WriteFloatingPointConstant(Utf8JsonWriter writer, Half value)
{
if (Half.IsNaN(value))
{
writer.WriteNumberValueAsStringUnescaped(JsonConstants.NaNValue);
}
else if (Half.IsPositiveInfinity(value))
{
writer.WriteNumberValueAsStringUnescaped(JsonConstants.PositiveInfinityValue);
}
else if (Half.IsNegativeInfinity(value))
{
writer.WriteNumberValueAsStringUnescaped(JsonConstants.NegativeInfinityValue);
}
else
{
WriteCore(writer, value);
}
}

private static bool TryParse(ReadOnlySpan<byte> buffer, out Half result)
{
#if NET8_0_OR_GREATER
bool success = Half.TryParse(buffer, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result);
#else
// Half.TryFormat/TryParse(ROS<byte>) are not available on .NET 7
// we need to use Half.TryFormat/TryParse(ROS<char>) in that case.
char[]? rentedCharBuffer = null;

Span<char> charBuffer = buffer.Length <= JsonConstants.StackallocCharThreshold
? stackalloc char[JsonConstants.StackallocCharThreshold]
: (rentedCharBuffer = ArrayPool<char>.Shared.Rent(buffer.Length));

int written = JsonReaderHelper.TranscodeHelper(buffer, charBuffer);

bool success = Half.TryParse(charBuffer, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result);

if (rentedCharBuffer != null)
{
ArrayPool<char>.Shared.Return(rentedCharBuffer);
}
#endif

// Half.TryParse is more lax with floating-point literals than other S.T.Json floating-point types
// e.g: it parses "naN" successfully. Only succeed with the exact match.
return success &&
(!Half.IsNaN(result) || buffer.SequenceEqual(JsonConstants.NaNValue)) &&
(!Half.IsPositiveInfinity(result) || buffer.SequenceEqual(JsonConstants.PositiveInfinityValue)) &&
(!Half.IsNegativeInfinity(result) || buffer.SequenceEqual(JsonConstants.NegativeInfinityValue));
}

private static void Format(
#if NET8_0_OR_GREATER
Span<byte> destination,
#else
Span<char> destination,
#endif
Half value, out int written)
{
bool formattedSuccessfully = value.TryFormat(destination, out written, provider: CultureInfo.InvariantCulture);
Debug.Assert(formattedSuccessfully);
}
}
}
Loading