aboutsummaryrefslogblamecommitdiff
path: root/src/ProtocolBuffers/Serialization/JsonFormatWriter.cs
blob: b2057524cc463f8c39730adb0ac39746a74eb718 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13


                                  
                   








                                                                                                             
                                                                
      

                                                        
          


                                             
 






                                                     
 





                                                                                                   
 

                                               
 



                                                                                     
              
                                                        
                  

















                                                                                                             

                     












                                                           
                  

                                                           
                  
                              
              
          
                                                          
          







                                                                
 
                                                   
              
                                          
                                











































                                                                                                  
              
          




















                                                                                                                

                      
                                                                        
                       











                                                                                                              


















































































































































































                                                                                                                                                                          




















































                                                                                           
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Google.ProtocolBuffers.Descriptors;

namespace Google.ProtocolBuffers.Serialization
{
    /// <summary>
    /// JsonFormatWriter is a .NET 2.0 friendly json formatter for proto buffer messages.  For .NET 3.5
    /// you may also use the XmlFormatWriter with an XmlWriter created by the
    /// <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory">JsonReaderWriterFactory</see>.
    /// </summary>
    public abstract class JsonFormatWriter : AbstractTextWriter
    {
        #region buffering implementations
        private class JsonTextWriter : JsonFormatWriter
        {
            private readonly char[] _buffer;
            private TextWriter _output;
            int _bufferPos;

            public JsonTextWriter(TextWriter output)
            {
                _buffer = new char[4096];
                _bufferPos = 0;
                _output = output;
                _counter.Add(0);
            }

            /// <summary>
            /// Returns the output of TextWriter.ToString() where TextWriter is the ctor argument.
            /// </summary>
            public override string ToString()
            {
                Flush();

                if (_output != null)
                    return _output.ToString();

                return new String(_buffer, 0, _bufferPos);
            }

            protected override void WriteToOutput(char[] chars, int offset, int len)
            {
                if (_bufferPos + len >= _buffer.Length)
                {
                    if (_output == null)
                        _output = new StringWriter(new System.Text.StringBuilder(_buffer.Length * 2 + len));
                    Flush();
                }

                if (len < _buffer.Length)
                {
                    if (len <= 12)
                    {
                        int stop = offset + len;
                        for (int i = offset; i < stop; i++)
                            _buffer[_bufferPos++] = chars[i];
                    }
                    else
                    {
                        Buffer.BlockCopy(chars, offset << 1, _buffer, _bufferPos << 1, len << 1);
                        _bufferPos += len;
                    }
                }
                else
                    _output.Write(chars, offset, len);
            }

            protected override void WriteToOutput(char ch)
            {
                if (_bufferPos >= _buffer.Length)
                    Flush();
                _buffer[_bufferPos++] = ch;
            }

            public override void Flush()
            {
                if (_bufferPos > 0 && _output != null)
                {
                    _output.Write(_buffer, 0, _bufferPos);
                    _bufferPos = 0;
                }
                base.Flush();
            }
        }
        private class JsonStreamWriter : JsonFormatWriter
        {
#if SILVERLIGHT2 || COMPACT_FRAMEWORK_35
            static readonly Encoding Encoding = Encoding.UTF8;
#else
            static readonly Encoding Encoding = Encoding.ASCII;
#endif
            private readonly byte[] _buffer;
            private Stream _output;
            int _bufferPos;

            public JsonStreamWriter(Stream output)
            {
                _buffer = new byte[8192];
                _bufferPos = 0;
                _output = output;
                _counter.Add(0);
            }

            protected override void WriteToOutput(char[] chars, int offset, int len)
            {
                if (_bufferPos + len >= _buffer.Length)
                    Flush();

                if (len < _buffer.Length)
                {
                    if (len <= 12)
                    {
                        int stop = offset + len;
                        for (int i = offset; i < stop; i++)
                            _buffer[_bufferPos++] = (byte)chars[i];
                    }
                    else
                    {
                        _bufferPos += Encoding.GetBytes(chars, offset, len, _buffer, _bufferPos);
                    }
                }
                else
                {
                    byte[] temp = Encoding.GetBytes(chars, offset, len);
                    _output.Write(temp, 0, temp.Length);
                }
            }

            protected override void WriteToOutput(char ch)
            {
                if (_bufferPos >= _buffer.Length)
                    Flush();
                _buffer[_bufferPos++] = (byte)ch;
            }

            public override void Flush()
            {
                if (_bufferPos > 0 && _output != null)
                {
                    _output.Write(_buffer, 0, _bufferPos);
                    _bufferPos = 0;
                }
                base.Flush();
            }
        }
        #endregion

        private readonly List<int> _counter;
        private bool _isArray;
        /// <summary>
        /// Constructs a JsonFormatWriter, use the ToString() member to extract the final Json on completion.
        /// </summary>
        protected JsonFormatWriter()
        {
            _counter = new List<int>();
        }

        /// <summary>
        /// Constructs a JsonFormatWriter, use ToString() to extract the final output
        /// </summary>
        public static JsonFormatWriter CreateInstance() { return new JsonTextWriter(null); }
            
        /// <summary>
        /// Constructs a JsonFormatWriter to output to the given text writer
        /// </summary>
        public static JsonFormatWriter CreateInstance(TextWriter output) { return new JsonTextWriter(output); }

        /// <summary>
        /// Constructs a JsonFormatWriter to output to the given stream
        /// </summary>
        public static JsonFormatWriter CreateInstance(Stream output) { return new JsonStreamWriter(output); }

        /// <summary> Write to the output stream </summary>
        protected void WriteToOutput(string format, params object[] args)
        { WriteToOutput(String.Format(format, args)); }
        /// <summary> Write to the output stream </summary>
        protected void WriteToOutput(string text)
        { WriteToOutput(text.ToCharArray(), 0, text.Length); }
        /// <summary> Write to the output stream </summary>
        protected abstract void WriteToOutput(char ch);
        /// <summary> Write to the output stream </summary>
        protected abstract void WriteToOutput(char[] chars, int offset, int len);

        /// <summary> Sets the output formatting to use Environment.NewLine with 4-character indentions </summary>
        public JsonFormatWriter Formatted()
        {
            NewLine = Environment.NewLine;
            Indent = "    ";
            Whitespace = " ";
            return this;
        }

        /// <summary> Gets or sets the characters to use for the new-line, default = empty </summary>
        public string NewLine { get; set; }
        /// <summary> Gets or sets the text to use for indenting, default = empty </summary>
        public string Indent { get; set; }
        /// <summary> Gets or sets the whitespace to use to separate the text, default = empty </summary>
        public string Whitespace { get; set; }

        private void Seperator()
        {
            if (_counter.Count == 0)
                throw new InvalidOperationException("Missmatched open/close in Json writer.");

            int index = _counter.Count - 1;
            if (_counter[index] > 0)
                WriteToOutput(',');

            WriteLine(String.Empty);
            _counter[index] = _counter[index] + 1;
        }

        private void WriteLine(string content)
        {
            if (!String.IsNullOrEmpty(NewLine))
            {
                WriteToOutput(NewLine);
                for (int i = 1; i < _counter.Count; i++)
                    WriteToOutput(Indent);
            }
            else if(!String.IsNullOrEmpty(Whitespace))
                WriteToOutput(Whitespace);

            WriteToOutput(content);
        }

        private void WriteName(string field)
        {
            Seperator();
            if (!String.IsNullOrEmpty(field))
            {
                WriteToOutput('"');
                WriteToOutput(field);
                WriteToOutput('"');
                WriteToOutput(':');
                if (!String.IsNullOrEmpty(Whitespace))
                    WriteToOutput(Whitespace);
            }
        }

        private void EncodeText(string value)
        {
            char[] text = value.ToCharArray();
            int len = text.Length;
            int pos = 0;

            while (pos < len)
            {
                int next = pos;
                while (next < len && text[next] >= 32 && text[next] < 127 && text[next] != '\\' && text[next] != '/' && text[next] != '"')
                    next++;
                WriteToOutput(text, pos, next - pos);
                if (next < len)
                {
                    switch (text[next])
                    {
                        case '"': WriteToOutput(@"\"""); break;
                        case '\\': WriteToOutput(@"\\"); break;
                        //odd at best to escape '/', most Json implementations don't, but it is defined in the rfc-4627
                        case '/': WriteToOutput(@"\/"); break; 
                        case '\b': WriteToOutput(@"\b"); break;
                        case '\f': WriteToOutput(@"\f"); break;
                        case '\n': WriteToOutput(@"\n"); break;
                        case '\r': WriteToOutput(@"\r"); break;
                        case '\t': WriteToOutput(@"\t"); break;
                        default: WriteToOutput(@"\u{0:x4}", (int)text[next]); break;
                    }
                    next++;
                }
                pos = next;
            }
        }

        /// <summary>
        /// Writes a String value
        /// </summary>
        protected override void WriteAsText(string field, string textValue, object typedValue)
        {
            WriteName(field);
            if(typedValue is bool || typedValue is int || typedValue is uint || typedValue is long || typedValue is ulong || typedValue is double || typedValue is float)
                WriteToOutput(textValue);
            else
            {
                WriteToOutput('"');
                if (typedValue is string)
                    EncodeText(textValue);
                else
                    WriteToOutput(textValue);
                WriteToOutput('"');
            }
        }

        /// <summary>
        /// Writes a Double value
        /// </summary>
        protected override void Write(string field, double value)
        {
            if (double.IsNaN(value) || double.IsNegativeInfinity(value) || double.IsPositiveInfinity(value))
                throw new InvalidOperationException("This format does not support NaN, Infinity, or -Infinity");
            base.Write(field, value);
        }

        /// <summary>
        /// Writes a Single value
        /// </summary>
        protected override void Write(string field, float value)
        {
            if (float.IsNaN(value) || float.IsNegativeInfinity(value) || float.IsPositiveInfinity(value))
                throw new InvalidOperationException("This format does not support NaN, Infinity, or -Infinity");
            base.Write(field, value);
        }

        // Treat enum as string
        protected override void WriteEnum(string field, int number, string name)
        {
            Write(field, name);
        }

        /// <summary>
        /// Writes an array of field values
        /// </summary>
        protected override void WriteArray(FieldType type, string field, System.Collections.IEnumerable items)
        {
            System.Collections.IEnumerator enumerator = items.GetEnumerator();
            try { if (!enumerator.MoveNext()) return; }
            finally { if (enumerator is IDisposable) ((IDisposable)enumerator).Dispose(); }

            WriteName(field);
            WriteToOutput("[");
            _counter.Add(0);

            base.WriteArray(type, String.Empty, items);

            _counter.RemoveAt(_counter.Count - 1);
            WriteLine("]");
        }

        /// <summary>
        /// Writes a message
        /// </summary>
        protected override void WriteMessageOrGroup(string field, IMessageLite message)
        {
            WriteName(field);
            WriteMessage(message);
        }

        /// <summary>
        /// Writes the message to the the formatted stream.
        /// </summary>
        public override void WriteMessage(IMessageLite message)
        {
            if (_isArray) Seperator();
            WriteToOutput("{");
            _counter.Add(0);
            message.WriteTo(this);
            _counter.RemoveAt(_counter.Count - 1);
            WriteLine("}");
            Flush();
        }

        /// <summary>
        /// Used in streaming arrays of objects to the writer
        /// </summary>
        /// <example>
        /// <code>
        /// using(writer.StartArray())
        ///     foreach(IMessageLite m in messages)
        ///         writer.WriteMessage(m);
        /// </code>
        /// </example>
        public sealed class JsonArray : IDisposable 
        {
            JsonFormatWriter _writer;
            internal JsonArray(JsonFormatWriter writer)
            {
                _writer = writer;
                _writer.WriteToOutput("[");
                _writer._counter.Add(0);
            }

            /// <summary>
            /// Causes the end of the array character to be written.
            /// </summary>
            void EndArray() 
            {
                if (_writer != null)
                {
                    _writer._counter.RemoveAt(_writer._counter.Count - 1);
                    _writer.WriteLine("]");
                    _writer.Flush();
                }
                _writer = null; 
            }
            void IDisposable.Dispose() { EndArray(); }
        }

        /// <summary>
        /// Used to write an array of messages as the output rather than a single message.
        /// </summary>
        /// <example>
        /// <code>
        /// using(writer.StartArray())
        ///     foreach(IMessageLite m in messages)
        ///         writer.WriteMessage(m);
        /// </code>
        /// </example>
        public JsonArray StartArray()
        {
            if (_isArray) Seperator();
            _isArray = true;
            return new JsonArray(this);
        }
    }
}