7a68bc6512cfb7cfeeb7e40cf60f2be5af0595e4
[platform/upstream/dotnet/runtime.git] /
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
4 using System.Buffers;
5 using System.Buffers.Text;
6 using System.Diagnostics;
7 using System.Runtime.CompilerServices;
8
9 namespace System.Text.Json
10 {
11     public sealed partial class Utf8JsonWriter
12     {
13         /// <summary>
14         /// Writes the pre-encoded property name and the JSON literal "null" as part of a name/value pair of a JSON object.
15         /// </summary>
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).
19         /// </exception>
20         public void WriteNull(JsonEncodedText propertyName)
21         {
22             WriteLiteralHelper(propertyName.EncodedUtf8Bytes, JsonConstants.NullValue);
23             _tokenType = JsonTokenType.Null;
24         }
25
26         internal void WriteNullSection(ReadOnlySpan<byte> escapedPropertyNameSection)
27         {
28             if (_options.Indented)
29             {
30                 ReadOnlySpan<byte> escapedName =
31                     escapedPropertyNameSection.Slice(1, escapedPropertyNameSection.Length - 3);
32
33                 WriteLiteralHelper(escapedName, JsonConstants.NullValue);
34                 _tokenType = JsonTokenType.Null;
35             }
36             else
37             {
38                 Debug.Assert(escapedPropertyNameSection.Length <= JsonConstants.MaxUnescapedTokenSize - 3);
39
40                 ReadOnlySpan<byte> span = JsonConstants.NullValue;
41
42                 WriteLiteralSection(escapedPropertyNameSection, span);
43
44                 SetFlagToAddListSeparatorBeforeNextItem();
45                 _tokenType = JsonTokenType.Null;
46             }
47         }
48
49         private void WriteLiteralHelper(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value)
50         {
51             Debug.Assert(utf8PropertyName.Length <= JsonConstants.MaxUnescapedTokenSize);
52
53             WriteLiteralByOptions(utf8PropertyName, value);
54
55             SetFlagToAddListSeparatorBeforeNextItem();
56         }
57
58         /// <summary>
59         /// Writes the property name and the JSON literal "null" as part of a name/value pair of a JSON object.
60         /// </summary>
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.
64         /// </exception>
65         /// <exception cref="ArgumentNullException">
66         /// The <paramref name="propertyName"/> parameter is <see langword="null"/>.
67         /// </exception>
68         /// <exception cref="InvalidOperationException">
69         /// Thrown if this would result in invalid JSON being written (while validation is enabled).
70         /// </exception>
71         /// <remarks>
72         /// The property name is escaped before writing.
73         /// </remarks>
74         public void WriteNull(string propertyName)
75         {
76             if (propertyName is null)
77             {
78                 ThrowHelper.ThrowArgumentNullException(nameof(propertyName));
79             }
80             WriteNull(propertyName.AsSpan());
81         }
82
83         /// <summary>
84         /// Writes the property name and the JSON literal "null" as part of a name/value pair of a JSON object.
85         /// </summary>
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.
89         /// </exception>
90         /// <exception cref="InvalidOperationException">
91         /// Thrown if this would result in invalid JSON being written (while validation is enabled).
92         /// </exception>
93         /// <remarks>
94         /// The property name is escaped before writing.
95         /// </remarks>
96         public void WriteNull(ReadOnlySpan<char> propertyName)
97         {
98             JsonWriterHelper.ValidateProperty(propertyName);
99
100             ReadOnlySpan<byte> span = JsonConstants.NullValue;
101
102             WriteLiteralEscape(propertyName, span);
103
104             SetFlagToAddListSeparatorBeforeNextItem();
105             _tokenType = JsonTokenType.Null;
106         }
107
108         /// <summary>
109         /// Writes the property name and the JSON literal "null" as part of a name/value pair of a JSON object.
110         /// </summary>
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.
114         /// </exception>
115         /// <exception cref="InvalidOperationException">
116         /// Thrown if this would result in invalid JSON being written (while validation is enabled).
117         /// </exception>
118         /// <remarks>
119         /// The property name is escaped before writing.
120         /// </remarks>
121         public void WriteNull(ReadOnlySpan<byte> utf8PropertyName)
122         {
123             JsonWriterHelper.ValidateProperty(utf8PropertyName);
124
125             ReadOnlySpan<byte> span = JsonConstants.NullValue;
126
127             WriteLiteralEscape(utf8PropertyName, span);
128
129             SetFlagToAddListSeparatorBeforeNextItem();
130             _tokenType = JsonTokenType.Null;
131         }
132
133         /// <summary>
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.
135         /// </summary>
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).
140         /// </exception>
141         public void WriteBoolean(JsonEncodedText propertyName, bool value)
142         {
143             if (value)
144             {
145                 WriteLiteralHelper(propertyName.EncodedUtf8Bytes, JsonConstants.TrueValue);
146                 _tokenType = JsonTokenType.True;
147             }
148             else
149             {
150                 WriteLiteralHelper(propertyName.EncodedUtf8Bytes, JsonConstants.FalseValue);
151                 _tokenType = JsonTokenType.False;
152             }
153         }
154
155         /// <summary>
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.
157         /// </summary>
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.
162         /// </exception>
163         /// <exception cref="ArgumentNullException">
164         /// The <paramref name="propertyName"/> parameter is <see langword="null"/>.
165         /// </exception>
166         /// <exception cref="InvalidOperationException">
167         /// Thrown if this would result in invalid JSON being written (while validation is enabled).
168         /// </exception>
169         /// <remarks>
170         /// The property name is escaped before writing.
171         /// </remarks>
172         public void WriteBoolean(string propertyName, bool value)
173         {
174             if (propertyName is null)
175             {
176                 ThrowHelper.ThrowArgumentNullException(nameof(propertyName));
177             }
178             WriteBoolean(propertyName.AsSpan(), value);
179         }
180
181         /// <summary>
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.
183         /// </summary>
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.
188         /// </exception>
189         /// <exception cref="InvalidOperationException">
190         /// Thrown if this would result in invalid JSON being written (while validation is enabled).
191         /// </exception>
192         /// <remarks>
193         /// The property name is escaped before writing.
194         /// </remarks>
195         public void WriteBoolean(ReadOnlySpan<char> propertyName, bool value)
196         {
197             JsonWriterHelper.ValidateProperty(propertyName);
198
199             ReadOnlySpan<byte> span = value ? JsonConstants.TrueValue : JsonConstants.FalseValue;
200
201             WriteLiteralEscape(propertyName, span);
202
203             SetFlagToAddListSeparatorBeforeNextItem();
204             _tokenType = value ? JsonTokenType.True : JsonTokenType.False;
205         }
206
207         /// <summary>
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.
209         /// </summary>
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.
214         /// </exception>
215         /// <exception cref="InvalidOperationException">
216         /// Thrown if this would result in invalid JSON being written (while validation is enabled).
217         /// </exception>
218         /// <remarks>
219         /// The property name is escaped before writing.
220         /// </remarks>
221         public void WriteBoolean(ReadOnlySpan<byte> utf8PropertyName, bool value)
222         {
223             JsonWriterHelper.ValidateProperty(utf8PropertyName);
224
225             ReadOnlySpan<byte> span = value ? JsonConstants.TrueValue : JsonConstants.FalseValue;
226
227             WriteLiteralEscape(utf8PropertyName, span);
228
229             SetFlagToAddListSeparatorBeforeNextItem();
230             _tokenType = value ? JsonTokenType.True : JsonTokenType.False;
231         }
232
233         private void WriteLiteralEscape(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value)
234         {
235             int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
236
237             Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
238
239             if (propertyIdx != -1)
240             {
241                 WriteLiteralEscapeProperty(propertyName, value, propertyIdx);
242             }
243             else
244             {
245                 WriteLiteralByOptions(propertyName, value);
246             }
247         }
248
249         private void WriteLiteralEscape(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value)
250         {
251             int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
252
253             Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
254
255             if (propertyIdx != -1)
256             {
257                 WriteLiteralEscapeProperty(utf8PropertyName, value, propertyIdx);
258             }
259             else
260             {
261                 WriteLiteralByOptions(utf8PropertyName, value);
262             }
263         }
264
265         private void WriteLiteralEscapeProperty(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value, int firstEscapeIndexProp)
266         {
267             Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= propertyName.Length);
268             Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < propertyName.Length);
269
270             char[]? propertyArray = null;
271
272             int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);
273
274             Span<char> escapedPropertyName = length <= JsonConstants.StackallocCharThreshold ?
275                 stackalloc char[JsonConstants.StackallocCharThreshold] :
276                 (propertyArray = ArrayPool<char>.Shared.Rent(length));
277
278             JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
279
280             WriteLiteralByOptions(escapedPropertyName.Slice(0, written), value);
281
282             if (propertyArray != null)
283             {
284                 ArrayPool<char>.Shared.Return(propertyArray);
285             }
286         }
287
288         private void WriteLiteralEscapeProperty(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value, int firstEscapeIndexProp)
289         {
290             Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length);
291             Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < utf8PropertyName.Length);
292
293             byte[]? propertyArray = null;
294
295             int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);
296
297             Span<byte> escapedPropertyName = length <= JsonConstants.StackallocByteThreshold ?
298                 stackalloc byte[JsonConstants.StackallocByteThreshold] :
299                 (propertyArray = ArrayPool<byte>.Shared.Rent(length));
300
301             JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
302
303             WriteLiteralByOptions(escapedPropertyName.Slice(0, written), value);
304
305             if (propertyArray != null)
306             {
307                 ArrayPool<byte>.Shared.Return(propertyArray);
308             }
309         }
310
311         private void WriteLiteralByOptions(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value)
312         {
313             ValidateWritingProperty();
314             if (_options.Indented)
315             {
316                 WriteLiteralIndented(propertyName, value);
317             }
318             else
319             {
320                 WriteLiteralMinimized(propertyName, value);
321             }
322         }
323
324         private void WriteLiteralByOptions(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value)
325         {
326             ValidateWritingProperty();
327             if (_options.Indented)
328             {
329                 WriteLiteralIndented(utf8PropertyName, value);
330             }
331             else
332             {
333                 WriteLiteralMinimized(utf8PropertyName, value);
334             }
335         }
336
337         private void WriteLiteralMinimized(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> value)
338         {
339             Debug.Assert(value.Length <= JsonConstants.MaxUnescapedTokenSize);
340             Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - value.Length - 4);
341
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;
345
346             if (_memory.Length - BytesPending < maxRequired)
347             {
348                 Grow(maxRequired);
349             }
350
351             Span<byte> output = _memory.Span;
352
353             if (_currentDepth < 0)
354             {
355                 output[BytesPending++] = JsonConstants.ListSeparator;
356             }
357             output[BytesPending++] = JsonConstants.Quote;
358
359             TranscodeAndWrite(escapedPropertyName, output);
360
361             output[BytesPending++] = JsonConstants.Quote;
362             output[BytesPending++] = JsonConstants.KeyValueSeperator;
363
364             value.CopyTo(output.Slice(BytesPending));
365             BytesPending += value.Length;
366         }
367
368         private void WriteLiteralMinimized(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> value)
369         {
370             Debug.Assert(value.Length <= JsonConstants.MaxUnescapedTokenSize);
371             Debug.Assert(escapedPropertyName.Length < int.MaxValue - value.Length - 4);
372
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
375
376             if (_memory.Length - BytesPending < maxRequired)
377             {
378                 Grow(maxRequired);
379             }
380
381             Span<byte> output = _memory.Span;
382
383             if (_currentDepth < 0)
384             {
385                 output[BytesPending++] = JsonConstants.ListSeparator;
386             }
387             output[BytesPending++] = JsonConstants.Quote;
388
389             escapedPropertyName.CopyTo(output.Slice(BytesPending));
390             BytesPending += escapedPropertyName.Length;
391
392             output[BytesPending++] = JsonConstants.Quote;
393             output[BytesPending++] = JsonConstants.KeyValueSeperator;
394
395             value.CopyTo(output.Slice(BytesPending));
396             BytesPending += value.Length;
397         }
398
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)
402         {
403             Debug.Assert(value.Length <= JsonConstants.MaxUnescapedTokenSize);
404             Debug.Assert(escapedPropertyNameSection.Length < int.MaxValue - value.Length - 1);
405
406             int minRequired = escapedPropertyNameSection.Length + value.Length;
407             int maxRequired = minRequired + 1; // Optionally, 1 list separator
408
409             if (_memory.Length - BytesPending < maxRequired)
410             {
411                 Grow(maxRequired);
412             }
413
414             Span<byte> output = _memory.Span;
415             if (_currentDepth < 0)
416             {
417                 output[BytesPending++] = JsonConstants.ListSeparator;
418             }
419
420             escapedPropertyNameSection.CopyTo(output.Slice(BytesPending));
421             BytesPending += escapedPropertyNameSection.Length;
422
423             value.CopyTo(output.Slice(BytesPending));
424             BytesPending += value.Length;
425         }
426
427         private void WriteLiteralIndented(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> value)
428         {
429             int indent = Indentation;
430             Debug.Assert(indent <= 2 * _options.MaxDepth);
431
432             Debug.Assert(value.Length <= JsonConstants.MaxUnescapedTokenSize);
433             Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - value.Length - 5 - s_newLineLength);
434
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;
438
439             if (_memory.Length - BytesPending < maxRequired)
440             {
441                 Grow(maxRequired);
442             }
443
444             Span<byte> output = _memory.Span;
445
446             if (_currentDepth < 0)
447             {
448                 output[BytesPending++] = JsonConstants.ListSeparator;
449             }
450
451             Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
452
453             if (_tokenType != JsonTokenType.None)
454             {
455                 WriteNewLine(output);
456             }
457
458             JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
459             BytesPending += indent;
460
461             output[BytesPending++] = JsonConstants.Quote;
462
463             TranscodeAndWrite(escapedPropertyName, output);
464
465             output[BytesPending++] = JsonConstants.Quote;
466             output[BytesPending++] = JsonConstants.KeyValueSeperator;
467             output[BytesPending++] = JsonConstants.Space;
468
469             value.CopyTo(output.Slice(BytesPending));
470             BytesPending += value.Length;
471         }
472
473         private void WriteLiteralIndented(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> value)
474         {
475             int indent = Indentation;
476             Debug.Assert(indent <= 2 * _options.MaxDepth);
477
478             Debug.Assert(value.Length <= JsonConstants.MaxUnescapedTokenSize);
479             Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - value.Length - 5 - s_newLineLength);
480
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
483
484             if (_memory.Length - BytesPending < maxRequired)
485             {
486                 Grow(maxRequired);
487             }
488
489             Span<byte> output = _memory.Span;
490
491             if (_currentDepth < 0)
492             {
493                 output[BytesPending++] = JsonConstants.ListSeparator;
494             }
495
496             Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
497
498             if (_tokenType != JsonTokenType.None)
499             {
500                 WriteNewLine(output);
501             }
502
503             JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
504             BytesPending += indent;
505
506             output[BytesPending++] = JsonConstants.Quote;
507
508             escapedPropertyName.CopyTo(output.Slice(BytesPending));
509             BytesPending += escapedPropertyName.Length;
510
511             output[BytesPending++] = JsonConstants.Quote;
512             output[BytesPending++] = JsonConstants.KeyValueSeperator;
513             output[BytesPending++] = JsonConstants.Space;
514
515             value.CopyTo(output.Slice(BytesPending));
516             BytesPending += value.Length;
517         }
518
519         internal void WritePropertyName(bool value)
520         {
521             Span<byte> utf8PropertyName = stackalloc byte[JsonConstants.MaximumFormatBooleanLength];
522
523             bool result = Utf8Formatter.TryFormat(value, utf8PropertyName, out int bytesWritten);
524             Debug.Assert(result);
525
526             WritePropertyNameUnescaped(utf8PropertyName.Slice(0, bytesWritten));
527         }
528     }
529 }