#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 Newtonsoft.Json.Utilities; namespace Newtonsoft.Json { /// <summary> /// Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. /// </summary> internal 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<char> _arrayPool; private char[] _indentChars; private Base64Encoder Base64Encoder { get { if (_base64Encoder == null) { _base64Encoder = new Base64Encoder(_writer); } return _base64Encoder; } } /// <summary> /// Gets or sets the writer's character array pool. /// </summary> public IArrayPool<char> ArrayPool { get => _arrayPool; set { if (value == null) { throw new ArgumentNullException(nameof(value)); } _arrayPool = value; } } /// <summary> /// Gets or sets how many <see cref="JsonTextWriter.IndentChar"/>s to write for each level in the hierarchy when <see cref="JsonWriter.Formatting"/> is set to <see cref="Formatting.Indented"/>. /// </summary> public int Indentation { get => _indentation; set { if (value < 0) { throw new ArgumentException("Indentation value must be greater than 0."); } _indentation = value; } } /// <summary> /// Gets or sets which character to use to quote attribute values. /// </summary> 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(); } } /// <summary> /// Gets or sets which character to use for indenting when <see cref="JsonWriter.Formatting"/> is set to <see cref="Formatting.Indented"/>. /// </summary> public char IndentChar { get => _indentChar; set { if (value != _indentChar) { _indentChar = value; _indentChars = null; } } } /// <summary> /// Gets or sets a value indicating whether object names will be surrounded with quotes. /// </summary> public bool QuoteName { get => _quoteName; set => _quoteName = value; } /// <summary> /// Initializes a new instance of the <see cref="JsonTextWriter"/> class using the specified <see cref="TextWriter"/>. /// </summary> /// <param name="textWriter">The <see cref="TextWriter"/> to write to.</param> 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 } /// <summary> /// Flushes whatever is in the buffer to the underlying <see cref="TextWriter"/> and also flushes the underlying <see cref="TextWriter"/>. /// </summary> public override void Flush() { _writer.Flush(); } /// <summary> /// Closes this writer. /// If <see cref="JsonWriter.CloseOutput"/> is set to <c>true</c>, the underlying <see cref="TextWriter"/> is also closed. /// If <see cref="JsonWriter.AutoCompleteOnClose"/> is set to <c>true</c>, the JSON is auto-completed. /// </summary> public override void Close() { base.Close(); CloseBufferAndWriter(); } private void CloseBufferAndWriter() { if (_writeBuffer != null) { BufferUtils.ReturnBuffer(_arrayPool, _writeBuffer); _writeBuffer = null; } if (CloseOutput) { _writer?.Close(); //_writer?.Dispose(); } } /// <summary> /// Writes the beginning of a JSON object. /// </summary> public override void WriteStartObject() { InternalWriteStart(JsonToken.StartObject, JsonContainerType.Object); _writer.Write('{'); } /// <summary> /// Writes the beginning of a JSON array. /// </summary> public override void WriteStartArray() { InternalWriteStart(JsonToken.StartArray, JsonContainerType.Array); _writer.Write('['); } /// <summary> /// Writes the start of a constructor with the given name. /// </summary> /// <param name="name">The name of the constructor.</param> public override void WriteStartConstructor(string name) { InternalWriteStart(JsonToken.StartConstructor, JsonContainerType.Constructor); _writer.Write("new "); _writer.Write(name); _writer.Write('('); } /// <summary> /// Writes the specified end token. /// </summary> /// <param name="token">The end token to write.</param> 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); } } /// <summary> /// Writes the property name of a name/value pair on a JSON object. /// </summary> /// <param name="name">The name of the property.</param> public override void WritePropertyName(string name) { InternalWritePropertyName(name); WriteEscapedString(name, _quoteName); _writer.Write(':'); } /// <summary> /// Writes the property name of a name/value pair on a JSON object. /// </summary> /// <param name="name">The name of the property.</param> /// <param name="escape">A flag to indicate whether the text should be escaped when it is written as a JSON property name.</param> 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); } /// <summary> /// Writes indent characters. /// </summary> 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; } /// <summary> /// Writes the JSON value delimiter. /// </summary> protected override void WriteValueDelimiter() { _writer.Write(','); } /// <summary> /// Writes an indent space. /// </summary> protected override void WriteIndentSpace() { _writer.Write(' '); } private void WriteValueInternal(string value, JsonToken token) { _writer.Write(value); } #region WriteValue methods /// <summary> /// Writes a <see cref="Object"/> value. /// An error will raised if the value cannot be written as a single JSON token. /// </summary> /// <param name="value">The <see cref="Object"/> value to write.</param> public override void WriteValue(object value) { #if HAVE_BIG_INTEGER if (value is BigInteger) { InternalWriteValue(JsonToken.Integer); WriteValueInternal(((BigInteger)value).ToString(CultureInfo.InvariantCulture), JsonToken.String); } else #endif { base.WriteValue(value); } } /// <summary> /// Writes a null value. /// </summary> public override void WriteNull() { InternalWriteValue(JsonToken.Null); WriteValueInternal(JsonConvert.Null, JsonToken.Null); } /// <summary> /// Writes an undefined value. /// </summary> public override void WriteUndefined() { InternalWriteValue(JsonToken.Undefined); WriteValueInternal(JsonConvert.Undefined, JsonToken.Undefined); } /// <summary> /// Writes raw JSON. /// </summary> /// <param name="json">The raw JSON to write.</param> public override void WriteRaw(string json) { InternalWriteRaw(); _writer.Write(json); } /// <summary> /// Writes a <see cref="String"/> value. /// </summary> /// <param name="value">The <see cref="String"/> value to write.</param> 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); } /// <summary> /// Writes a <see cref="Int32"/> value. /// </summary> /// <param name="value">The <see cref="Int32"/> value to write.</param> public override void WriteValue(int value) { InternalWriteValue(JsonToken.Integer); WriteIntegerValue(value); } /// <summary> /// Writes a <see cref="UInt32"/> value. /// </summary> /// <param name="value">The <see cref="UInt32"/> value to write.</param> public override void WriteValue(uint value) { InternalWriteValue(JsonToken.Integer); WriteIntegerValue(value); } /// <summary> /// Writes a <see cref="Int64"/> value. /// </summary> /// <param name="value">The <see cref="Int64"/> value to write.</param> public override void WriteValue(long value) { InternalWriteValue(JsonToken.Integer); WriteIntegerValue(value); } /// <summary> /// Writes a <see cref="UInt64"/> value. /// </summary> /// <param name="value">The <see cref="UInt64"/> value to write.</param> public override void WriteValue(ulong value) { InternalWriteValue(JsonToken.Integer); WriteIntegerValue(value, false); } /// <summary> /// Writes a <see cref="Single"/> value. /// </summary> /// <param name="value">The <see cref="Single"/> value to write.</param> public override void WriteValue(float value) { InternalWriteValue(JsonToken.Float); WriteValueInternal(JsonConvert.ToString(value, FloatFormatHandling, QuoteChar, false), JsonToken.Float); } /// <summary> /// Writes a <see cref="Nullable{T}"/> of <see cref="Single"/> value. /// </summary> /// <param name="value">The <see cref="Nullable{T}"/> of <see cref="Single"/> value to write.</param> public override void WriteValue(float? value) { if (value == null) { WriteNull(); } else { InternalWriteValue(JsonToken.Float); WriteValueInternal(JsonConvert.ToString(value.GetValueOrDefault(), FloatFormatHandling, QuoteChar, true), JsonToken.Float); } } /// <summary> /// Writes a <see cref="Double"/> value. /// </summary> /// <param name="value">The <see cref="Double"/> value to write.</param> public override void WriteValue(double value) { InternalWriteValue(JsonToken.Float); WriteValueInternal(JsonConvert.ToString(value, FloatFormatHandling, QuoteChar, false), JsonToken.Float); } /// <summary> /// Writes a <see cref="Nullable{T}"/> of <see cref="Double"/> value. /// </summary> /// <param name="value">The <see cref="Nullable{T}"/> of <see cref="Double"/> value to write.</param> public override void WriteValue(double? value) { if (value == null) { WriteNull(); } else { InternalWriteValue(JsonToken.Float); WriteValueInternal(JsonConvert.ToString(value.GetValueOrDefault(), FloatFormatHandling, QuoteChar, true), JsonToken.Float); } } /// <summary> /// Writes a <see cref="Boolean"/> value. /// </summary> /// <param name="value">The <see cref="Boolean"/> value to write.</param> public override void WriteValue(bool value) { InternalWriteValue(JsonToken.Boolean); WriteValueInternal(JsonConvert.ToString(value), JsonToken.Boolean); } /// <summary> /// Writes a <see cref="Int16"/> value. /// </summary> /// <param name="value">The <see cref="Int16"/> value to write.</param> public override void WriteValue(short value) { InternalWriteValue(JsonToken.Integer); WriteIntegerValue(value); } /// <summary> /// Writes a <see cref="UInt16"/> value. /// </summary> /// <param name="value">The <see cref="UInt16"/> value to write.</param> public override void WriteValue(ushort value) { InternalWriteValue(JsonToken.Integer); WriteIntegerValue(value); } /// <summary> /// Writes a <see cref="Char"/> value. /// </summary> /// <param name="value">The <see cref="Char"/> value to write.</param> public override void WriteValue(char value) { InternalWriteValue(JsonToken.String); WriteValueInternal(JsonConvert.ToString(value), JsonToken.String); } /// <summary> /// Writes a <see cref="Byte"/> value. /// </summary> /// <param name="value">The <see cref="Byte"/> value to write.</param> public override void WriteValue(byte value) { InternalWriteValue(JsonToken.Integer); WriteIntegerValue(value); } /// <summary> /// Writes a <see cref="SByte"/> value. /// </summary> /// <param name="value">The <see cref="SByte"/> value to write.</param> public override void WriteValue(sbyte value) { InternalWriteValue(JsonToken.Integer); WriteIntegerValue(value); } /// <summary> /// Writes a <see cref="Decimal"/> value. /// </summary> /// <param name="value">The <see cref="Decimal"/> value to write.</param> public override void WriteValue(decimal value) { InternalWriteValue(JsonToken.Float); WriteValueInternal(JsonConvert.ToString(value), JsonToken.Float); } /// <summary> /// Writes a <see cref="DateTime"/> value. /// </summary> /// <param name="value">The <see cref="DateTime"/> value to write.</param> public override void WriteValue(DateTime value) { InternalWriteValue(JsonToken.Date); value = DateTimeUtils.EnsureDateTime(value, DateTimeZoneHandling); if (string.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(); int pos = 0; _writeBuffer[pos++] = _quoteChar; pos = DateTimeUtils.WriteDateTimeString(_writeBuffer, pos, value, null, value.Kind, DateFormatHandling); _writeBuffer[pos++] = _quoteChar; return pos; } /// <summary> /// Writes a <see cref="Byte"/>[] value. /// </summary> /// <param name="value">The <see cref="Byte"/>[] value to write.</param> 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 !NET20 /// <summary> /// Writes a <see cref="DateTimeOffset"/> value. /// </summary> /// <param name="value">The <see cref="DateTimeOffset"/> value to write.</param> public override void WriteValue(DateTimeOffset value) { InternalWriteValue(JsonToken.Date); if (string.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(); 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 /// <summary> /// Writes a <see cref="Guid"/> value. /// </summary> /// <param name="value">The <see cref="Guid"/> value to write.</param> public override void WriteValue(Guid value) { InternalWriteValue(JsonToken.String); string text = null; #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); } /// <summary> /// Writes a <see cref="TimeSpan"/> value. /// </summary> /// <param name="value">The <see cref="TimeSpan"/> value to write.</param> public override void WriteValue(TimeSpan value) { InternalWriteValue(JsonToken.String); string text; #if NET20 text = value.ToString(); #else text = value.ToString(null, CultureInfo.InvariantCulture); #endif _writer.Write(_quoteChar); _writer.Write(text); _writer.Write(_quoteChar); } /// <summary> /// Writes a <see cref="Uri"/> value. /// </summary> /// <param name="value">The <see cref="Uri"/> value to write.</param> public override void WriteValue(Uri value) { if (value == null) { WriteNull(); } else { InternalWriteValue(JsonToken.String); WriteEscapedString(value.OriginalString, true); } } #endregion /// <summary> /// Writes a comment <c>/*...*/</c> containing the specified text. /// </summary> /// <param name="text">Text to place inside the comment.</param> public override void WriteComment(string text) { InternalWriteComment(); _writer.Write("/*"); _writer.Write(text); _writer.Write("*/"); } /// <summary> /// Writes the given white space. /// </summary> /// <param name="ws">The string of white space characters.</param> 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(); 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(); 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; } } }