| File: System\Text\Json\Writer\Utf8JsonWriter.WriteValues.String.cs | Web Access |
| Project: src\runtime\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.Diagnostics; namespace System.Text.Json { public sealed partial class Utf8JsonWriter { /// <summary> /// Writes the pre-encoded text value (as a JSON string) as an element of a JSON array. /// </summary> /// <param name="value">The JSON-encoded value to write.</param> /// <exception cref="InvalidOperationException"> /// Thrown if this would result in invalid JSON being written (while validation is enabled). /// </exception> public void WriteStringValue(JsonEncodedText value) { ReadOnlySpan<byte> utf8Value = value.EncodedUtf8Bytes; Debug.Assert(utf8Value.Length <= JsonConstants.MaxUnescapedTokenSize); WriteStringByOptions(utf8Value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; } /// <summary> /// Writes the string text value (as a JSON string) as an element of a JSON array. /// </summary> /// <param name="value">The value to write.</param> /// <exception cref="ArgumentException"> /// Thrown when the specified value is too large. /// </exception> /// <exception cref="InvalidOperationException"> /// Thrown if this would result in invalid JSON being written (while validation is enabled). /// </exception> /// <remarks> /// <para> /// The value is escaped before writing.</para> /// <para> /// If <paramref name="value"/> is <see langword="null"/> the JSON null value is written, /// as if <see cref="WriteNullValue"/> was called. /// </para> /// </remarks> public void WriteStringValue(string? value) { if (value is null) { WriteNullValue(); } else { WriteStringValue(value.AsSpan()); } } /// <summary> /// Writes the text value (as a JSON string) as an element of a JSON array. /// </summary> /// <param name="value">The value to write.</param> /// <exception cref="ArgumentException"> /// Thrown when the specified value is too large. /// </exception> /// <exception cref="InvalidOperationException"> /// Thrown if this would result in invalid JSON being written (while validation is enabled). /// </exception> /// <remarks> /// The value is escaped before writing. /// </remarks> public void WriteStringValue(ReadOnlySpan<char> value) { JsonWriterHelper.ValidateValue(value); WriteStringEscape(value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; } private void WriteStringEscape(ReadOnlySpan<char> value) { int valueIdx = JsonWriterHelper.NeedsEscaping(value, _options.Encoder); Debug.Assert(valueIdx >= -1 && valueIdx < value.Length); if (valueIdx != -1) { WriteStringEscapeValue(value, valueIdx); } else { // Each input char may transcode to up to 3 bytes. WriteStringByOptions(value, value.Length * JsonConstants.MaxExpansionFactorWhileTranscoding); } } private void WriteStringByOptions(ReadOnlySpan<char> value, int maxRequiredBytes) { if (!_options.SkipValidation) { ValidateWritingValue(); } if (_options.Indented) { WriteStringIndented(value, maxRequiredBytes); } else { WriteStringMinimized(value, maxRequiredBytes); } } // TODO: https://github.com/dotnet/runtime/issues/29293 private void WriteStringMinimized(ReadOnlySpan<char> escapedValue, int maxRequiredBytes) { Debug.Assert(maxRequiredBytes is >= 0 and < int.MaxValue - 3); // 2 quotes + optional 1 list separator, plus precomputed max bytes for the payload. int maxRequired = maxRequiredBytes + 3; if (_memory.Length - BytesPending < maxRequired) { Grow(maxRequired); } Span<byte> output = _memory.Span; if (_currentDepth < 0) { output[BytesPending++] = JsonConstants.ListSeparator; } output[BytesPending++] = JsonConstants.Quote; TranscodeAndWrite(escapedValue, output); output[BytesPending++] = JsonConstants.Quote; } // TODO: https://github.com/dotnet/runtime/issues/29293 private void WriteStringIndented(ReadOnlySpan<char> escapedValue, int maxRequiredBytes) { int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(maxRequiredBytes >= 0 && maxRequiredBytes < int.MaxValue - indent - 3 - _newLineLength); // indent + 2 quotes + optional 1 list separator + 1-2 bytes for new line, plus precomputed max bytes for the payload. int maxRequired = indent + maxRequiredBytes + 3 + _newLineLength; 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; } output[BytesPending++] = JsonConstants.Quote; TranscodeAndWrite(escapedValue, output); output[BytesPending++] = JsonConstants.Quote; } private unsafe void WriteStringEscapeValue(ReadOnlySpan<char> value, int firstEscapeIndexVal) { Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= value.Length); Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < value.Length); char[]? valueArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(value.Length, firstEscapeIndexVal); Span<char> escapedValue = length <= JsonConstants.StackallocCharThreshold ? stackalloc char[JsonConstants.StackallocCharThreshold] : (valueArray = ArrayPool<char>.Shared.Rent(length)); JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, _options.Encoder, out int written); // Each original input char expands to at most MaxExpansionFactorWhileEscaping bytes to the output. // Escaped sequences are all ASCII (1 byte each), so × 6 ≥ transcoded bytes. int requiredBytes = value.Length * JsonConstants.MaxExpansionFactorWhileEscaping; WriteStringByOptions(escapedValue.Slice(0, written), requiredBytes); if (valueArray is not null) { ArrayPool<char>.Shared.Return(valueArray); } } /// <summary> /// Writes the UTF-8 text value (as a JSON string) as an element of a JSON array. /// </summary> /// <param name="utf8Value">The UTF-8 encoded value to be written as a JSON string element of a JSON array.</param> /// <exception cref="ArgumentException"> /// Thrown when the specified value is too large. /// </exception> /// <exception cref="InvalidOperationException"> /// Thrown if this would result in invalid JSON being written (while validation is enabled). /// </exception> /// <remarks> /// The value is escaped before writing. /// </remarks> public void WriteStringValue(ReadOnlySpan<byte> utf8Value) { JsonWriterHelper.ValidateValue(utf8Value); WriteStringEscape(utf8Value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; } private void WriteStringEscape(ReadOnlySpan<byte> utf8Value) { int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value, _options.Encoder); Debug.Assert(valueIdx >= -1 && valueIdx < utf8Value.Length); if (valueIdx != -1) { WriteStringEscapeValue(utf8Value, valueIdx); } else { WriteStringByOptions(utf8Value); } } private void WriteStringByOptions(ReadOnlySpan<byte> utf8Value) { if (!_options.SkipValidation) { ValidateWritingValue(); } if (_options.Indented) { WriteStringIndented(utf8Value); } else { WriteStringMinimized(utf8Value); } } // TODO: https://github.com/dotnet/runtime/issues/29293 private void WriteStringMinimized(ReadOnlySpan<byte> escapedValue) { Debug.Assert(escapedValue.Length < int.MaxValue - 3); int minRequired = escapedValue.Length + 2; // 2 quotes int maxRequired = minRequired + 1; // Optionally, 1 list separator if (_memory.Length - BytesPending < maxRequired) { Grow(maxRequired); } Span<byte> output = _memory.Span; if (_currentDepth < 0) { output[BytesPending++] = JsonConstants.ListSeparator; } output[BytesPending++] = JsonConstants.Quote; escapedValue.CopyTo(output.Slice(BytesPending)); BytesPending += escapedValue.Length; output[BytesPending++] = JsonConstants.Quote; } // TODO: https://github.com/dotnet/runtime/issues/29293 private void WriteStringIndented(ReadOnlySpan<byte> escapedValue) { int indent = Indentation; Debug.Assert(indent <= _indentLength * _options.MaxDepth); Debug.Assert(escapedValue.Length < int.MaxValue - indent - 3 - _newLineLength); int minRequired = indent + escapedValue.Length + 2; // 2 quotes int maxRequired = minRequired + 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; } output[BytesPending++] = JsonConstants.Quote; escapedValue.CopyTo(output.Slice(BytesPending)); BytesPending += escapedValue.Length; output[BytesPending++] = JsonConstants.Quote; } private unsafe void WriteStringEscapeValue(ReadOnlySpan<byte> utf8Value, int firstEscapeIndexVal) { Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length); Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length); byte[]? valueArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal); Span<byte> escapedValue = length <= JsonConstants.StackallocByteThreshold ? stackalloc byte[JsonConstants.StackallocByteThreshold] : (valueArray = ArrayPool<byte>.Shared.Rent(length)); JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, _options.Encoder, out int written); WriteStringByOptions(escapedValue.Slice(0, written)); if (valueArray is not null) { ArrayPool<byte>.Shared.Return(valueArray); } } /// <summary> /// Writes a number as a JSON string. The string value is not escaped. /// </summary> /// <param name="utf8Value"></param> internal void WriteNumberValueAsStringUnescaped(ReadOnlySpan<byte> utf8Value) { // The value has been validated prior to calling this method. WriteStringByOptions(utf8Value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; } } }