File: System\Text\Json\Writer\Utf8JsonWriter.WriteValues.Double.cs
Web Access
Project: src\src\libraries\System.Text.Json\src\System.Text.Json.csproj (System.Text.Json)
// 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.Buffers.Text;
using System.Diagnostics;
using System.Globalization;
 
namespace System.Text.Json
{
    public sealed partial class Utf8JsonWriter
    {
        /// <summary>
        /// Writes the <see cref="double"/> value (as a JSON number) as an element of a JSON array.
        /// </summary>
        /// <param name="value">The value to write.</param>
        /// <exception cref="InvalidOperationException">
        /// Thrown if this would result in invalid JSON being written (while validation is enabled).
        /// </exception>
        /// <remarks>
        /// Writes the <see cref="double"/> using the default <see cref="StandardFormat"/> on .NET Core 3 or higher
        /// and 'G17' on any other framework.
        /// </remarks>
        public void WriteNumberValue(double value)
        {
            JsonWriterHelper.ValidateDouble(value);
 
            if (!_options.SkipValidation)
            {
                ValidateWritingValue();
            }
 
            if (_options.Indented)
            {
                WriteNumberValueIndented(value);
            }
            else
            {
                WriteNumberValueMinimized(value);
            }
 
            SetFlagToAddListSeparatorBeforeNextItem();
            _tokenType = JsonTokenType.Number;
        }
 
        private void WriteNumberValueMinimized(double value)
        {
            int maxRequired = JsonConstants.MaximumFormatDoubleLength + 1; // Optionally, 1 list separator
 
            if (_memory.Length - BytesPending < maxRequired)
            {
                Grow(maxRequired);
            }
 
            Span<byte> output = _memory.Span;
 
            if (_currentDepth < 0)
            {
                output[BytesPending++] = JsonConstants.ListSeparator;
            }
 
            bool result = TryFormatDouble(value, output.Slice(BytesPending), out int bytesWritten);
            Debug.Assert(result);
            BytesPending += bytesWritten;
        }
 
        private void WriteNumberValueIndented(double value)
        {
            int indent = Indentation;
            Debug.Assert(indent <= _indentLength * _options.MaxDepth);
 
            int maxRequired = indent + JsonConstants.MaximumFormatDoubleLength + 1 + _newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
 
            if (_memory.Length - BytesPending < maxRequired)
            {
                Grow(maxRequired);
            }
 
            Span<byte> output = _memory.Span;
 
            if (_currentDepth < 0)
            {
                output[BytesPending++] = JsonConstants.ListSeparator;
            }
 
            if (_tokenType != JsonTokenType.PropertyName)
            {
                if (_tokenType != JsonTokenType.None)
                {
                    WriteNewLine(output);
                }
                WriteIndentation(output.Slice(BytesPending), indent);
                BytesPending += indent;
            }
 
            bool result = TryFormatDouble(value, output.Slice(BytesPending), out int bytesWritten);
            Debug.Assert(result);
            BytesPending += bytesWritten;
        }
 
        private static bool TryFormatDouble(double value, Span<byte> destination, out int bytesWritten)
        {
            // Frameworks that are not .NET Core 3.0 or higher do not produce roundtrippable strings by
            // default. Further, the Utf8Formatter on older frameworks does not support taking a precision
            // specifier for 'G' nor does it represent other formats such as 'R'. As such, we duplicate
            // the .NET Core 3.0 logic of forwarding to the UTF16 formatter and transcoding it back to UTF8,
            // with some additional changes to remove dependencies on Span APIs which don't exist downlevel.
 
#if NET
            return Utf8Formatter.TryFormat(value, destination, out bytesWritten);
#else
            string utf16Text = value.ToString(JsonConstants.DoubleFormatString, CultureInfo.InvariantCulture);
 
            // Copy the value to the destination, if it's large enough.
 
            if (utf16Text.Length > destination.Length)
            {
                bytesWritten = 0;
                return false;
            }
 
            try
            {
                byte[] bytes = Encoding.UTF8.GetBytes(utf16Text);
 
                if (bytes.Length > destination.Length)
                {
                    bytesWritten = 0;
                    return false;
                }
 
                bytes.CopyTo(destination);
                bytesWritten = bytes.Length;
 
                return true;
            }
            catch
            {
                bytesWritten = 0;
                return false;
            }
#endif
        }
 
        internal void WriteNumberValueAsString(double value)
        {
            Span<byte> utf8Number = stackalloc byte[JsonConstants.MaximumFormatDoubleLength];
            bool result = TryFormatDouble(value, utf8Number, out int bytesWritten);
            Debug.Assert(result);
            WriteNumberValueAsStringUnescaped(utf8Number.Slice(0, bytesWritten));
        }
 
        internal void WriteFloatingPointConstant(double value)
        {
            if (double.IsNaN(value))
            {
                WriteNumberValueAsStringUnescaped(JsonConstants.NaNValue);
            }
            else if (double.IsPositiveInfinity(value))
            {
                WriteNumberValueAsStringUnescaped(JsonConstants.PositiveInfinityValue);
            }
            else if (double.IsNegativeInfinity(value))
            {
                WriteNumberValueAsStringUnescaped(JsonConstants.NegativeInfinityValue);
            }
            else
            {
                WriteNumberValue(value);
            }
        }
    }
}