1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
5 using System.Buffers.Text;
6 using System.Diagnostics;
7 using System.Runtime.CompilerServices;
9 namespace System.Text.Json
11 public sealed partial class Utf8JsonWriter
14 /// Writes the pre-encoded property name and the JSON literal "null" as part of a name/value pair of a JSON object.
16 /// <param name="propertyName">The JSON-encoded name of the property to write.</param>
17 /// <exception cref="InvalidOperationException">
18 /// Thrown if this would result in invalid JSON being written (while validation is enabled).
20 public void WriteNull(JsonEncodedText propertyName)
22 WriteLiteralHelper(propertyName.EncodedUtf8Bytes, JsonConstants.NullValue);
23 _tokenType = JsonTokenType.Null;
26 internal void WriteNullSection(ReadOnlySpan<byte> escapedPropertyNameSection)
28 if (_options.Indented)
30 ReadOnlySpan<byte> escapedName =
31 escapedPropertyNameSection.Slice(1, escapedPropertyNameSection.Length - 3);
33 WriteLiteralHelper(escapedName, JsonConstants.NullValue);
34 _tokenType = JsonTokenType.Null;
38 Debug.Assert(escapedPropertyNameSection.Length <= JsonConstants.MaxUnescapedTokenSize - 3);
40 ReadOnlySpan<byte> span = JsonConstants.NullValue;
42 WriteLiteralSection(escapedPropertyNameSection, span);
44 SetFlagToAddListSeparatorBeforeNextItem();
45 _tokenType = JsonTokenType.Null;
49 private void WriteLiteralHelper(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value)
51 Debug.Assert(utf8PropertyName.Length <= JsonConstants.MaxUnescapedTokenSize);
53 WriteLiteralByOptions(utf8PropertyName, value);
55 SetFlagToAddListSeparatorBeforeNextItem();
59 /// Writes the property name and the JSON literal "null" as part of a name/value pair of a JSON object.
61 /// <param name="propertyName">The name of the property to write.</param>
62 /// <exception cref="ArgumentException">
63 /// Thrown when the specified property name is too large.
65 /// <exception cref="ArgumentNullException">
66 /// The <paramref name="propertyName"/> parameter is <see langword="null"/>.
68 /// <exception cref="InvalidOperationException">
69 /// Thrown if this would result in invalid JSON being written (while validation is enabled).
72 /// The property name is escaped before writing.
74 public void WriteNull(string propertyName)
76 if (propertyName is null)
78 ThrowHelper.ThrowArgumentNullException(nameof(propertyName));
80 WriteNull(propertyName.AsSpan());
84 /// Writes the property name and the JSON literal "null" as part of a name/value pair of a JSON object.
86 /// <param name="propertyName">The name of the property to write.</param>
87 /// <exception cref="ArgumentException">
88 /// Thrown when the specified property name is too large.
90 /// <exception cref="InvalidOperationException">
91 /// Thrown if this would result in invalid JSON being written (while validation is enabled).
94 /// The property name is escaped before writing.
96 public void WriteNull(ReadOnlySpan<char> propertyName)
98 JsonWriterHelper.ValidateProperty(propertyName);
100 ReadOnlySpan<byte> span = JsonConstants.NullValue;
102 WriteLiteralEscape(propertyName, span);
104 SetFlagToAddListSeparatorBeforeNextItem();
105 _tokenType = JsonTokenType.Null;
109 /// Writes the property name and the JSON literal "null" as part of a name/value pair of a JSON object.
111 /// <param name="utf8PropertyName">The UTF-8 encoded name of the property to write.</param>
112 /// <exception cref="ArgumentException">
113 /// Thrown when the specified property name is too large.
115 /// <exception cref="InvalidOperationException">
116 /// Thrown if this would result in invalid JSON being written (while validation is enabled).
119 /// The property name is escaped before writing.
121 public void WriteNull(ReadOnlySpan<byte> utf8PropertyName)
123 JsonWriterHelper.ValidateProperty(utf8PropertyName);
125 ReadOnlySpan<byte> span = JsonConstants.NullValue;
127 WriteLiteralEscape(utf8PropertyName, span);
129 SetFlagToAddListSeparatorBeforeNextItem();
130 _tokenType = JsonTokenType.Null;
134 /// Writes the pre-encoded property name and <see cref="bool"/> value (as a JSON literal "true" or "false") as part of a name/value pair of a JSON object.
136 /// <param name="propertyName">The JSON-encoded name of the property to write.</param>
137 /// <param name="value">The value to write.</param>
138 /// <exception cref="InvalidOperationException">
139 /// Thrown if this would result in invalid JSON being written (while validation is enabled).
141 public void WriteBoolean(JsonEncodedText propertyName, bool value)
145 WriteLiteralHelper(propertyName.EncodedUtf8Bytes, JsonConstants.TrueValue);
146 _tokenType = JsonTokenType.True;
150 WriteLiteralHelper(propertyName.EncodedUtf8Bytes, JsonConstants.FalseValue);
151 _tokenType = JsonTokenType.False;
156 /// Writes the property name and <see cref="bool"/> value (as a JSON literal "true" or "false") as part of a name/value pair of a JSON object.
158 /// <param name="propertyName">The name of the property to write.</param>
159 /// <param name="value">The value to write.</param>
160 /// <exception cref="ArgumentException">
161 /// Thrown when the specified property name is too large.
163 /// <exception cref="ArgumentNullException">
164 /// The <paramref name="propertyName"/> parameter is <see langword="null"/>.
166 /// <exception cref="InvalidOperationException">
167 /// Thrown if this would result in invalid JSON being written (while validation is enabled).
170 /// The property name is escaped before writing.
172 public void WriteBoolean(string propertyName, bool value)
174 if (propertyName is null)
176 ThrowHelper.ThrowArgumentNullException(nameof(propertyName));
178 WriteBoolean(propertyName.AsSpan(), value);
182 /// Writes the property name and <see cref="bool"/> value (as a JSON literal "true" or "false") as part of a name/value pair of a JSON object.
184 /// <param name="propertyName">The name of the property to write.</param>
185 /// <param name="value">The value to write.</param>
186 /// <exception cref="ArgumentException">
187 /// Thrown when the specified property name is too large.
189 /// <exception cref="InvalidOperationException">
190 /// Thrown if this would result in invalid JSON being written (while validation is enabled).
193 /// The property name is escaped before writing.
195 public void WriteBoolean(ReadOnlySpan<char> propertyName, bool value)
197 JsonWriterHelper.ValidateProperty(propertyName);
199 ReadOnlySpan<byte> span = value ? JsonConstants.TrueValue : JsonConstants.FalseValue;
201 WriteLiteralEscape(propertyName, span);
203 SetFlagToAddListSeparatorBeforeNextItem();
204 _tokenType = value ? JsonTokenType.True : JsonTokenType.False;
208 /// Writes the property name and <see cref="bool"/> value (as a JSON literal "true" or "false") as part of a name/value pair of a JSON object.
210 /// <param name="utf8PropertyName">The UTF-8 encoded name of the property to write.</param>
211 /// <param name="value">The value to write.</param>
212 /// <exception cref="ArgumentException">
213 /// Thrown when the specified property name is too large.
215 /// <exception cref="InvalidOperationException">
216 /// Thrown if this would result in invalid JSON being written (while validation is enabled).
219 /// The property name is escaped before writing.
221 public void WriteBoolean(ReadOnlySpan<byte> utf8PropertyName, bool value)
223 JsonWriterHelper.ValidateProperty(utf8PropertyName);
225 ReadOnlySpan<byte> span = value ? JsonConstants.TrueValue : JsonConstants.FalseValue;
227 WriteLiteralEscape(utf8PropertyName, span);
229 SetFlagToAddListSeparatorBeforeNextItem();
230 _tokenType = value ? JsonTokenType.True : JsonTokenType.False;
233 private void WriteLiteralEscape(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value)
235 int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
237 Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
239 if (propertyIdx != -1)
241 WriteLiteralEscapeProperty(propertyName, value, propertyIdx);
245 WriteLiteralByOptions(propertyName, value);
249 private void WriteLiteralEscape(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value)
251 int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
253 Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
255 if (propertyIdx != -1)
257 WriteLiteralEscapeProperty(utf8PropertyName, value, propertyIdx);
261 WriteLiteralByOptions(utf8PropertyName, value);
265 private void WriteLiteralEscapeProperty(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value, int firstEscapeIndexProp)
267 Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= propertyName.Length);
268 Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < propertyName.Length);
270 char[]? propertyArray = null;
272 int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);
274 Span<char> escapedPropertyName = length <= JsonConstants.StackallocCharThreshold ?
275 stackalloc char[JsonConstants.StackallocCharThreshold] :
276 (propertyArray = ArrayPool<char>.Shared.Rent(length));
278 JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
280 WriteLiteralByOptions(escapedPropertyName.Slice(0, written), value);
282 if (propertyArray != null)
284 ArrayPool<char>.Shared.Return(propertyArray);
288 private void WriteLiteralEscapeProperty(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value, int firstEscapeIndexProp)
290 Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length);
291 Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < utf8PropertyName.Length);
293 byte[]? propertyArray = null;
295 int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);
297 Span<byte> escapedPropertyName = length <= JsonConstants.StackallocByteThreshold ?
298 stackalloc byte[JsonConstants.StackallocByteThreshold] :
299 (propertyArray = ArrayPool<byte>.Shared.Rent(length));
301 JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
303 WriteLiteralByOptions(escapedPropertyName.Slice(0, written), value);
305 if (propertyArray != null)
307 ArrayPool<byte>.Shared.Return(propertyArray);
311 private void WriteLiteralByOptions(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value)
313 ValidateWritingProperty();
314 if (_options.Indented)
316 WriteLiteralIndented(propertyName, value);
320 WriteLiteralMinimized(propertyName, value);
324 private void WriteLiteralByOptions(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value)
326 ValidateWritingProperty();
327 if (_options.Indented)
329 WriteLiteralIndented(utf8PropertyName, value);
333 WriteLiteralMinimized(utf8PropertyName, value);
337 private void WriteLiteralMinimized(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> value)
339 Debug.Assert(value.Length <= JsonConstants.MaxUnescapedTokenSize);
340 Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - value.Length - 4);
342 // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + value.Length + 3
343 // Optionally, 1 list separator, and up to 3x growth when transcoding
344 int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + value.Length + 4;
346 if (_memory.Length - BytesPending < maxRequired)
351 Span<byte> output = _memory.Span;
353 if (_currentDepth < 0)
355 output[BytesPending++] = JsonConstants.ListSeparator;
357 output[BytesPending++] = JsonConstants.Quote;
359 TranscodeAndWrite(escapedPropertyName, output);
361 output[BytesPending++] = JsonConstants.Quote;
362 output[BytesPending++] = JsonConstants.KeyValueSeperator;
364 value.CopyTo(output.Slice(BytesPending));
365 BytesPending += value.Length;
368 private void WriteLiteralMinimized(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> value)
370 Debug.Assert(value.Length <= JsonConstants.MaxUnescapedTokenSize);
371 Debug.Assert(escapedPropertyName.Length < int.MaxValue - value.Length - 4);
373 int minRequired = escapedPropertyName.Length + value.Length + 3; // 2 quotes for property name, and 1 colon
374 int maxRequired = minRequired + 1; // Optionally, 1 list separator
376 if (_memory.Length - BytesPending < maxRequired)
381 Span<byte> output = _memory.Span;
383 if (_currentDepth < 0)
385 output[BytesPending++] = JsonConstants.ListSeparator;
387 output[BytesPending++] = JsonConstants.Quote;
389 escapedPropertyName.CopyTo(output.Slice(BytesPending));
390 BytesPending += escapedPropertyName.Length;
392 output[BytesPending++] = JsonConstants.Quote;
393 output[BytesPending++] = JsonConstants.KeyValueSeperator;
395 value.CopyTo(output.Slice(BytesPending));
396 BytesPending += value.Length;
399 // AggressiveInlining used since this is only called from one location.
400 [MethodImpl(MethodImplOptions.AggressiveInlining)]
401 private void WriteLiteralSection(ReadOnlySpan<byte> escapedPropertyNameSection, ReadOnlySpan<byte> value)
403 Debug.Assert(value.Length <= JsonConstants.MaxUnescapedTokenSize);
404 Debug.Assert(escapedPropertyNameSection.Length < int.MaxValue - value.Length - 1);
406 int minRequired = escapedPropertyNameSection.Length + value.Length;
407 int maxRequired = minRequired + 1; // Optionally, 1 list separator
409 if (_memory.Length - BytesPending < maxRequired)
414 Span<byte> output = _memory.Span;
415 if (_currentDepth < 0)
417 output[BytesPending++] = JsonConstants.ListSeparator;
420 escapedPropertyNameSection.CopyTo(output.Slice(BytesPending));
421 BytesPending += escapedPropertyNameSection.Length;
423 value.CopyTo(output.Slice(BytesPending));
424 BytesPending += value.Length;
427 private void WriteLiteralIndented(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> value)
429 int indent = Indentation;
430 Debug.Assert(indent <= 2 * _options.MaxDepth);
432 Debug.Assert(value.Length <= JsonConstants.MaxUnescapedTokenSize);
433 Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - value.Length - 5 - s_newLineLength);
435 // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + value.Length + 4
436 // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding
437 int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + value.Length + 5 + s_newLineLength;
439 if (_memory.Length - BytesPending < maxRequired)
444 Span<byte> output = _memory.Span;
446 if (_currentDepth < 0)
448 output[BytesPending++] = JsonConstants.ListSeparator;
451 Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
453 if (_tokenType != JsonTokenType.None)
455 WriteNewLine(output);
458 JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
459 BytesPending += indent;
461 output[BytesPending++] = JsonConstants.Quote;
463 TranscodeAndWrite(escapedPropertyName, output);
465 output[BytesPending++] = JsonConstants.Quote;
466 output[BytesPending++] = JsonConstants.KeyValueSeperator;
467 output[BytesPending++] = JsonConstants.Space;
469 value.CopyTo(output.Slice(BytesPending));
470 BytesPending += value.Length;
473 private void WriteLiteralIndented(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> value)
475 int indent = Indentation;
476 Debug.Assert(indent <= 2 * _options.MaxDepth);
478 Debug.Assert(value.Length <= JsonConstants.MaxUnescapedTokenSize);
479 Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - value.Length - 5 - s_newLineLength);
481 int minRequired = indent + escapedPropertyName.Length + value.Length + 4; // 2 quotes for property name, 1 colon, and 1 space
482 int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line
484 if (_memory.Length - BytesPending < maxRequired)
489 Span<byte> output = _memory.Span;
491 if (_currentDepth < 0)
493 output[BytesPending++] = JsonConstants.ListSeparator;
496 Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
498 if (_tokenType != JsonTokenType.None)
500 WriteNewLine(output);
503 JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
504 BytesPending += indent;
506 output[BytesPending++] = JsonConstants.Quote;
508 escapedPropertyName.CopyTo(output.Slice(BytesPending));
509 BytesPending += escapedPropertyName.Length;
511 output[BytesPending++] = JsonConstants.Quote;
512 output[BytesPending++] = JsonConstants.KeyValueSeperator;
513 output[BytesPending++] = JsonConstants.Space;
515 value.CopyTo(output.Slice(BytesPending));
516 BytesPending += value.Length;
519 internal void WritePropertyName(bool value)
521 Span<byte> utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatBooleanLength];
523 bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten);
524 Debug.Assert(result);
526 WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten));