File: System\Text\Json\Writer\Utf8JsonWriter.WriteValues.Helpers.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.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
 
namespace System.Text.Json
{
    public sealed partial class Utf8JsonWriter
    {
        /// <summary>
        /// Returns whether a JSON value can be written at the current position based on the current <see cref="_enclosingContainer"/>:
        /// <list type="bullet">
        /// <item>
        /// <see cref="EnclosingContainerType.Array"/>: Writing a value is always allowed.
        /// </item>
        /// <item>
        /// <see cref="EnclosingContainerType.Object"/>: Writing a value is allowed only if <see cref="_tokenType"/> is a property name.
        /// Because we designed <see cref="EnclosingContainerType.Object"/> == <see cref="JsonTokenType.PropertyName"/>, we can just check for equality.
        /// </item>
        /// <item>
        /// <see cref="EnclosingContainerType.None"/>: Writing a value is allowed only if <see cref="_tokenType"/> is None (only one value may be written at the root).
        /// This case is identical to the previous case.
        /// </item>
        /// <item>
        /// <see cref="EnclosingContainerType.Utf8StringSequence"/>, <see cref="EnclosingContainerType.Utf16StringSequence"/>, <see cref="EnclosingContainerType.Base64StringSequence"/>:
        /// Writing a value is never valid and <see cref="_enclosingContainer"/> does not equal any <see cref="JsonTokenType"/> by construction.
        /// </item>
        /// </list>
        /// This method performs better without short circuiting (this often gets inlined so using simple branch free code seems to have some benefits).
        /// </summary>
        private bool CanWriteValue => _enclosingContainer == EnclosingContainerType.Array | (byte)_enclosingContainer == (byte)_tokenType;
 
        private bool HasPartialStringData => _partialStringDataLength != 0;
 
        private void ClearPartialStringData() => _partialStringDataLength = 0;
 
        private void ValidateWritingValue()
        {
            if (!CanWriteValue)
            {
                OnValidateWritingValueFailed();
            }
        }
 
        [DoesNotReturn]
        [MethodImpl(MethodImplOptions.NoInlining)]
        private void OnValidateWritingValueFailed()
        {
            Debug.Assert(!_options.SkipValidation);
 
            if (IsWritingPartialString)
            {
                ThrowInvalidOperationException(ExceptionResource.CannotWriteWithinString);
            }
 
            Debug.Assert(!HasPartialStringData);
 
            if (_enclosingContainer == EnclosingContainerType.Object)
            {
                Debug.Assert(_tokenType != JsonTokenType.PropertyName);
                Debug.Assert(_tokenType != JsonTokenType.None && _tokenType != JsonTokenType.StartArray);
                ThrowInvalidOperationException(ExceptionResource.CannotWriteValueWithinObject);
            }
            else
            {
                Debug.Assert(_tokenType != JsonTokenType.PropertyName);
                Debug.Assert(CurrentDepth == 0 && _tokenType != JsonTokenType.None);
                ThrowInvalidOperationException(ExceptionResource.CannotWriteValueAfterPrimitiveOrClose);
            }
        }
 
        private void ValidateWritingSegment(EnclosingContainerType currentSegmentEncoding)
        {
            Debug.Assert(currentSegmentEncoding is EnclosingContainerType.Utf8StringSequence or EnclosingContainerType.Utf16StringSequence or EnclosingContainerType.Base64StringSequence);
 
            // A string segment can be written if either:
            // 1) The writer is currently in a partial string of the same type. In this case the new segment
            // will continue the partial string.
            // - or -
            // 2) The writer can write a value at the current position, in which case a new string can be started.
            if (_enclosingContainer != currentSegmentEncoding && !CanWriteValue)
            {
                OnValidateWritingSegmentFailed(currentSegmentEncoding);
            }
        }
 
        [DoesNotReturn]
        [MethodImpl(MethodImplOptions.NoInlining)]
        private void OnValidateWritingSegmentFailed(EnclosingContainerType currentSegmentEncoding)
        {
            if (IsWritingPartialString)
            {
                ThrowHelper.ThrowInvalidOperationException_CannotMixEncodings(_enclosingContainer, currentSegmentEncoding);
            }
 
            Debug.Assert(!HasPartialStringData);
 
            if (_enclosingContainer == EnclosingContainerType.Object)
            {
                Debug.Assert(_tokenType != JsonTokenType.PropertyName);
                Debug.Assert(_tokenType != JsonTokenType.None && _tokenType != JsonTokenType.StartArray);
                ThrowInvalidOperationException(ExceptionResource.CannotWriteValueWithinObject);
            }
            else
            {
                Debug.Assert(_tokenType != JsonTokenType.PropertyName);
                Debug.Assert(CurrentDepth == 0 && _tokenType != JsonTokenType.None);
                ThrowInvalidOperationException(ExceptionResource.CannotWriteValueAfterPrimitiveOrClose);
            }
        }
 
        private void ValidateNotWithinUnfinalizedString()
        {
            if (IsWritingPartialString)
            {
                ThrowInvalidOperationException(ExceptionResource.CannotWriteWithinString);
            }
 
            Debug.Assert(!HasPartialStringData);
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void Base64EncodeAndWrite(ReadOnlySpan<byte> bytes, Span<byte> output)
        {
            Span<byte> destination = output.Slice(BytesPending);
            OperationStatus status = Base64.EncodeToUtf8(bytes, destination, out int consumed, out int written);
            Debug.Assert(status == OperationStatus.Done);
            Debug.Assert(consumed == bytes.Length);
            BytesPending += written;
        }
    }
}