#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.Text; using System.IO; using System.Xml; using Newtonsoft.Json.Utilities; using Newtonsoft.Json.Linq; using System.Globalization; namespace Newtonsoft.Json { /// /// Specifies the state of the . /// public enum WriteState { /// /// An exception has been thrown, which has left the in an invalid state. /// You may call the method to put the in the Closed state. /// Any other method calls results in an being thrown. /// Error, /// /// The method has been called. /// Closed, /// /// An object is being written. /// Object, /// /// A array is being written. /// Array, /// /// A constructor is being written. /// Constructor, /// /// A property is being written. /// Property, /// /// A write method has not been called. /// Start } /// /// Specifies formatting options for the . /// public enum Formatting { /// /// No special formatting is applied. This is the default. /// None, /// /// Causes child objects to be indented according to the and settings. /// Indented } /// /// Represents a writer that provides a fast, non-cached, forward-only way of generating Json data. /// public abstract class JsonWriter : IDisposable { private enum State { Start, Property, ObjectStart, Object, ArrayStart, Array, ConstructorStart, Constructor, Bytes, Closed, Error } // array that gives a new state based on the current state an the token being written private static readonly State[][] stateArray = 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 }, /* StartProperty */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 */new[]{ State.Start, State.Object, State.Error, State.Error, State.Array, State.Array, State.Constructor, State.Constructor, State.Error, State.Error }, }; private int _top; private readonly List _stack; private State _currentState; private Formatting _formatting; /// /// Gets the top. /// /// The top. protected internal int Top { get { return _top; } } /// /// Gets the state of the writer. /// 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 new JsonWriterException("Invalid state: " + _currentState); } } } /// /// Indicates how the output is formatted. /// public Formatting Formatting { get { return _formatting; } set { _formatting = value; } } /// /// Creates an instance of the JsonWriter class. /// public JsonWriter() { _stack = new List(8); _stack.Add(JTokenType.None); _currentState = State.Start; _formatting = Formatting.None; } private void Push(JTokenType value) { _top++; if (_stack.Count <= _top) _stack.Add(value); else _stack[_top] = value; } private JTokenType Pop() { JTokenType value = Peek(); _top--; return value; } private JTokenType Peek() { return _stack[_top]; } /// /// Flushes whatever is in the buffer to the underlying streams and also flushes the underlying stream. /// public abstract void Flush(); /// /// Closes this stream and the underlying stream. /// public virtual void Close() { AutoCompleteAll(); } /// /// Writes the beginning of a Json object. /// public virtual void WriteStartObject() { AutoComplete(JsonToken.StartObject); Push(JTokenType.Object); } /// /// Writes the end of a Json object. /// public void WriteEndObject() { AutoCompleteClose(JsonToken.EndObject); } /// /// Writes the beginning of a Json array. /// public virtual void WriteStartArray() { AutoComplete(JsonToken.StartArray); Push(JTokenType.Array); } /// /// Writes the end of an array. /// public void WriteEndArray() { AutoCompleteClose(JsonToken.EndArray); } /// /// Writes the start of a constructor with the given name. /// /// The name of the constructor. public virtual void WriteStartConstructor(string name) { AutoComplete(JsonToken.StartConstructor); Push(JTokenType.Constructor); } /// /// Writes the end constructor. /// public void WriteEndConstructor() { AutoCompleteClose(JsonToken.EndConstructor); } /// /// Writes the property name of a name/value pair on a Json object. /// /// The name of the property. public virtual void WritePropertyName(string name) { AutoComplete(JsonToken.PropertyName); } /// /// Writes the end of the current Json object or array. /// public void WriteEnd() { WriteEnd(Peek()); } /// /// Writes the current token. /// /// The to read the token from. public void WriteToken(JsonReader reader) { ValidationUtils.ArgumentNotNull(reader, "reader"); int initialDepth; if (reader.TokenType == JsonToken.None) initialDepth = -1; else if (!IsStartToken(reader.TokenType)) initialDepth = reader.Depth + 1; else initialDepth = reader.Depth; WriteToken(reader, initialDepth); } internal void WriteToken(JsonReader reader, int initialDepth) { do { switch (reader.TokenType) { case JsonToken.None: // read to next break; case JsonToken.StartObject: WriteStartObject(); break; case JsonToken.StartArray: WriteStartArray(); break; case JsonToken.StartConstructor: string constructorName = reader.Value.ToString(); // write a JValue date when the constructor is for a date if (string.Compare(constructorName, "Date", StringComparison.Ordinal) == 0) WriteConstructorDate(reader); else WriteStartConstructor(reader.Value.ToString()); break; case JsonToken.PropertyName: WritePropertyName(reader.Value.ToString()); break; case JsonToken.Comment: WriteComment(reader.Value.ToString()); break; case JsonToken.Integer: WriteValue((long)reader.Value); break; case JsonToken.Float: WriteValue((double)reader.Value); break; case JsonToken.String: WriteValue(reader.Value.ToString()); break; case JsonToken.Boolean: WriteValue((bool)reader.Value); 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: WriteValue((DateTime)reader.Value); break; case JsonToken.Raw: WriteRawValue((string)reader.Value); break; case JsonToken.Bytes: WriteValue((byte[])reader.Value); break; default: throw MiscellaneousUtils.CreateArgumentOutOfRangeException("TokenType", reader.TokenType, "Unexpected token type."); } } while ( // stop if we have reached the end of the token being read initialDepth - 1 < reader.Depth - (IsEndToken(reader.TokenType) ? 1 : 0) && reader.Read()); } private void WriteConstructorDate(JsonReader reader) { if (!reader.Read()) throw new Exception("Unexpected end while reading date constructor."); if (reader.TokenType != JsonToken.Integer) throw new Exception("Unexpected token while reading date constructor. Expected Integer, got " + reader.TokenType); long ticks = (long)reader.Value; DateTime date = JsonConvert.ConvertJavaScriptTicksToDateTime(ticks); if (!reader.Read()) throw new Exception("Unexpected end while reading date constructor."); if (reader.TokenType != JsonToken.EndConstructor) throw new Exception("Unexpected token while reading date constructor. Expected EndConstructor, got " + reader.TokenType); WriteValue(date); } private bool IsEndToken(JsonToken token) { switch (token) { case JsonToken.EndObject: case JsonToken.EndArray: case JsonToken.EndConstructor: return true; default: return false; } } private bool IsStartToken(JsonToken token) { switch (token) { case JsonToken.StartObject: case JsonToken.StartArray: case JsonToken.StartConstructor: return true; default: return false; } } private void WriteEnd(JTokenType type) { switch (type) { case JTokenType.Object: WriteEndObject(); break; case JTokenType.Array: WriteEndArray(); break; case JTokenType.Constructor: WriteEndConstructor(); break; default: throw new JsonWriterException("Unexpected type when writing end: " + type); } } private void AutoCompleteAll() { while (_top > 0) { WriteEnd(); } } private JTokenType GetTypeForCloseToken(JsonToken token) { switch (token) { case JsonToken.EndObject: return JTokenType.Object; case JsonToken.EndArray: return JTokenType.Array; case JsonToken.EndConstructor: return JTokenType.Constructor; default: throw new JsonWriterException("No type for token: " + token); } } private JsonToken GetCloseTokenForType(JTokenType type) { switch (type) { case JTokenType.Object: return JsonToken.EndObject; case JTokenType.Array: return JsonToken.EndArray; case JTokenType.Constructor: return JsonToken.EndConstructor; default: throw new JsonWriterException("No close token for type: " + type); } } private void AutoCompleteClose(JsonToken tokenBeingClosed) { // write closing symbol and calculate new state int levelsToComplete = 0; for (int i = 0; i < _top; i++) { int currentLevel = _top - i; if (_stack[currentLevel] == GetTypeForCloseToken(tokenBeingClosed)) { levelsToComplete = i + 1; break; } } if (levelsToComplete == 0) throw new JsonWriterException("No token to close."); for (int i = 0; i < levelsToComplete; i++) { JsonToken token = GetCloseTokenForType(Pop()); if (_currentState != State.ObjectStart && _currentState != State.ArrayStart) WriteIndent(); WriteEnd(token); } JTokenType currentLevelType = Peek(); switch (currentLevelType) { case JTokenType.Object: _currentState = State.Object; break; case JTokenType.Array: _currentState = State.Array; break; case JTokenType.Constructor: _currentState = State.Array; break; case JTokenType.None: _currentState = State.Start; break; default: throw new JsonWriterException("Unknown JsonType: " + currentLevelType); } } /// /// Writes the specified end token. /// /// The end token to write. protected virtual void WriteEnd(JsonToken token) { } /// /// Writes indent characters. /// protected virtual void WriteIndent() { } /// /// Writes the JSON value delimiter. /// protected virtual void WriteValueDelimiter() { } /// /// Writes an indent space. /// protected virtual void WriteIndentSpace() { } internal void AutoComplete(JsonToken tokenBeingWritten) { int token; switch (tokenBeingWritten) { default: token = (int)tokenBeingWritten; break; case JsonToken.Integer: case JsonToken.Float: case JsonToken.String: case JsonToken.Boolean: case JsonToken.Null: case JsonToken.Undefined: case JsonToken.Date: case JsonToken.Bytes: // a value is being written token = 7; break; } // gets new state based on the current state and what is being written State newState = stateArray[token][(int)_currentState]; if (newState == State.Error) throw new JsonWriterException("Token {0} in state {1} would result in an invalid JavaScript object.".FormatWith(CultureInfo.InvariantCulture, tokenBeingWritten.ToString(), _currentState.ToString())); if ((_currentState == State.Object || _currentState == State.Array || _currentState == State.Constructor) && tokenBeingWritten != JsonToken.Comment) { WriteValueDelimiter(); } else if (_currentState == State.Property) { if (_formatting == Formatting.Indented) WriteIndentSpace(); } WriteState writeState = WriteState; // don't indent a property when it is the first token to be written (i.e. at the start) if ((tokenBeingWritten == JsonToken.PropertyName && writeState != WriteState.Start) || writeState == WriteState.Array || writeState == WriteState.Constructor) { WriteIndent(); } _currentState = newState; } #region WriteValue methods /// /// Writes a null value. /// public virtual void WriteNull() { AutoComplete(JsonToken.Null); } /// /// Writes an undefined value. /// public virtual void WriteUndefined() { AutoComplete(JsonToken.Undefined); } /// /// Writes raw JSON without changing the writer's state. /// /// The raw JSON to write. public virtual void WriteRaw(string json) { } /// /// Writes raw JSON where a value is expected and updates the writer's state. /// /// The raw JSON to write. public virtual void WriteRawValue(string json) { // hack. want writer to change state as if a value had been written AutoComplete(JsonToken.Undefined); WriteRaw(json); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(string value) { AutoComplete(JsonToken.String); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(int value) { AutoComplete(JsonToken.Integer); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(uint value) { AutoComplete(JsonToken.Integer); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(long value) { AutoComplete(JsonToken.Integer); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(ulong value) { AutoComplete(JsonToken.Integer); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(float value) { AutoComplete(JsonToken.Float); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(double value) { AutoComplete(JsonToken.Float); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(bool value) { AutoComplete(JsonToken.Boolean); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(short value) { AutoComplete(JsonToken.Integer); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(ushort value) { AutoComplete(JsonToken.Integer); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(char value) { AutoComplete(JsonToken.String); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(byte value) { AutoComplete(JsonToken.Integer); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(sbyte value) { AutoComplete(JsonToken.Integer); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(decimal value) { AutoComplete(JsonToken.Float); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(DateTime value) { AutoComplete(JsonToken.Date); } #if !PocketPC && !NET20 /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(DateTimeOffset value) { AutoComplete(JsonToken.Date); } #endif /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(int? value) { if (value == null) WriteNull(); else WriteValue(value.Value); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(uint? value) { if (value == null) WriteNull(); else WriteValue(value.Value); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(long? value) { if (value == null) WriteNull(); else WriteValue(value.Value); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(ulong? value) { if (value == null) WriteNull(); else WriteValue(value.Value); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(float? value) { if (value == null) WriteNull(); else WriteValue(value.Value); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(double? value) { if (value == null) WriteNull(); else WriteValue(value.Value); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(bool? value) { if (value == null) WriteNull(); else WriteValue(value.Value); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(short? value) { if (value == null) WriteNull(); else WriteValue(value.Value); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(ushort? value) { if (value == null) WriteNull(); else WriteValue(value.Value); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(char? value) { if (value == null) WriteNull(); else WriteValue(value.Value); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(byte? value) { if (value == null) WriteNull(); else WriteValue(value.Value); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(sbyte? value) { if (value == null) WriteNull(); else WriteValue(value.Value); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(decimal? value) { if (value == null) WriteNull(); else WriteValue(value.Value); } /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(DateTime? value) { if (value == null) WriteNull(); else WriteValue(value.Value); } #if !PocketPC && !NET20 /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(DateTimeOffset? value) { if (value == null) WriteNull(); else WriteValue(value.Value); } #endif /// /// Writes a value. /// /// The value to write. public virtual void WriteValue(byte[] value) { if (value == null) WriteNull(); else AutoComplete(JsonToken.Bytes); } /// /// Writes a value. /// An error will raised if the value cannot be written as a single JSON token. /// /// The value to write. public virtual void WriteValue(object value) { if (value == null) { WriteNull(); return; } else if (value is IConvertible) { IConvertible convertible = value as IConvertible; switch (convertible.GetTypeCode()) { case TypeCode.String: WriteValue(convertible.ToString(CultureInfo.InvariantCulture)); return; case TypeCode.Char: WriteValue(convertible.ToChar(CultureInfo.InvariantCulture)); return; case TypeCode.Boolean: WriteValue(convertible.ToBoolean(CultureInfo.InvariantCulture)); return; case TypeCode.SByte: WriteValue(convertible.ToSByte(CultureInfo.InvariantCulture)); return; case TypeCode.Int16: WriteValue(convertible.ToInt16(CultureInfo.InvariantCulture)); return; case TypeCode.UInt16: WriteValue(convertible.ToUInt16(CultureInfo.InvariantCulture)); return; case TypeCode.Int32: WriteValue(convertible.ToInt32(CultureInfo.InvariantCulture)); return; case TypeCode.Byte: WriteValue(convertible.ToByte(CultureInfo.InvariantCulture)); return; case TypeCode.UInt32: WriteValue(convertible.ToUInt32(CultureInfo.InvariantCulture)); return; case TypeCode.Int64: WriteValue(convertible.ToInt64(CultureInfo.InvariantCulture)); return; case TypeCode.UInt64: WriteValue(convertible.ToUInt64(CultureInfo.InvariantCulture)); return; case TypeCode.Single: WriteValue(convertible.ToSingle(CultureInfo.InvariantCulture)); return; case TypeCode.Double: WriteValue(convertible.ToDouble(CultureInfo.InvariantCulture)); return; case TypeCode.DateTime: WriteValue(convertible.ToDateTime(CultureInfo.InvariantCulture)); return; case TypeCode.Decimal: WriteValue(convertible.ToDecimal(CultureInfo.InvariantCulture)); return; case TypeCode.DBNull: WriteNull(); return; } } #if !PocketPC && !NET20 else if (value is DateTimeOffset) { WriteValue((DateTimeOffset)value); return; } #endif else if (value is byte[]) { WriteValue((byte[])value); return; } throw new ArgumentException("Unsupported type: {0}. Use the JsonSerializer class to get the object's JSON representation.".FormatWith(CultureInfo.InvariantCulture, value.GetType())); } #endregion /// /// Writes out a comment /*...*/ containing the specified text. /// /// Text to place inside the comment. public virtual void WriteComment(string text) { AutoComplete(JsonToken.Comment); } /// /// Writes out the given white space. /// /// The string of white space characters. public virtual void WriteWhitespace(string ws) { if (ws != null) { if (!StringUtils.IsWhiteSpace(ws)) throw new JsonWriterException("Only white space characters should be used."); } } void IDisposable.Dispose() { Dispose(true); } private void Dispose(bool disposing) { if (WriteState != WriteState.Closed) Close(); } } }