< Summary

Line coverage
2%
Covered lines: 5
Uncovered lines: 166
Coverable lines: 171
Total lines: 290
Line coverage: 2.9%
Branch coverage
0%
Covered branches: 0
Total branches: 62
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Ieee754FloatingPointConverter.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Diagnostics;
 6using System.Globalization;
 7using System.Numerics;
 8using System.Text.Json.Schema;
 9
 10namespace System.Text.Json.Serialization.Converters
 11{
 12    /// <summary>
 13    /// Converter for IEEE 754 floating-point types that share the same JSON number representation,
 14    /// such as <see cref="BFloat16"/> and the IEEE 754 decimal types.
 15    /// </summary>
 16    internal sealed class Ieee754FloatingPointConverter<T> : JsonPrimitiveConverter<T>
 17        where T : struct, IFloatingPointIeee754<T>
 18    {
 19        // Values that need more room than this are formatted into a pooled buffer.
 20        // Formatting of the IEEE 754 decimal types only uses scientific notation when it is
 21        // required or more compact, so large-magnitude values expand to many digits.
 22        private const int StackBufferLength = 64;
 23        // This buffer only ever holds one of the named literals "NaN", "Infinity" or "-Infinity",
 24        // so it is sized from their maximum length rather than from any numeric format length.
 25        private const int MaxEscapedNamedLiteralLength = JsonConstants.MaximumFloatingPointConstantLength * JsonConstant
 26
 27        private readonly NumericType _numericType;
 28
 429        public Ieee754FloatingPointConverter(NumericType numericType)
 430        {
 431            _numericType = numericType;
 432            IsInternalConverterForNumberType = true;
 433        }
 34
 035        internal override bool IsIeeeFloatingPointConverter => true;
 36
 37        public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
 038        {
 039            if (options?.NumberHandling is not null and not JsonNumberHandling.Strict)
 040            {
 041                return ReadNumberWithCustomHandling(ref reader, options.NumberHandling, options);
 42            }
 43
 044            if (reader.TokenType != JsonTokenType.Number)
 045            {
 046                ThrowHelper.ThrowInvalidOperationException_ExpectedNumber(reader.TokenType);
 47            }
 48
 049            return ReadCore(ref reader, isStringValue: false);
 050        }
 51
 52        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
 053        {
 054            if (options?.NumberHandling is not null and not JsonNumberHandling.Strict)
 055            {
 056                WriteNumberWithCustomHandling(writer, value, options.NumberHandling);
 057                return;
 58            }
 59
 060            WriteCore(writer, value);
 061        }
 62
 63        internal override T ReadAsPropertyNameCore(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions 
 064        {
 065            Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);
 066            return ReadCore(ref reader, isStringValue: true);
 067        }
 68
 69        internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, T value, JsonSerializerOptions options, bo
 070        {
 071            ValidateFinite(value);
 72
 073            Span<byte> stackBuffer = stackalloc byte[StackBufferLength];
 074            byte[]? rented = Format(value, stackBuffer, out ReadOnlySpan<byte> formatted);
 075            writer.WritePropertyName(formatted);
 076            Return(rented);
 077        }
 78
 79        internal override T ReadNumberWithCustomHandling(ref Utf8JsonReader reader, JsonNumberHandling handling, JsonSer
 080        {
 081            if (reader.TokenType == JsonTokenType.String)
 082            {
 083                if ((JsonNumberHandling.AllowReadingFromString & handling) != 0)
 084                {
 085                    if (TryGetFloatingPointConstant(ref reader, out T value))
 086                    {
 087                        return value;
 88                    }
 89
 090                    return ReadCore(ref reader, isStringValue: true);
 91                }
 092                else if ((JsonNumberHandling.AllowNamedFloatingPointLiterals & handling) != 0)
 093                {
 094                    if (!TryGetFloatingPointConstant(ref reader, out T value))
 095                    {
 096                        ThrowHelper.ThrowFormatException(_numericType);
 97                    }
 98
 099                    return value;
 100                }
 0101            }
 102
 0103            if (reader.TokenType != JsonTokenType.Number)
 0104            {
 0105                ThrowHelper.ThrowInvalidOperationException_ExpectedNumber(reader.TokenType);
 106            }
 107
 0108            return ReadCore(ref reader, isStringValue: false);
 0109        }
 110
 111        internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, T value, JsonNumberHandling handling
 0112        {
 0113            if ((JsonNumberHandling.WriteAsString & handling) != 0)
 0114            {
 0115                Span<byte> stackBuffer = stackalloc byte[StackBufferLength];
 0116                byte[]? rented = Format(value, stackBuffer, out ReadOnlySpan<byte> formatted);
 0117                writer.WriteNumberValueAsStringUnescaped(formatted);
 0118                Return(rented);
 0119            }
 0120            else if ((JsonNumberHandling.AllowNamedFloatingPointLiterals & handling) != 0)
 0121            {
 0122                WriteFloatingPointConstant(writer, value);
 0123            }
 124            else
 0125            {
 0126                WriteCore(writer, value);
 0127            }
 0128        }
 129
 130        internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) =>
 0131            GetSchemaForNumericType(JsonSchemaType.Number, numberHandling, isIeeeFloatingPoint: true);
 132
 133        internal override JsonValueType GetSupportedJsonValueTypes(JsonNumberHandling numberHandling) =>
 0134            GetSupportedJsonValueTypesForNumericType(numberHandling);
 135
 136        // Reads the current token as a number. When the token is a string or property name,
 137        // non-finite results are rejected: the underlying parsers accept named literals such as "naN"
 138        // that are not valid JSON, and a string that overflows to infinity is rejected for consistency
 139        // with float and double. Number tokens are already constrained to the JSON number grammar by
 140        // the reader, so they are permitted to overflow to infinity per IEEE 754.
 141        private T ReadCore(ref Utf8JsonReader reader, bool isStringValue)
 0142        {
 0143            byte[]? rentedByteBuffer = null;
 0144            int bufferLength = reader.ValueLength;
 145
 0146            Span<byte> byteBuffer = bufferLength <= JsonConstants.StackallocByteThreshold
 0147                ? stackalloc byte[JsonConstants.StackallocByteThreshold]
 0148                : (rentedByteBuffer = ArrayPool<byte>.Shared.Rent(bufferLength));
 149
 0150            int written = reader.CopyValue(byteBuffer);
 0151            byteBuffer = byteBuffer.Slice(0, written);
 152
 0153            bool success = TryParse(byteBuffer, out T result) && (!isStringValue || T.IsFinite(result));
 0154            Return(rentedByteBuffer);
 155
 0156            if (!success)
 0157            {
 0158                ThrowHelper.ThrowFormatException(_numericType);
 159            }
 160
 0161            return result;
 0162        }
 163
 164        private static void WriteCore(Utf8JsonWriter writer, T value)
 0165        {
 0166            ValidateFinite(value);
 167
 0168            Span<byte> stackBuffer = stackalloc byte[StackBufferLength];
 0169            byte[]? rented = Format(value, stackBuffer, out ReadOnlySpan<byte> formatted);
 0170            writer.WriteRawValue(formatted);
 0171            Return(rented);
 0172        }
 173
 174        // NaN and infinity have no representation in the JSON number grammar. Reject them with the
 175        // same error Utf8JsonWriter reports for float and double, which points callers at
 176        // JsonNumberHandling.AllowNamedFloatingPointLiterals.
 177        private static void ValidateFinite(T value)
 0178        {
 0179            if (!T.IsFinite(value))
 0180            {
 0181                ThrowHelper.ThrowArgumentException_ValueNotSupported();
 182            }
 0183        }
 184
 185        private static void WriteFloatingPointConstant(Utf8JsonWriter writer, T value)
 0186        {
 0187            if (T.IsNaN(value))
 0188            {
 0189                writer.WriteNumberValueAsStringUnescaped(JsonConstants.NaNValue);
 0190            }
 0191            else if (T.IsPositiveInfinity(value))
 0192            {
 0193                writer.WriteNumberValueAsStringUnescaped(JsonConstants.PositiveInfinityValue);
 0194            }
 0195            else if (T.IsNegativeInfinity(value))
 0196            {
 0197                writer.WriteNumberValueAsStringUnescaped(JsonConstants.NegativeInfinityValue);
 0198            }
 199            else
 0200            {
 0201                WriteCore(writer, value);
 0202            }
 0203        }
 204
 205        private static bool TryGetFloatingPointConstant(ref Utf8JsonReader reader, out T value)
 0206        {
 207            scoped Span<byte> buffer;
 208
 0209            if (reader.ValueIsEscaped)
 0210            {
 0211                if (reader.ValueLength > MaxEscapedNamedLiteralLength)
 0212                {
 0213                    value = default;
 0214                    return false;
 215                }
 216
 0217                buffer = stackalloc byte[MaxEscapedNamedLiteralLength];
 0218            }
 219            else
 0220            {
 0221                if (reader.ValueLength > JsonConstants.MaximumFloatingPointConstantLength)
 0222                {
 0223                    value = default;
 0224                    return false;
 225                }
 226
 0227                buffer = stackalloc byte[JsonConstants.MaximumFloatingPointConstantLength];
 0228            }
 229
 0230            int written = reader.CopyValue(buffer);
 0231            return TryGetFloatingPointConstant(buffer.Slice(0, written), out value);
 0232        }
 233
 234        private static bool TryGetFloatingPointConstant(ReadOnlySpan<byte> span, out T value)
 0235        {
 0236            if (span.SequenceEqual(JsonConstants.NaNValue))
 0237            {
 0238                value = T.NaN;
 0239                return true;
 240            }
 241
 0242            if (span.SequenceEqual(JsonConstants.PositiveInfinityValue))
 0243            {
 0244                value = T.PositiveInfinity;
 0245                return true;
 246            }
 247
 0248            if (span.SequenceEqual(JsonConstants.NegativeInfinityValue))
 0249            {
 0250                value = T.NegativeInfinity;
 0251                return true;
 252            }
 253
 0254            value = default;
 0255            return false;
 0256        }
 257
 258        private static bool TryParse(ReadOnlySpan<byte> buffer, out T result) =>
 0259            T.TryParse(buffer, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out resul
 260
 261        // Formats value into initialBuffer, growing into a pooled buffer if necessary.
 262        // Returns the rented array, if any, which the caller must return.
 263        private static byte[]? Format(T value, Span<byte> initialBuffer, out ReadOnlySpan<byte> formatted)
 0264        {
 0265            byte[]? rented = null;
 0266            Span<byte> destination = initialBuffer;
 267            int written;
 268
 0269            while (!value.TryFormat(destination, out written, format: default, provider: CultureInfo.InvariantCulture))
 0270            {
 0271                byte[]? toReturn = rented;
 0272                rented = ArrayPool<byte>.Shared.Rent(destination.Length * 2);
 0273                destination = rented;
 0274                Return(toReturn);
 0275            }
 276
 0277            formatted = destination.Slice(0, written);
 0278            return rented;
 0279        }
 280
 281        private static void Return(byte[]? rented)
 0282        {
 0283            if (rented is not null)
 0284            {
 0285                ArrayPool<byte>.Shared.Return(rented);
 0286            }
 0287        }
 288    }
 289}
 290