view Implab/JSON/JSONWriter.cs @ 141:0fa293bb1351 v2

fixed JSON writer
author cin
date Tue, 24 Feb 2015 00:54:22 +0300
parents 2573b562e328
children 2100965eb97f
line wrap: on
line source

using System;
using System.Collections.Generic;
using System.IO;

namespace Implab.JSON {
    public class JSONWriter {
        struct Context {
            public bool needComma;
            public JSONElementContext element;
        }
        Stack<Context> m_contextStack = new Stack<Context>();
        Context m_context;

        TextWriter m_writer;
        readonly bool m_indent = true;
        readonly int m_indentSize = 4;

        static readonly char [] _escapeBKS,
            _escapeFWD,
            _escapeCR,
            _escapeNL,
            _escapeTAB,
            _escapeBSLASH,
            _escapeQ;

        static JSONWriter() {
            _escapeBKS = "\\b".ToCharArray();
            _escapeFWD = "\\f".ToCharArray();
            _escapeCR = "\\r".ToCharArray();
            _escapeNL = "\\n".ToCharArray();
            _escapeTAB = "\\t".ToCharArray();
            _escapeBSLASH = "\\\\".ToCharArray();
            _escapeQ = "\\\"".ToCharArray();
        }

        public JSONWriter(TextWriter writer) {
            Safe.ArgumentNotNull(writer, "writer");

            m_writer = writer;
        }

        public JSONWriter(TextWriter writer, bool indent) {
            Safe.ArgumentNotNull(writer, "writer");

            m_writer = writer;
            m_indent = indent;
        }

        void WriteIndent() {
            if (m_indent) {
                var indent = new char[m_contextStack.Count * m_indentSize + 1];
                indent[0] = '\n';
                for (int i = 1; i < indent.Length; i++)
                    indent[i] = ' ';
                m_writer.Write(new String(indent));
            } else {
                m_writer.Write(' ');
            }
        }

        void WriteMemberName(string name) {
            Safe.ArgumentNotEmpty(name, "name");
            if (m_context.element != JSONElementContext.Object)
                OperationNotApplicable("WriteMember");
            if (m_context.needComma)
                m_writer.Write(",");

            WriteIndent();
            m_context.needComma = true;
            Write(name);
            m_writer.Write(" : ");
        }

        public void WriteValue(string name, string value) {
            WriteMemberName(name);
            Write(value);            
        }

        public void WriteValue(string name, bool value) {
            WriteMemberName(name);
            Write(value);
        }

        public void WriteValue(string name, double value) {
            WriteMemberName(name);
            Write(value);
        }

        public void WriteValue(string value) {
            if (m_context.element == JSONElementContext.Array) {

                if (m_context.needComma)
                    m_writer.Write(",");
                WriteIndent();
                m_context.needComma = true;

                Write(value);
            } else if (m_context.element == JSONElementContext.None) {
                Write(value);
                m_context.element = JSONElementContext.Closed;
            } else {
                OperationNotApplicable("WriteValue");
            }
        }

        public void WriteValue(bool value) {
            if (m_context.element == JSONElementContext.Array) {

                if (m_context.needComma)
                    m_writer.Write(",");
                WriteIndent();
                m_context.needComma = true;

                Write(value);
            } else if (m_context.element == JSONElementContext.None) {
                Write(value);
                m_context.element = JSONElementContext.Closed;
            } else {
                OperationNotApplicable("WriteValue");
            }
        }

        public void WriteValue(double value) {
            if (m_context.element == JSONElementContext.Array) {

                if (m_context.needComma)
                    m_writer.Write(",");
                WriteIndent();
                m_context.needComma = true;

                Write(value);
            } else if (m_context.element == JSONElementContext.None) {
                Write(value);
                m_context.element = JSONElementContext.Closed;
            } else {
                OperationNotApplicable("WriteValue");
            }
        }
        
        public void BeginObject() {
            if (m_context.element != JSONElementContext.None && m_context.element != JSONElementContext.Array)
                OperationNotApplicable("BeginObject");
            if (m_context.needComma)
                m_writer.Write(",");

            WriteIndent();

            m_context.needComma = true;

            m_contextStack.Push(m_context);

            m_context = new Context { element = JSONElementContext.Object, needComma = false };
            m_writer.Write("{");
        }

        public void BeginObject(string name) {
            WriteMemberName(name);

            m_contextStack.Push(m_context);

            m_context = new Context { element = JSONElementContext.Object, needComma = false };
            m_writer.Write("{");
        }

        public void EndObject() {
            if (m_context.element != JSONElementContext.Object)
                OperationNotApplicable("EndObject");

            m_context = m_contextStack.Pop();
            if (m_contextStack.Count == 0)
                m_context.element = JSONElementContext.Closed;
            WriteIndent();
            m_writer.Write("}");
        }

        public void BeginArray() {
            if (m_context.element != JSONElementContext.None && m_context.element != JSONElementContext.Array)
                throw new InvalidOperationException();
            if (m_context.needComma) {
                m_writer.Write(",");

            }
            m_context.needComma = true;

            WriteIndent();
            m_contextStack.Push(m_context);
            m_context = new Context { element = JSONElementContext.Array, needComma = false };
            m_writer.Write("[");
        }

        public void BeginArray(string name) {
            WriteMemberName(name);

            m_contextStack.Push(m_context);

            m_context = new Context { element = JSONElementContext.Array, needComma = false };
            m_writer.Write("[");
        }

        public void EndArray() {
            if (m_context.element != JSONElementContext.Array)
                OperationNotApplicable("EndArray");

            m_context = m_contextStack.Pop();
            if (m_contextStack.Count == 0)
                m_context.element = JSONElementContext.Closed;
            WriteIndent();
            m_writer.Write("]");
        }

        void Write(bool value) {
            m_writer.Write(value ? "true" : "false");
        }
 

        void Write(string value) {
            if (value == null) {
                m_writer.Write("null");
                return;
            }

            var chars = value.ToCharArray();
            m_writer.Write('"');
            
            // Analysis disable once ForCanBeConvertedToForeach
            for (int i = 0; i < chars.Length; i++) {
                var ch = chars[i];

                switch (ch) {
                    case '\b':
                        m_writer.Write(_escapeBKS);
                        break;
                    case '\f':
                        m_writer.Write(_escapeFWD);
                        break;
                    case '\r':
                        m_writer.Write(_escapeCR);
                        break;
                    case '\n':
                        m_writer.Write(_escapeNL);
                        break;
                    case '\t':
                        m_writer.Write(_escapeTAB);
                        break;
                    case '\\':
                        m_writer.Write(_escapeBSLASH);
                        break;
                    case '"':
                        m_writer.Write(_escapeQ);
                        break;
                    default:
                        if (ch < 0x20) {
                            m_writer.Write("\\u00{0:x2}",(int)ch);
                        } else {
                            m_writer.Write(ch);
                        }
                        break;
                }
            }

            m_writer.Write('"');
        }

        void Write(double value) {
            m_writer.Write(value);
        }

        void OperationNotApplicable(string opName) {
            throw new InvalidOperationException(String.Format("The operation '{0}' isn't applicable in the context of '{1}'", opName, m_context.element ));
        }
        
    }
}