1789 lines
62 KiB
C#
1789 lines
62 KiB
C#
|
#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.IO;
|
||
|
#if HAVE_BIG_INTEGER
|
||
|
using System.Numerics;
|
||
|
#endif
|
||
|
using LC.Newtonsoft.Json.Utilities;
|
||
|
using System.Globalization;
|
||
|
#if !HAVE_LINQ
|
||
|
using LC.Newtonsoft.Json.Utilities.LinqBridge;
|
||
|
#else
|
||
|
using System.Linq;
|
||
|
|
||
|
#endif
|
||
|
|
||
|
namespace LC.Newtonsoft.Json
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data.
|
||
|
/// </summary>
|
||
|
public abstract partial class JsonWriter : IDisposable
|
||
|
{
|
||
|
internal enum State
|
||
|
{
|
||
|
Start = 0,
|
||
|
Property = 1,
|
||
|
ObjectStart = 2,
|
||
|
Object = 3,
|
||
|
ArrayStart = 4,
|
||
|
Array = 5,
|
||
|
ConstructorStart = 6,
|
||
|
Constructor = 7,
|
||
|
Closed = 8,
|
||
|
Error = 9
|
||
|
}
|
||
|
|
||
|
// array that gives a new state based on the current state an the token being written
|
||
|
private static readonly State[][] StateArray;
|
||
|
|
||
|
internal static readonly State[][] StateArrayTemplate = new[]
|
||
|
{
|
||
|
// Start PropertyName ObjectStart Object ArrayStart Array ConstructorStart Constructor Closed Error
|
||
|
//
|
||
|
/* None */new[] { State.Error, State.Error, State.Error, State.Error, State.Error, State.Error, State.Error, State.Error, State.Error, State.Error },
|
||
|
/* StartObject */new[] { State.ObjectStart, State.ObjectStart, State.Error, State.Error, State.ObjectStart, State.ObjectStart, State.ObjectStart, State.ObjectStart, State.Error, State.Error },
|
||
|
/* StartArray */new[] { State.ArrayStart, State.ArrayStart, State.Error, State.Error, State.ArrayStart, State.ArrayStart, State.ArrayStart, State.ArrayStart, State.Error, State.Error },
|
||
|
/* StartConstructor */new[] { State.ConstructorStart, State.ConstructorStart, State.Error, State.Error, State.ConstructorStart, State.ConstructorStart, State.ConstructorStart, State.ConstructorStart, State.Error, State.Error },
|
||
|
/* Property */new[] { State.Property, State.Error, State.Property, State.Property, State.Error, State.Error, State.Error, State.Error, State.Error, State.Error },
|
||
|
/* Comment */new[] { State.Start, State.Property, State.ObjectStart, State.Object, State.ArrayStart, State.Array, State.Constructor, State.Constructor, State.Error, State.Error },
|
||
|
/* Raw */new[] { State.Start, State.Property, State.ObjectStart, State.Object, State.ArrayStart, State.Array, State.Constructor, State.Constructor, State.Error, State.Error },
|
||
|
/* Value (this will be copied) */new[] { State.Start, State.Object, State.Error, State.Error, State.Array, State.Array, State.Constructor, State.Constructor, State.Error, State.Error }
|
||
|
};
|
||
|
|
||
|
internal static State[][] BuildStateArray()
|
||
|
{
|
||
|
List<State[]> allStates = StateArrayTemplate.ToList();
|
||
|
State[] errorStates = StateArrayTemplate[0];
|
||
|
State[] valueStates = StateArrayTemplate[7];
|
||
|
|
||
|
EnumInfo enumValuesAndNames = EnumUtils.GetEnumValuesAndNames(typeof(JsonToken));
|
||
|
|
||
|
foreach (ulong valueToken in enumValuesAndNames.Values)
|
||
|
{
|
||
|
if (allStates.Count <= (int)valueToken)
|
||
|
{
|
||
|
JsonToken token = (JsonToken)valueToken;
|
||
|
switch (token)
|
||
|
{
|
||
|
case JsonToken.Integer:
|
||
|
case JsonToken.Float:
|
||
|
case JsonToken.String:
|
||
|
case JsonToken.Boolean:
|
||
|
case JsonToken.Null:
|
||
|
case JsonToken.Undefined:
|
||
|
case JsonToken.Date:
|
||
|
case JsonToken.Bytes:
|
||
|
allStates.Add(valueStates);
|
||
|
break;
|
||
|
default:
|
||
|
allStates.Add(errorStates);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return allStates.ToArray();
|
||
|
}
|
||
|
|
||
|
static JsonWriter()
|
||
|
{
|
||
|
StateArray = BuildStateArray();
|
||
|
}
|
||
|
|
||
|
private List<JsonPosition>? _stack;
|
||
|
private JsonPosition _currentPosition;
|
||
|
private State _currentState;
|
||
|
private Formatting _formatting;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets a value indicating whether the destination should be closed when this writer is closed.
|
||
|
/// </summary>
|
||
|
/// <value>
|
||
|
/// <c>true</c> to close the destination when this writer is closed; otherwise <c>false</c>. The default is <c>true</c>.
|
||
|
/// </value>
|
||
|
public bool CloseOutput { get; set; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets a value indicating whether the JSON should be auto-completed when this writer is closed.
|
||
|
/// </summary>
|
||
|
/// <value>
|
||
|
/// <c>true</c> to auto-complete the JSON when this writer is closed; otherwise <c>false</c>. The default is <c>true</c>.
|
||
|
/// </value>
|
||
|
public bool AutoCompleteOnClose { get; set; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the top.
|
||
|
/// </summary>
|
||
|
/// <value>The top.</value>
|
||
|
protected internal int Top
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
int depth = _stack?.Count ?? 0;
|
||
|
if (Peek() != JsonContainerType.None)
|
||
|
{
|
||
|
depth++;
|
||
|
}
|
||
|
|
||
|
return depth;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the state of the writer.
|
||
|
/// </summary>
|
||
|
public WriteState WriteState
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
switch (_currentState)
|
||
|
{
|
||
|
case State.Error:
|
||
|
return WriteState.Error;
|
||
|
case State.Closed:
|
||
|
return WriteState.Closed;
|
||
|
case State.Object:
|
||
|
case State.ObjectStart:
|
||
|
return WriteState.Object;
|
||
|
case State.Array:
|
||
|
case State.ArrayStart:
|
||
|
return WriteState.Array;
|
||
|
case State.Constructor:
|
||
|
case State.ConstructorStart:
|
||
|
return WriteState.Constructor;
|
||
|
case State.Property:
|
||
|
return WriteState.Property;
|
||
|
case State.Start:
|
||
|
return WriteState.Start;
|
||
|
default:
|
||
|
throw JsonWriterException.Create(this, "Invalid state: " + _currentState, null);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal string ContainerPath
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (_currentPosition.Type == JsonContainerType.None || _stack == null)
|
||
|
{
|
||
|
return string.Empty;
|
||
|
}
|
||
|
|
||
|
return JsonPosition.BuildPath(_stack, null);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the path of the writer.
|
||
|
/// </summary>
|
||
|
public string Path
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (_currentPosition.Type == JsonContainerType.None)
|
||
|
{
|
||
|
return string.Empty;
|
||
|
}
|
||
|
|
||
|
bool insideContainer = (_currentState != State.ArrayStart
|
||
|
&& _currentState != State.ConstructorStart
|
||
|
&& _currentState != State.ObjectStart);
|
||
|
|
||
|
JsonPosition? current = insideContainer ? (JsonPosition?)_currentPosition : null;
|
||
|
|
||
|
return JsonPosition.BuildPath(_stack!, current);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private DateFormatHandling _dateFormatHandling;
|
||
|
private DateTimeZoneHandling _dateTimeZoneHandling;
|
||
|
private StringEscapeHandling _stringEscapeHandling;
|
||
|
private FloatFormatHandling _floatFormatHandling;
|
||
|
private string? _dateFormatString;
|
||
|
private CultureInfo? _culture;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets a value indicating how JSON text output should be formatted.
|
||
|
/// </summary>
|
||
|
public Formatting Formatting
|
||
|
{
|
||
|
get => _formatting;
|
||
|
set
|
||
|
{
|
||
|
if (value < Formatting.None || value > Formatting.Indented)
|
||
|
{
|
||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||
|
}
|
||
|
|
||
|
_formatting = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets how dates are written to JSON text.
|
||
|
/// </summary>
|
||
|
public DateFormatHandling DateFormatHandling
|
||
|
{
|
||
|
get => _dateFormatHandling;
|
||
|
set
|
||
|
{
|
||
|
if (value < DateFormatHandling.IsoDateFormat || value > DateFormatHandling.MicrosoftDateFormat)
|
||
|
{
|
||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||
|
}
|
||
|
|
||
|
_dateFormatHandling = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets how <see cref="DateTime"/> time zones are handled when writing JSON text.
|
||
|
/// </summary>
|
||
|
public DateTimeZoneHandling DateTimeZoneHandling
|
||
|
{
|
||
|
get => _dateTimeZoneHandling;
|
||
|
set
|
||
|
{
|
||
|
if (value < DateTimeZoneHandling.Local || value > DateTimeZoneHandling.RoundtripKind)
|
||
|
{
|
||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||
|
}
|
||
|
|
||
|
_dateTimeZoneHandling = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets how strings are escaped when writing JSON text.
|
||
|
/// </summary>
|
||
|
public StringEscapeHandling StringEscapeHandling
|
||
|
{
|
||
|
get => _stringEscapeHandling;
|
||
|
set
|
||
|
{
|
||
|
if (value < StringEscapeHandling.Default || value > StringEscapeHandling.EscapeHtml)
|
||
|
{
|
||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||
|
}
|
||
|
|
||
|
_stringEscapeHandling = value;
|
||
|
OnStringEscapeHandlingChanged();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal virtual void OnStringEscapeHandlingChanged()
|
||
|
{
|
||
|
// hacky but there is a calculated value that relies on StringEscapeHandling
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets how special floating point numbers, e.g. <see cref="Double.NaN"/>,
|
||
|
/// <see cref="Double.PositiveInfinity"/> and <see cref="Double.NegativeInfinity"/>,
|
||
|
/// are written to JSON text.
|
||
|
/// </summary>
|
||
|
public FloatFormatHandling FloatFormatHandling
|
||
|
{
|
||
|
get => _floatFormatHandling;
|
||
|
set
|
||
|
{
|
||
|
if (value < FloatFormatHandling.String || value > FloatFormatHandling.DefaultValue)
|
||
|
{
|
||
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||
|
}
|
||
|
|
||
|
_floatFormatHandling = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets how <see cref="DateTime"/> and <see cref="DateTimeOffset"/> values are formatted when writing JSON text.
|
||
|
/// </summary>
|
||
|
public string? DateFormatString
|
||
|
{
|
||
|
get => _dateFormatString;
|
||
|
set => _dateFormatString = value;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets the culture used when writing JSON. Defaults to <see cref="CultureInfo.InvariantCulture"/>.
|
||
|
/// </summary>
|
||
|
public CultureInfo Culture
|
||
|
{
|
||
|
get => _culture ?? CultureInfo.InvariantCulture;
|
||
|
set => _culture = value;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Initializes a new instance of the <see cref="JsonWriter"/> class.
|
||
|
/// </summary>
|
||
|
protected JsonWriter()
|
||
|
{
|
||
|
_currentState = State.Start;
|
||
|
_formatting = Formatting.None;
|
||
|
_dateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind;
|
||
|
|
||
|
CloseOutput = true;
|
||
|
AutoCompleteOnClose = true;
|
||
|
}
|
||
|
|
||
|
internal void UpdateScopeWithFinishedValue()
|
||
|
{
|
||
|
if (_currentPosition.HasIndex)
|
||
|
{
|
||
|
_currentPosition.Position++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void Push(JsonContainerType value)
|
||
|
{
|
||
|
if (_currentPosition.Type != JsonContainerType.None)
|
||
|
{
|
||
|
if (_stack == null)
|
||
|
{
|
||
|
_stack = new List<JsonPosition>();
|
||
|
}
|
||
|
|
||
|
_stack.Add(_currentPosition);
|
||
|
}
|
||
|
|
||
|
_currentPosition = new JsonPosition(value);
|
||
|
}
|
||
|
|
||
|
private JsonContainerType Pop()
|
||
|
{
|
||
|
JsonPosition oldPosition = _currentPosition;
|
||
|
|
||
|
if (_stack != null && _stack.Count > 0)
|
||
|
{
|
||
|
_currentPosition = _stack[_stack.Count - 1];
|
||
|
_stack.RemoveAt(_stack.Count - 1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_currentPosition = new JsonPosition();
|
||
|
}
|
||
|
|
||
|
return oldPosition.Type;
|
||
|
}
|
||
|
|
||
|
private JsonContainerType Peek()
|
||
|
{
|
||
|
return _currentPosition.Type;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Flushes whatever is in the buffer to the destination and also flushes the destination.
|
||
|
/// </summary>
|
||
|
public abstract void Flush();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Closes this writer.
|
||
|
/// If <see cref="CloseOutput"/> is set to <c>true</c>, the destination is also closed.
|
||
|
/// If <see cref="AutoCompleteOnClose"/> is set to <c>true</c>, the JSON is auto-completed.
|
||
|
/// </summary>
|
||
|
public virtual void Close()
|
||
|
{
|
||
|
if (AutoCompleteOnClose)
|
||
|
{
|
||
|
AutoCompleteAll();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes the beginning of a JSON object.
|
||
|
/// </summary>
|
||
|
public virtual void WriteStartObject()
|
||
|
{
|
||
|
InternalWriteStart(JsonToken.StartObject, JsonContainerType.Object);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes the end of a JSON object.
|
||
|
/// </summary>
|
||
|
public virtual void WriteEndObject()
|
||
|
{
|
||
|
InternalWriteEnd(JsonContainerType.Object);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes the beginning of a JSON array.
|
||
|
/// </summary>
|
||
|
public virtual void WriteStartArray()
|
||
|
{
|
||
|
InternalWriteStart(JsonToken.StartArray, JsonContainerType.Array);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes the end of an array.
|
||
|
/// </summary>
|
||
|
public virtual void WriteEndArray()
|
||
|
{
|
||
|
InternalWriteEnd(JsonContainerType.Array);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes the start of a constructor with the given name.
|
||
|
/// </summary>
|
||
|
/// <param name="name">The name of the constructor.</param>
|
||
|
public virtual void WriteStartConstructor(string name)
|
||
|
{
|
||
|
InternalWriteStart(JsonToken.StartConstructor, JsonContainerType.Constructor);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes the end constructor.
|
||
|
/// </summary>
|
||
|
public virtual void WriteEndConstructor()
|
||
|
{
|
||
|
InternalWriteEnd(JsonContainerType.Constructor);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes the property name of a name/value pair of a JSON object.
|
||
|
/// </summary>
|
||
|
/// <param name="name">The name of the property.</param>
|
||
|
public virtual void WritePropertyName(string name)
|
||
|
{
|
||
|
InternalWritePropertyName(name);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes the property name of a name/value pair of 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 virtual void WritePropertyName(string name, bool escape)
|
||
|
{
|
||
|
WritePropertyName(name);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes the end of the current JSON object or array.
|
||
|
/// </summary>
|
||
|
public virtual void WriteEnd()
|
||
|
{
|
||
|
WriteEnd(Peek());
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes the current <see cref="JsonReader"/> token and its children.
|
||
|
/// </summary>
|
||
|
/// <param name="reader">The <see cref="JsonReader"/> to read the token from.</param>
|
||
|
public void WriteToken(JsonReader reader)
|
||
|
{
|
||
|
WriteToken(reader, true);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes the current <see cref="JsonReader"/> token.
|
||
|
/// </summary>
|
||
|
/// <param name="reader">The <see cref="JsonReader"/> to read the token from.</param>
|
||
|
/// <param name="writeChildren">A flag indicating whether the current token's children should be written.</param>
|
||
|
public void WriteToken(JsonReader reader, bool writeChildren)
|
||
|
{
|
||
|
ValidationUtils.ArgumentNotNull(reader, nameof(reader));
|
||
|
|
||
|
WriteToken(reader, writeChildren, true, true);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes the <see cref="JsonToken"/> token and its value.
|
||
|
/// </summary>
|
||
|
/// <param name="token">The <see cref="JsonToken"/> to write.</param>
|
||
|
/// <param name="value">
|
||
|
/// The value to write.
|
||
|
/// A value is only required for tokens that have an associated value, e.g. the <see cref="String"/> property name for <see cref="JsonToken.PropertyName"/>.
|
||
|
/// <c>null</c> can be passed to the method for tokens that don't have a value, e.g. <see cref="JsonToken.StartObject"/>.
|
||
|
/// </param>
|
||
|
public void WriteToken(JsonToken token, object? value)
|
||
|
{
|
||
|
switch (token)
|
||
|
{
|
||
|
case JsonToken.None:
|
||
|
// read to next
|
||
|
break;
|
||
|
case JsonToken.StartObject:
|
||
|
WriteStartObject();
|
||
|
break;
|
||
|
case JsonToken.StartArray:
|
||
|
WriteStartArray();
|
||
|
break;
|
||
|
case JsonToken.StartConstructor:
|
||
|
ValidationUtils.ArgumentNotNull(value, nameof(value));
|
||
|
WriteStartConstructor(value.ToString());
|
||
|
break;
|
||
|
case JsonToken.PropertyName:
|
||
|
ValidationUtils.ArgumentNotNull(value, nameof(value));
|
||
|
WritePropertyName(value.ToString());
|
||
|
break;
|
||
|
case JsonToken.Comment:
|
||
|
WriteComment(value?.ToString());
|
||
|
break;
|
||
|
case JsonToken.Integer:
|
||
|
ValidationUtils.ArgumentNotNull(value, nameof(value));
|
||
|
#if HAVE_BIG_INTEGER
|
||
|
if (value is BigInteger integer)
|
||
|
{
|
||
|
WriteValue(integer);
|
||
|
}
|
||
|
else
|
||
|
#endif
|
||
|
{
|
||
|
WriteValue(Convert.ToInt64(value, CultureInfo.InvariantCulture));
|
||
|
}
|
||
|
break;
|
||
|
case JsonToken.Float:
|
||
|
ValidationUtils.ArgumentNotNull(value, nameof(value));
|
||
|
if (value is decimal decimalValue)
|
||
|
{
|
||
|
WriteValue(decimalValue);
|
||
|
}
|
||
|
else if (value is double doubleValue)
|
||
|
{
|
||
|
WriteValue(doubleValue);
|
||
|
}
|
||
|
else if (value is float floatValue)
|
||
|
{
|
||
|
WriteValue(floatValue);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(Convert.ToDouble(value, CultureInfo.InvariantCulture));
|
||
|
}
|
||
|
break;
|
||
|
case JsonToken.String:
|
||
|
// Allow for a null string. This matches JTokenReader behavior which can read
|
||
|
// a JsonToken.String with a null value.
|
||
|
WriteValue(value?.ToString());
|
||
|
break;
|
||
|
case JsonToken.Boolean:
|
||
|
ValidationUtils.ArgumentNotNull(value, nameof(value));
|
||
|
WriteValue(Convert.ToBoolean(value, CultureInfo.InvariantCulture));
|
||
|
break;
|
||
|
case JsonToken.Null:
|
||
|
WriteNull();
|
||
|
break;
|
||
|
case JsonToken.Undefined:
|
||
|
WriteUndefined();
|
||
|
break;
|
||
|
case JsonToken.EndObject:
|
||
|
WriteEndObject();
|
||
|
break;
|
||
|
case JsonToken.EndArray:
|
||
|
WriteEndArray();
|
||
|
break;
|
||
|
case JsonToken.EndConstructor:
|
||
|
WriteEndConstructor();
|
||
|
break;
|
||
|
case JsonToken.Date:
|
||
|
ValidationUtils.ArgumentNotNull(value, nameof(value));
|
||
|
#if HAVE_DATE_TIME_OFFSET
|
||
|
if (value is DateTimeOffset dt)
|
||
|
{
|
||
|
WriteValue(dt);
|
||
|
}
|
||
|
else
|
||
|
#endif
|
||
|
{
|
||
|
WriteValue(Convert.ToDateTime(value, CultureInfo.InvariantCulture));
|
||
|
}
|
||
|
break;
|
||
|
case JsonToken.Raw:
|
||
|
WriteRawValue(value?.ToString());
|
||
|
break;
|
||
|
case JsonToken.Bytes:
|
||
|
ValidationUtils.ArgumentNotNull(value, nameof(value));
|
||
|
if (value is Guid guid)
|
||
|
{
|
||
|
WriteValue(guid);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue((byte[])value!);
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
throw MiscellaneousUtils.CreateArgumentOutOfRangeException(nameof(token), token, "Unexpected token type.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes the <see cref="JsonToken"/> token.
|
||
|
/// </summary>
|
||
|
/// <param name="token">The <see cref="JsonToken"/> to write.</param>
|
||
|
public void WriteToken(JsonToken token)
|
||
|
{
|
||
|
WriteToken(token, null);
|
||
|
}
|
||
|
|
||
|
internal virtual void WriteToken(JsonReader reader, bool writeChildren, bool writeDateConstructorAsDate, bool writeComments)
|
||
|
{
|
||
|
int initialDepth = CalculateWriteTokenInitialDepth(reader);
|
||
|
|
||
|
do
|
||
|
{
|
||
|
// write a JValue date when the constructor is for a date
|
||
|
if (writeDateConstructorAsDate && reader.TokenType == JsonToken.StartConstructor && string.Equals(reader.Value?.ToString(), "Date", StringComparison.Ordinal))
|
||
|
{
|
||
|
WriteConstructorDate(reader);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (writeComments || reader.TokenType != JsonToken.Comment)
|
||
|
{
|
||
|
WriteToken(reader.TokenType, reader.Value);
|
||
|
}
|
||
|
}
|
||
|
} while (
|
||
|
// stop if we have reached the end of the token being read
|
||
|
initialDepth - 1 < reader.Depth - (JsonTokenUtils.IsEndToken(reader.TokenType) ? 1 : 0)
|
||
|
&& writeChildren
|
||
|
&& reader.Read());
|
||
|
|
||
|
if (IsWriteTokenIncomplete(reader, writeChildren, initialDepth))
|
||
|
{
|
||
|
throw JsonWriterException.Create(this, "Unexpected end when reading token.", null);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private bool IsWriteTokenIncomplete(JsonReader reader, bool writeChildren, int initialDepth)
|
||
|
{
|
||
|
int finalDepth = CalculateWriteTokenFinalDepth(reader);
|
||
|
return initialDepth < finalDepth ||
|
||
|
(writeChildren && initialDepth == finalDepth && JsonTokenUtils.IsStartToken(reader.TokenType));
|
||
|
}
|
||
|
|
||
|
private int CalculateWriteTokenInitialDepth(JsonReader reader)
|
||
|
{
|
||
|
JsonToken type = reader.TokenType;
|
||
|
if (type == JsonToken.None)
|
||
|
{
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
return JsonTokenUtils.IsStartToken(type) ? reader.Depth : reader.Depth + 1;
|
||
|
}
|
||
|
|
||
|
private int CalculateWriteTokenFinalDepth(JsonReader reader)
|
||
|
{
|
||
|
JsonToken type = reader.TokenType;
|
||
|
if (type == JsonToken.None)
|
||
|
{
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
return JsonTokenUtils.IsEndToken(type) ? reader.Depth - 1 : reader.Depth;
|
||
|
}
|
||
|
|
||
|
private void WriteConstructorDate(JsonReader reader)
|
||
|
{
|
||
|
if (!JavaScriptUtils.TryGetDateFromConstructorJson(reader, out DateTime dateTime, out string? errorMessage))
|
||
|
{
|
||
|
throw JsonWriterException.Create(this, errorMessage, null);
|
||
|
}
|
||
|
|
||
|
WriteValue(dateTime);
|
||
|
}
|
||
|
|
||
|
private void WriteEnd(JsonContainerType type)
|
||
|
{
|
||
|
switch (type)
|
||
|
{
|
||
|
case JsonContainerType.Object:
|
||
|
WriteEndObject();
|
||
|
break;
|
||
|
case JsonContainerType.Array:
|
||
|
WriteEndArray();
|
||
|
break;
|
||
|
case JsonContainerType.Constructor:
|
||
|
WriteEndConstructor();
|
||
|
break;
|
||
|
default:
|
||
|
throw JsonWriterException.Create(this, "Unexpected type when writing end: " + type, null);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void AutoCompleteAll()
|
||
|
{
|
||
|
while (Top > 0)
|
||
|
{
|
||
|
WriteEnd();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private JsonToken GetCloseTokenForType(JsonContainerType type)
|
||
|
{
|
||
|
switch (type)
|
||
|
{
|
||
|
case JsonContainerType.Object:
|
||
|
return JsonToken.EndObject;
|
||
|
case JsonContainerType.Array:
|
||
|
return JsonToken.EndArray;
|
||
|
case JsonContainerType.Constructor:
|
||
|
return JsonToken.EndConstructor;
|
||
|
default:
|
||
|
throw JsonWriterException.Create(this, "No close token for type: " + type, null);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void AutoCompleteClose(JsonContainerType type)
|
||
|
{
|
||
|
int levelsToComplete = CalculateLevelsToComplete(type);
|
||
|
|
||
|
for (int i = 0; i < levelsToComplete; i++)
|
||
|
{
|
||
|
JsonToken token = GetCloseTokenForType(Pop());
|
||
|
|
||
|
if (_currentState == State.Property)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
|
||
|
if (_formatting == Formatting.Indented)
|
||
|
{
|
||
|
if (_currentState != State.ObjectStart && _currentState != State.ArrayStart)
|
||
|
{
|
||
|
WriteIndent();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
WriteEnd(token);
|
||
|
|
||
|
UpdateCurrentState();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private int CalculateLevelsToComplete(JsonContainerType type)
|
||
|
{
|
||
|
int levelsToComplete = 0;
|
||
|
|
||
|
if (_currentPosition.Type == type)
|
||
|
{
|
||
|
levelsToComplete = 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int top = Top - 2;
|
||
|
for (int i = top; i >= 0; i--)
|
||
|
{
|
||
|
int currentLevel = top - i;
|
||
|
|
||
|
if (_stack![currentLevel].Type == type)
|
||
|
{
|
||
|
levelsToComplete = i + 2;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (levelsToComplete == 0)
|
||
|
{
|
||
|
throw JsonWriterException.Create(this, "No token to close.", null);
|
||
|
}
|
||
|
|
||
|
return levelsToComplete;
|
||
|
}
|
||
|
|
||
|
private void UpdateCurrentState()
|
||
|
{
|
||
|
JsonContainerType currentLevelType = Peek();
|
||
|
|
||
|
switch (currentLevelType)
|
||
|
{
|
||
|
case JsonContainerType.Object:
|
||
|
_currentState = State.Object;
|
||
|
break;
|
||
|
case JsonContainerType.Array:
|
||
|
_currentState = State.Array;
|
||
|
break;
|
||
|
case JsonContainerType.Constructor:
|
||
|
_currentState = State.Array;
|
||
|
break;
|
||
|
case JsonContainerType.None:
|
||
|
_currentState = State.Start;
|
||
|
break;
|
||
|
default:
|
||
|
throw JsonWriterException.Create(this, "Unknown JsonType: " + currentLevelType, null);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes the specified end token.
|
||
|
/// </summary>
|
||
|
/// <param name="token">The end token to write.</param>
|
||
|
protected virtual void WriteEnd(JsonToken token)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes indent characters.
|
||
|
/// </summary>
|
||
|
protected virtual void WriteIndent()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes the JSON value delimiter.
|
||
|
/// </summary>
|
||
|
protected virtual void WriteValueDelimiter()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes an indent space.
|
||
|
/// </summary>
|
||
|
protected virtual void WriteIndentSpace()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
internal void AutoComplete(JsonToken tokenBeingWritten)
|
||
|
{
|
||
|
// gets new state based on the current state and what is being written
|
||
|
State newState = StateArray[(int)tokenBeingWritten][(int)_currentState];
|
||
|
|
||
|
if (newState == State.Error)
|
||
|
{
|
||
|
throw JsonWriterException.Create(this, "Token {0} in state {1} would result in an invalid JSON object.".FormatWith(CultureInfo.InvariantCulture, tokenBeingWritten.ToString(), _currentState.ToString()), null);
|
||
|
}
|
||
|
|
||
|
if ((_currentState == State.Object || _currentState == State.Array || _currentState == State.Constructor) && tokenBeingWritten != JsonToken.Comment)
|
||
|
{
|
||
|
WriteValueDelimiter();
|
||
|
}
|
||
|
|
||
|
if (_formatting == Formatting.Indented)
|
||
|
{
|
||
|
if (_currentState == State.Property)
|
||
|
{
|
||
|
WriteIndentSpace();
|
||
|
}
|
||
|
|
||
|
// don't indent a property when it is the first token to be written (i.e. at the start)
|
||
|
if ((_currentState == State.Array || _currentState == State.ArrayStart || _currentState == State.Constructor || _currentState == State.ConstructorStart)
|
||
|
|| (tokenBeingWritten == JsonToken.PropertyName && _currentState != State.Start))
|
||
|
{
|
||
|
WriteIndent();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_currentState = newState;
|
||
|
}
|
||
|
|
||
|
#region WriteValue methods
|
||
|
/// <summary>
|
||
|
/// Writes a null value.
|
||
|
/// </summary>
|
||
|
public virtual void WriteNull()
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.Null);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes an undefined value.
|
||
|
/// </summary>
|
||
|
public virtual void WriteUndefined()
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.Undefined);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes raw JSON without changing the writer's state.
|
||
|
/// </summary>
|
||
|
/// <param name="json">The raw JSON to write.</param>
|
||
|
public virtual void WriteRaw(string? json)
|
||
|
{
|
||
|
InternalWriteRaw();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes raw JSON where a value is expected and updates the writer's state.
|
||
|
/// </summary>
|
||
|
/// <param name="json">The raw JSON to write.</param>
|
||
|
public virtual void WriteRawValue(string? json)
|
||
|
{
|
||
|
// hack. want writer to change state as if a value had been written
|
||
|
UpdateScopeWithFinishedValue();
|
||
|
AutoComplete(JsonToken.Undefined);
|
||
|
WriteRaw(json);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="String"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="String"/> value to write.</param>
|
||
|
public virtual void WriteValue(string? value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.String);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Int32"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Int32"/> value to write.</param>
|
||
|
public virtual void WriteValue(int value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.Integer);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="UInt32"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="UInt32"/> value to write.</param>
|
||
|
[CLSCompliant(false)]
|
||
|
public virtual void WriteValue(uint value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.Integer);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Int64"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Int64"/> value to write.</param>
|
||
|
public virtual void WriteValue(long value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.Integer);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="UInt64"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="UInt64"/> value to write.</param>
|
||
|
[CLSCompliant(false)]
|
||
|
public virtual void WriteValue(ulong value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.Integer);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Single"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Single"/> value to write.</param>
|
||
|
public virtual void WriteValue(float value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.Float);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Double"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Double"/> value to write.</param>
|
||
|
public virtual void WriteValue(double value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.Float);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Boolean"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Boolean"/> value to write.</param>
|
||
|
public virtual void WriteValue(bool value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.Boolean);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Int16"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Int16"/> value to write.</param>
|
||
|
public virtual void WriteValue(short value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.Integer);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="UInt16"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="UInt16"/> value to write.</param>
|
||
|
[CLSCompliant(false)]
|
||
|
public virtual void WriteValue(ushort value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.Integer);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Char"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Char"/> value to write.</param>
|
||
|
public virtual void WriteValue(char value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.String);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Byte"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Byte"/> value to write.</param>
|
||
|
public virtual void WriteValue(byte value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.Integer);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="SByte"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="SByte"/> value to write.</param>
|
||
|
[CLSCompliant(false)]
|
||
|
public virtual void WriteValue(sbyte value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.Integer);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Decimal"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Decimal"/> value to write.</param>
|
||
|
public virtual void WriteValue(decimal value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.Float);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="DateTime"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="DateTime"/> value to write.</param>
|
||
|
public virtual void WriteValue(DateTime value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.Date);
|
||
|
}
|
||
|
|
||
|
#if HAVE_DATE_TIME_OFFSET
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="DateTimeOffset"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="DateTimeOffset"/> value to write.</param>
|
||
|
public virtual void WriteValue(DateTimeOffset value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.Date);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Guid"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Guid"/> value to write.</param>
|
||
|
public virtual void WriteValue(Guid value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.String);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="TimeSpan"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="TimeSpan"/> value to write.</param>
|
||
|
public virtual void WriteValue(TimeSpan value)
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.String);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Nullable{T}"/> of <see cref="Int32"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Nullable{T}"/> of <see cref="Int32"/> value to write.</param>
|
||
|
public virtual void WriteValue(int? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(value.GetValueOrDefault());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Nullable{T}"/> of <see cref="UInt32"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Nullable{T}"/> of <see cref="UInt32"/> value to write.</param>
|
||
|
[CLSCompliant(false)]
|
||
|
public virtual void WriteValue(uint? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(value.GetValueOrDefault());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Nullable{T}"/> of <see cref="Int64"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Nullable{T}"/> of <see cref="Int64"/> value to write.</param>
|
||
|
public virtual void WriteValue(long? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(value.GetValueOrDefault());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Nullable{T}"/> of <see cref="UInt64"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Nullable{T}"/> of <see cref="UInt64"/> value to write.</param>
|
||
|
[CLSCompliant(false)]
|
||
|
public virtual void WriteValue(ulong? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(value.GetValueOrDefault());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <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 virtual void WriteValue(float? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(value.GetValueOrDefault());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <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 virtual void WriteValue(double? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(value.GetValueOrDefault());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Nullable{T}"/> of <see cref="Boolean"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Nullable{T}"/> of <see cref="Boolean"/> value to write.</param>
|
||
|
public virtual void WriteValue(bool? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(value.GetValueOrDefault());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Nullable{T}"/> of <see cref="Int16"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Nullable{T}"/> of <see cref="Int16"/> value to write.</param>
|
||
|
public virtual void WriteValue(short? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(value.GetValueOrDefault());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Nullable{T}"/> of <see cref="UInt16"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Nullable{T}"/> of <see cref="UInt16"/> value to write.</param>
|
||
|
[CLSCompliant(false)]
|
||
|
public virtual void WriteValue(ushort? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(value.GetValueOrDefault());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Nullable{T}"/> of <see cref="Char"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Nullable{T}"/> of <see cref="Char"/> value to write.</param>
|
||
|
public virtual void WriteValue(char? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(value.GetValueOrDefault());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Nullable{T}"/> of <see cref="Byte"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Nullable{T}"/> of <see cref="Byte"/> value to write.</param>
|
||
|
public virtual void WriteValue(byte? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(value.GetValueOrDefault());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Nullable{T}"/> of <see cref="SByte"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Nullable{T}"/> of <see cref="SByte"/> value to write.</param>
|
||
|
[CLSCompliant(false)]
|
||
|
public virtual void WriteValue(sbyte? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(value.GetValueOrDefault());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Nullable{T}"/> of <see cref="Decimal"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Nullable{T}"/> of <see cref="Decimal"/> value to write.</param>
|
||
|
public virtual void WriteValue(decimal? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(value.GetValueOrDefault());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Nullable{T}"/> of <see cref="DateTime"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Nullable{T}"/> of <see cref="DateTime"/> value to write.</param>
|
||
|
public virtual void WriteValue(DateTime? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(value.GetValueOrDefault());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if HAVE_DATE_TIME_OFFSET
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Nullable{T}"/> of <see cref="DateTimeOffset"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Nullable{T}"/> of <see cref="DateTimeOffset"/> value to write.</param>
|
||
|
public virtual void WriteValue(DateTimeOffset? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(value.GetValueOrDefault());
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Nullable{T}"/> of <see cref="Guid"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Nullable{T}"/> of <see cref="Guid"/> value to write.</param>
|
||
|
public virtual void WriteValue(Guid? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(value.GetValueOrDefault());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Nullable{T}"/> of <see cref="TimeSpan"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Nullable{T}"/> of <see cref="TimeSpan"/> value to write.</param>
|
||
|
public virtual void WriteValue(TimeSpan? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WriteValue(value.GetValueOrDefault());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Byte"/>[] value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Byte"/>[] value to write.</param>
|
||
|
public virtual void WriteValue(byte[]? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.Bytes);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a <see cref="Uri"/> value.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The <see cref="Uri"/> value to write.</param>
|
||
|
public virtual void WriteValue(Uri? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
InternalWriteValue(JsonToken.String);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <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 virtual void WriteValue(object? value)
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
WriteNull();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
#if HAVE_BIG_INTEGER
|
||
|
// this is here because adding a WriteValue(BigInteger) to JsonWriter will
|
||
|
// mean the user has to add a reference to System.Numerics.dll
|
||
|
if (value is BigInteger)
|
||
|
{
|
||
|
throw CreateUnsupportedTypeException(this, value);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
WriteValue(this, ConvertUtils.GetTypeCode(value.GetType()), value);
|
||
|
}
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes a comment <c>/*...*/</c> containing the specified text.
|
||
|
/// </summary>
|
||
|
/// <param name="text">Text to place inside the comment.</param>
|
||
|
public virtual void WriteComment(string? text)
|
||
|
{
|
||
|
InternalWriteComment();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Writes the given white space.
|
||
|
/// </summary>
|
||
|
/// <param name="ws">The string of white space characters.</param>
|
||
|
public virtual void WriteWhitespace(string ws)
|
||
|
{
|
||
|
InternalWriteWhitespace(ws);
|
||
|
}
|
||
|
|
||
|
void IDisposable.Dispose()
|
||
|
{
|
||
|
Dispose(true);
|
||
|
GC.SuppressFinalize(this);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Releases unmanaged and - optionally - managed resources.
|
||
|
/// </summary>
|
||
|
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||
|
protected virtual void Dispose(bool disposing)
|
||
|
{
|
||
|
if (_currentState != State.Closed && disposing)
|
||
|
{
|
||
|
Close();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal static void WriteValue(JsonWriter writer, PrimitiveTypeCode typeCode, object value)
|
||
|
{
|
||
|
while (true)
|
||
|
{
|
||
|
switch (typeCode)
|
||
|
{
|
||
|
case PrimitiveTypeCode.Char:
|
||
|
writer.WriteValue((char)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.CharNullable:
|
||
|
writer.WriteValue((value == null) ? (char?)null : (char)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.Boolean:
|
||
|
writer.WriteValue((bool)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.BooleanNullable:
|
||
|
writer.WriteValue((value == null) ? (bool?)null : (bool)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.SByte:
|
||
|
writer.WriteValue((sbyte)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.SByteNullable:
|
||
|
writer.WriteValue((value == null) ? (sbyte?)null : (sbyte)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.Int16:
|
||
|
writer.WriteValue((short)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.Int16Nullable:
|
||
|
writer.WriteValue((value == null) ? (short?)null : (short)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.UInt16:
|
||
|
writer.WriteValue((ushort)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.UInt16Nullable:
|
||
|
writer.WriteValue((value == null) ? (ushort?)null : (ushort)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.Int32:
|
||
|
writer.WriteValue((int)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.Int32Nullable:
|
||
|
writer.WriteValue((value == null) ? (int?)null : (int)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.Byte:
|
||
|
writer.WriteValue((byte)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.ByteNullable:
|
||
|
writer.WriteValue((value == null) ? (byte?)null : (byte)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.UInt32:
|
||
|
writer.WriteValue((uint)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.UInt32Nullable:
|
||
|
writer.WriteValue((value == null) ? (uint?)null : (uint)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.Int64:
|
||
|
writer.WriteValue((long)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.Int64Nullable:
|
||
|
writer.WriteValue((value == null) ? (long?)null : (long)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.UInt64:
|
||
|
writer.WriteValue((ulong)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.UInt64Nullable:
|
||
|
writer.WriteValue((value == null) ? (ulong?)null : (ulong)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.Single:
|
||
|
writer.WriteValue((float)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.SingleNullable:
|
||
|
writer.WriteValue((value == null) ? (float?)null : (float)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.Double:
|
||
|
writer.WriteValue((double)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.DoubleNullable:
|
||
|
writer.WriteValue((value == null) ? (double?)null : (double)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.DateTime:
|
||
|
writer.WriteValue((DateTime)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.DateTimeNullable:
|
||
|
writer.WriteValue((value == null) ? (DateTime?)null : (DateTime)value);
|
||
|
return;
|
||
|
|
||
|
#if HAVE_DATE_TIME_OFFSET
|
||
|
case PrimitiveTypeCode.DateTimeOffset:
|
||
|
writer.WriteValue((DateTimeOffset)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.DateTimeOffsetNullable:
|
||
|
writer.WriteValue((value == null) ? (DateTimeOffset?)null : (DateTimeOffset)value);
|
||
|
return;
|
||
|
#endif
|
||
|
case PrimitiveTypeCode.Decimal:
|
||
|
writer.WriteValue((decimal)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.DecimalNullable:
|
||
|
writer.WriteValue((value == null) ? (decimal?)null : (decimal)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.Guid:
|
||
|
writer.WriteValue((Guid)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.GuidNullable:
|
||
|
writer.WriteValue((value == null) ? (Guid?)null : (Guid)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.TimeSpan:
|
||
|
writer.WriteValue((TimeSpan)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.TimeSpanNullable:
|
||
|
writer.WriteValue((value == null) ? (TimeSpan?)null : (TimeSpan)value);
|
||
|
return;
|
||
|
|
||
|
#if HAVE_BIG_INTEGER
|
||
|
case PrimitiveTypeCode.BigInteger:
|
||
|
// this will call to WriteValue(object)
|
||
|
writer.WriteValue((BigInteger)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.BigIntegerNullable:
|
||
|
// this will call to WriteValue(object)
|
||
|
writer.WriteValue((value == null) ? (BigInteger?)null : (BigInteger)value);
|
||
|
return;
|
||
|
#endif
|
||
|
case PrimitiveTypeCode.Uri:
|
||
|
writer.WriteValue((Uri)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.String:
|
||
|
writer.WriteValue((string)value);
|
||
|
return;
|
||
|
|
||
|
case PrimitiveTypeCode.Bytes:
|
||
|
writer.WriteValue((byte[])value);
|
||
|
return;
|
||
|
|
||
|
#if HAVE_DB_NULL_TYPE_CODE
|
||
|
case PrimitiveTypeCode.DBNull:
|
||
|
writer.WriteNull();
|
||
|
return;
|
||
|
#endif
|
||
|
default:
|
||
|
#if HAVE_ICONVERTIBLE
|
||
|
if (value is IConvertible convertible)
|
||
|
{
|
||
|
ResolveConvertibleValue(convertible, out typeCode, out value);
|
||
|
continue;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// write an unknown null value, fix https://github.com/JamesNK/Newtonsoft.Json/issues/1460
|
||
|
if (value == null)
|
||
|
{
|
||
|
writer.WriteNull();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
throw CreateUnsupportedTypeException(writer, value);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if HAVE_ICONVERTIBLE
|
||
|
private static void ResolveConvertibleValue(IConvertible convertible, out PrimitiveTypeCode typeCode, out object value)
|
||
|
{
|
||
|
// the value is a non-standard IConvertible
|
||
|
// convert to the underlying value and retry
|
||
|
TypeInformation typeInformation = ConvertUtils.GetTypeInformation(convertible);
|
||
|
|
||
|
// if convertible has an underlying typecode of Object then attempt to convert it to a string
|
||
|
typeCode = typeInformation.TypeCode == PrimitiveTypeCode.Object ? PrimitiveTypeCode.String : typeInformation.TypeCode;
|
||
|
Type resolvedType = typeInformation.TypeCode == PrimitiveTypeCode.Object ? typeof(string) : typeInformation.Type;
|
||
|
value = convertible.ToType(resolvedType, CultureInfo.InvariantCulture);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
private static JsonWriterException CreateUnsupportedTypeException(JsonWriter writer, object value)
|
||
|
{
|
||
|
return JsonWriterException.Create(writer, "Unsupported type: {0}. Use the JsonSerializer class to get the object's JSON representation.".FormatWith(CultureInfo.InvariantCulture, value.GetType()), null);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Sets the state of the <see cref="JsonWriter"/>.
|
||
|
/// </summary>
|
||
|
/// <param name="token">The <see cref="JsonToken"/> being written.</param>
|
||
|
/// <param name="value">The value being written.</param>
|
||
|
protected void SetWriteState(JsonToken token, object value)
|
||
|
{
|
||
|
switch (token)
|
||
|
{
|
||
|
case JsonToken.StartObject:
|
||
|
InternalWriteStart(token, JsonContainerType.Object);
|
||
|
break;
|
||
|
case JsonToken.StartArray:
|
||
|
InternalWriteStart(token, JsonContainerType.Array);
|
||
|
break;
|
||
|
case JsonToken.StartConstructor:
|
||
|
InternalWriteStart(token, JsonContainerType.Constructor);
|
||
|
break;
|
||
|
case JsonToken.PropertyName:
|
||
|
if (!(value is string s))
|
||
|
{
|
||
|
throw new ArgumentException("A name is required when setting property name state.", nameof(value));
|
||
|
}
|
||
|
|
||
|
InternalWritePropertyName(s);
|
||
|
break;
|
||
|
case JsonToken.Comment:
|
||
|
InternalWriteComment();
|
||
|
break;
|
||
|
case JsonToken.Raw:
|
||
|
InternalWriteRaw();
|
||
|
break;
|
||
|
case JsonToken.Integer:
|
||
|
case JsonToken.Float:
|
||
|
case JsonToken.String:
|
||
|
case JsonToken.Boolean:
|
||
|
case JsonToken.Date:
|
||
|
case JsonToken.Bytes:
|
||
|
case JsonToken.Null:
|
||
|
case JsonToken.Undefined:
|
||
|
InternalWriteValue(token);
|
||
|
break;
|
||
|
case JsonToken.EndObject:
|
||
|
InternalWriteEnd(JsonContainerType.Object);
|
||
|
break;
|
||
|
case JsonToken.EndArray:
|
||
|
InternalWriteEnd(JsonContainerType.Array);
|
||
|
break;
|
||
|
case JsonToken.EndConstructor:
|
||
|
InternalWriteEnd(JsonContainerType.Constructor);
|
||
|
break;
|
||
|
default:
|
||
|
throw new ArgumentOutOfRangeException(nameof(token));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void InternalWriteEnd(JsonContainerType container)
|
||
|
{
|
||
|
AutoCompleteClose(container);
|
||
|
}
|
||
|
|
||
|
internal void InternalWritePropertyName(string name)
|
||
|
{
|
||
|
_currentPosition.PropertyName = name;
|
||
|
AutoComplete(JsonToken.PropertyName);
|
||
|
}
|
||
|
|
||
|
internal void InternalWriteRaw()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
internal void InternalWriteStart(JsonToken token, JsonContainerType container)
|
||
|
{
|
||
|
UpdateScopeWithFinishedValue();
|
||
|
AutoComplete(token);
|
||
|
Push(container);
|
||
|
}
|
||
|
|
||
|
internal void InternalWriteValue(JsonToken token)
|
||
|
{
|
||
|
UpdateScopeWithFinishedValue();
|
||
|
AutoComplete(token);
|
||
|
}
|
||
|
|
||
|
internal void InternalWriteWhitespace(string ws)
|
||
|
{
|
||
|
if (ws != null)
|
||
|
{
|
||
|
if (!StringUtils.IsWhiteSpace(ws))
|
||
|
{
|
||
|
throw JsonWriterException.Create(this, "Only white space characters should be used.", null);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void InternalWriteComment()
|
||
|
{
|
||
|
AutoComplete(JsonToken.Comment);
|
||
|
}
|
||
|
}
|
||
|
}
|