#region License // Copyright (c) 2007 James Newton-King // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. #endregion using System; using System.Collections.Generic; using System.Globalization; #if HAVE_BIG_INTEGER using System.Numerics; #endif using System.Text; using System.IO; using System.Xml; using LC.Newtonsoft.Json.Utilities; using System.Diagnostics; namespace LC.Newtonsoft.Json { /// /// Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. /// public partial class JsonTextWriter : JsonWriter { private const int IndentCharBufferSize = 12; private readonly TextWriter _writer; private Base64Encoder? _base64Encoder; private char _indentChar; private int _indentation; private char _quoteChar; private bool _quoteName; private bool[]? _charEscapeFlags; private char[]? _writeBuffer; private IArrayPool? _arrayPool; private char[]? _indentChars; private Base64Encoder Base64Encoder { get { if (_base64Encoder == null) { _base64Encoder = new Base64Encoder(_writer); } return _base64Encoder; } } /// /// Gets or sets the writer's character array pool. /// public IArrayPool? ArrayPool { get => _arrayPool; set { if (value == null) { throw new ArgumentNullException(nameof(value)); } _arrayPool = value; } } /// /// Gets or sets how many s to write for each level in the hierarchy when is set to . /// public int Indentation { get => _indentation; set { if (value < 0) { throw new ArgumentException("Indentation value must be greater than 0."); } _indentation = value; } } /// /// Gets or sets which character to use to quote attribute values. /// public char QuoteChar { get => _quoteChar; set { if (value != '"' && value != '\'') { throw new ArgumentException(@"Invalid JavaScript string quote character. Valid quote characters are ' and ""."); } _quoteChar = value; UpdateCharEscapeFlags(); } } /// /// Gets or sets which character to use for indenting when is set to . /// public char IndentChar { get => _indentChar; set { if (value != _indentChar) { _indentChar = value; _indentChars = null; } } } /// /// Gets or sets a value indicating whether object names will be surrounded with quotes. /// public bool QuoteName { get => _quoteName; set => _quoteName = value; } /// /// Initializes a new instance of the class using the specified . /// /// The to write to. public JsonTextWriter(TextWriter textWriter) { if (textWriter == null) { throw new ArgumentNullException(nameof(textWriter)); } _writer = textWriter; _quoteChar = '"'; _quoteName = true; _indentChar = ' '; _indentation = 2; UpdateCharEscapeFlags(); #if HAVE_ASYNC _safeAsync = GetType() == typeof(JsonTextWriter); #endif } /// /// Flushes whatever is in the buffer to the underlying and also flushes the underlying . /// public override void Flush() { _writer.Flush(); } /// /// Closes this writer. /// If is set to true, the underlying is also closed. /// If is set to true, the JSON is auto-completed. /// public override void Close() { base.Close(); CloseBufferAndWriter(); } private void CloseBufferAndWriter() { if (_writeBuffer != null) { BufferUtils.ReturnBuffer(_arrayPool, _writeBuffer); _writeBuffer = null; } if (CloseOutput) { #if HAVE_STREAM_READER_WRITER_CLOSE _writer?.Close(); #else _writer?.Dispose(); #endif } } /// /// Writes the beginning of a JSON object. /// public override void WriteStartObject() { InternalWriteStart(JsonToken.StartObject, JsonContainerType.Object); _writer.Write('{'); } /// /// Writes the beginning of a JSON array. /// public override void WriteStartArray() { InternalWriteStart(JsonToken.StartArray, JsonContainerType.Array); _writer.Write('['); } /// /// Writes the start of a constructor with the given name. /// /// The name of the constructor. public override void WriteStartConstructor(string name) { InternalWriteStart(JsonToken.StartConstructor, JsonContainerType.Constructor); _writer.Write("new "); _writer.Write(name); _writer.Write('('); } /// /// Writes the specified end token. /// /// The end token to write. protected override void WriteEnd(JsonToken token) { switch (token) { case JsonToken.EndObject: _writer.Write('}'); break; case JsonToken.EndArray: _writer.Write(']'); break; case JsonToken.EndConstructor: _writer.Write(')'); break; default: throw JsonWriterException.Create(this, "Invalid JsonToken: " + token, null); } } /// /// Writes the property name of a name/value pair on a JSON object. /// /// The name of the property. public override void WritePropertyName(string name) { InternalWritePropertyName(name); WriteEscapedString(name, _quoteName); _writer.Write(':'); } /// /// Writes the property name of a name/value pair on a JSON object. /// /// The name of the property. /// A flag to indicate whether the text should be escaped when it is written as a JSON property name. public override void WritePropertyName(string name, bool escape) { InternalWritePropertyName(name); if (escape) { WriteEscapedString(name, _quoteName); } else { if (_quoteName) { _writer.Write(_quoteChar); } _writer.Write(name); if (_quoteName) { _writer.Write(_quoteChar); } } _writer.Write(':'); } internal override void OnStringEscapeHandlingChanged() { UpdateCharEscapeFlags(); } private void UpdateCharEscapeFlags() { _charEscapeFlags = JavaScriptUtils.GetCharEscapeFlags(StringEscapeHandling, _quoteChar); } /// /// Writes indent characters. /// protected override void WriteIndent() { // levels of indentation multiplied by the indent count int currentIndentCount = Top * _indentation; int newLineLen = SetIndentChars(); _writer.Write(_indentChars, 0, newLineLen + Math.Min(currentIndentCount, IndentCharBufferSize)); while ((currentIndentCount -= IndentCharBufferSize) > 0) { _writer.Write(_indentChars, newLineLen, Math.Min(currentIndentCount, IndentCharBufferSize)); } } private int SetIndentChars() { // Set _indentChars to be a newline followed by IndentCharBufferSize indent characters. string writerNewLine = _writer.NewLine; int newLineLen = writerNewLine.Length; bool match = _indentChars != null && _indentChars.Length == IndentCharBufferSize + newLineLen; if (match) { for (int i = 0; i != newLineLen; ++i) { if (writerNewLine[i] != _indentChars![i]) { match = false; break; } } } if (!match) { // If we're here, either _indentChars hasn't been set yet, or _writer.NewLine // has been changed, or _indentChar has been changed. _indentChars = (writerNewLine + new string(_indentChar, IndentCharBufferSize)).ToCharArray(); } return newLineLen; } /// /// Writes the JSON value delimiter. /// protected override void WriteValueDelimiter() { _writer.Write(','); } /// /// Writes an indent space. /// protected override void WriteIndentSpace() { _writer.Write(' '); } private void WriteValueInternal(string value, JsonToken token) { _writer.Write(value); } #region WriteValue methods /// /// Writes a value. /// An error will raised if the value cannot be written as a single JSON token. /// /// The value to write. public override void WriteValue(object? value) { #if HAVE_BIG_INTEGER if (value is BigInteger i) { InternalWriteValue(JsonToken.Integer); WriteValueInternal(i.ToString(CultureInfo.InvariantCulture), JsonToken.String); } else #endif { base.WriteValue(value); } } /// /// Writes a null value. /// public override void WriteNull() { InternalWriteValue(JsonToken.Null); WriteValueInternal(JsonConvert.Null, JsonToken.Null); } /// /// Writes an undefined value. /// public override void WriteUndefined() { InternalWriteValue(JsonToken.Undefined); WriteValueInternal(JsonConvert.Undefined, JsonToken.Undefined); } /// /// Writes raw JSON. /// /// The raw JSON to write. public override void WriteRaw(string? json) { InternalWriteRaw(); _writer.Write(json); } /// /// Writes a value. /// /// The value to write. public override void WriteValue(string? value) { InternalWriteValue(JsonToken.String); if (value == null) { WriteValueInternal(JsonConvert.Null, JsonToken.Null); } else { WriteEscapedString(value, true); } } private void WriteEscapedString(string value, bool quote) { EnsureWriteBuffer(); JavaScriptUtils.WriteEscapedJavaScriptString(_writer, value, _quoteChar, quote, _charEscapeFlags!, StringEscapeHandling, _arrayPool, ref _writeBuffer); } /// /// Writes a value. /// /// The value to write. public override void WriteValue(int value) { InternalWriteValue(JsonToken.Integer); WriteIntegerValue(value); } /// /// Writes a value. /// /// The value to write. [CLSCompliant(false)] public override void WriteValue(uint value) { InternalWriteValue(JsonToken.Integer); WriteIntegerValue(value); } /// /// Writes a value. /// /// The value to write. public override void WriteValue(long value) { InternalWriteValue(JsonToken.Integer); WriteIntegerValue(value); } /// /// Writes a value. /// /// The value to write. [CLSCompliant(false)] public override void WriteValue(ulong value) { InternalWriteValue(JsonToken.Integer); WriteIntegerValue(value, false); } /// /// Writes a value. /// /// The value to write. public override void WriteValue(float value) { InternalWriteValue(JsonToken.Float); WriteValueInternal(JsonConvert.ToString(value, FloatFormatHandling, QuoteChar, false), JsonToken.Float); } /// /// Writes a of value. /// /// The of value to write. public override void WriteValue(float? value) { if (value == null) { WriteNull(); } else { InternalWriteValue(JsonToken.Float); WriteValueInternal(JsonConvert.ToString(value.GetValueOrDefault(), FloatFormatHandling, QuoteChar, true), JsonToken.Float); } } /// /// Writes a value. /// /// The value to write. public override void WriteValue(double value) { InternalWriteValue(JsonToken.Float); WriteValueInternal(JsonConvert.ToString(value, FloatFormatHandling, QuoteChar, false), JsonToken.Float); } /// /// Writes a of value. /// /// The of value to write. public override void WriteValue(double? value) { if (value == null) { WriteNull(); } else { InternalWriteValue(JsonToken.Float); WriteValueInternal(JsonConvert.ToString(value.GetValueOrDefault(), FloatFormatHandling, QuoteChar, true), JsonToken.Float); } } /// /// Writes a value. /// /// The value to write. public override void WriteValue(bool value) { InternalWriteValue(JsonToken.Boolean); WriteValueInternal(JsonConvert.ToString(value), JsonToken.Boolean); } /// /// Writes a value. /// /// The value to write. public override void WriteValue(short value) { InternalWriteValue(JsonToken.Integer); WriteIntegerValue(value); } /// /// Writes a value. /// /// The value to write. [CLSCompliant(false)] public override void WriteValue(ushort value) { InternalWriteValue(JsonToken.Integer); WriteIntegerValue(value); } /// /// Writes a value. /// /// The value to write. public override void WriteValue(char value) { InternalWriteValue(JsonToken.String); WriteValueInternal(JsonConvert.ToString(value), JsonToken.String); } /// /// Writes a value. /// /// The value to write. public override void WriteValue(byte value) { InternalWriteValue(JsonToken.Integer); WriteIntegerValue(value); } /// /// Writes a value. /// /// The value to write. [CLSCompliant(false)] public override void WriteValue(sbyte value) { InternalWriteValue(JsonToken.Integer); WriteIntegerValue(value); } /// /// Writes a value. /// /// The value to write. public override void WriteValue(decimal value) { InternalWriteValue(JsonToken.Float); WriteValueInternal(JsonConvert.ToString(value), JsonToken.Float); } /// /// Writes a value. /// /// The value to write. public override void WriteValue(DateTime value) { InternalWriteValue(JsonToken.Date); value = DateTimeUtils.EnsureDateTime(value, DateTimeZoneHandling); if (StringUtils.IsNullOrEmpty(DateFormatString)) { int length = WriteValueToBuffer(value); _writer.Write(_writeBuffer, 0, length); } else { _writer.Write(_quoteChar); _writer.Write(value.ToString(DateFormatString, Culture)); _writer.Write(_quoteChar); } } private int WriteValueToBuffer(DateTime value) { EnsureWriteBuffer(); MiscellaneousUtils.Assert(_writeBuffer != null); int pos = 0; _writeBuffer[pos++] = _quoteChar; pos = DateTimeUtils.WriteDateTimeString(_writeBuffer, pos, value, null, value.Kind, DateFormatHandling); _writeBuffer[pos++] = _quoteChar; return pos; } /// /// Writes a [] value. /// /// The [] value to write. public override void WriteValue(byte[]? value) { if (value == null) { WriteNull(); } else { InternalWriteValue(JsonToken.Bytes); _writer.Write(_quoteChar); Base64Encoder.Encode(value, 0, value.Length); Base64Encoder.Flush(); _writer.Write(_quoteChar); } } #if HAVE_DATE_TIME_OFFSET /// /// Writes a value. /// /// The value to write. public override void WriteValue(DateTimeOffset value) { InternalWriteValue(JsonToken.Date); if (StringUtils.IsNullOrEmpty(DateFormatString)) { int length = WriteValueToBuffer(value); _writer.Write(_writeBuffer, 0, length); } else { _writer.Write(_quoteChar); _writer.Write(value.ToString(DateFormatString, Culture)); _writer.Write(_quoteChar); } } private int WriteValueToBuffer(DateTimeOffset value) { EnsureWriteBuffer(); MiscellaneousUtils.Assert(_writeBuffer != null); int pos = 0; _writeBuffer[pos++] = _quoteChar; pos = DateTimeUtils.WriteDateTimeString(_writeBuffer, pos, (DateFormatHandling == DateFormatHandling.IsoDateFormat) ? value.DateTime : value.UtcDateTime, value.Offset, DateTimeKind.Local, DateFormatHandling); _writeBuffer[pos++] = _quoteChar; return pos; } #endif /// /// Writes a value. /// /// The value to write. public override void WriteValue(Guid value) { InternalWriteValue(JsonToken.String); string text; #if HAVE_CHAR_TO_STRING_WITH_CULTURE text = value.ToString("D", CultureInfo.InvariantCulture); #else text = value.ToString("D"); #endif _writer.Write(_quoteChar); _writer.Write(text); _writer.Write(_quoteChar); } /// /// Writes a value. /// /// The value to write. public override void WriteValue(TimeSpan value) { InternalWriteValue(JsonToken.String); string text; #if !HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE text = value.ToString(); #else text = value.ToString(null, CultureInfo.InvariantCulture); #endif _writer.Write(_quoteChar); _writer.Write(text); _writer.Write(_quoteChar); } /// /// Writes a value. /// /// The value to write. public override void WriteValue(Uri? value) { if (value == null) { WriteNull(); } else { InternalWriteValue(JsonToken.String); WriteEscapedString(value.OriginalString, true); } } #endregion /// /// Writes a comment /*...*/ containing the specified text. /// /// Text to place inside the comment. public override void WriteComment(string? text) { InternalWriteComment(); _writer.Write("/*"); _writer.Write(text); _writer.Write("*/"); } /// /// Writes the given white space. /// /// The string of white space characters. public override void WriteWhitespace(string ws) { InternalWriteWhitespace(ws); _writer.Write(ws); } private void EnsureWriteBuffer() { if (_writeBuffer == null) { // maximum buffer sized used when writing iso date _writeBuffer = BufferUtils.RentBuffer(_arrayPool, 35); } } private void WriteIntegerValue(long value) { if (value >= 0 && value <= 9) { _writer.Write((char)('0' + value)); } else { bool negative = value < 0; WriteIntegerValue(negative ? (ulong)-value : (ulong)value, negative); } } private void WriteIntegerValue(ulong value, bool negative) { if (!negative & value <= 9) { _writer.Write((char)('0' + value)); } else { int length = WriteNumberToBuffer(value, negative); _writer.Write(_writeBuffer, 0, length); } } private int WriteNumberToBuffer(ulong value, bool negative) { if (value <= uint.MaxValue) { // avoid the 64 bit division if possible return WriteNumberToBuffer((uint)value, negative); } EnsureWriteBuffer(); MiscellaneousUtils.Assert(_writeBuffer != null); int totalLength = MathUtils.IntLength(value); if (negative) { totalLength++; _writeBuffer[0] = '-'; } int index = totalLength; do { ulong quotient = value / 10; ulong digit = value - (quotient * 10); _writeBuffer[--index] = (char)('0' + digit); value = quotient; } while (value != 0); return totalLength; } private void WriteIntegerValue(int value) { if (value >= 0 && value <= 9) { _writer.Write((char)('0' + value)); } else { bool negative = value < 0; WriteIntegerValue(negative ? (uint)-value : (uint)value, negative); } } private void WriteIntegerValue(uint value, bool negative) { if (!negative & value <= 9) { _writer.Write((char)('0' + value)); } else { int length = WriteNumberToBuffer(value, negative); _writer.Write(_writeBuffer, 0, length); } } private int WriteNumberToBuffer(uint value, bool negative) { EnsureWriteBuffer(); MiscellaneousUtils.Assert(_writeBuffer != null); int totalLength = MathUtils.IntLength(value); if (negative) { totalLength++; _writeBuffer[0] = '-'; } int index = totalLength; do { uint quotient = value / 10; uint digit = value - (quotient * 10); _writeBuffer[--index] = (char)('0' + digit); value = quotient; } while (value != 0); return totalLength; } } }