// JsonKit v0.5 - A simple but flexible Json library in a single .cs file. // // Copyright (C) 2014 Topten Software (contact@toptensoftware.com) All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product // except in compliance with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software distributed under the // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, // either express or implied. See the License for the specific language governing permissions // and limitations under the License. using System; using System.Collections.Generic; using System.Linq; using System.IO; using System.Globalization; namespace Topten.JsonKit { class JsonWriter : IJsonWriter { static JsonWriter() { _formatterResolver = ResolveFormatter; // Register standard formatters _formatters.Set(typeof(string), (w, o) => w.WriteStringLiteral((string)o)); _formatters.Set(typeof(char), (w, o) => w.WriteStringLiteral(((char)o).ToString())); _formatters.Set(typeof(bool), (w, o) => w.WriteRaw(((bool)o) ? "true" : "false")); Action convertWriter = (w, o) => w.WriteRaw((string)Convert.ChangeType(o, typeof(string), System.Globalization.CultureInfo.InvariantCulture)); _formatters.Set(typeof(int), convertWriter); _formatters.Set(typeof(uint), convertWriter); _formatters.Set(typeof(long), convertWriter); _formatters.Set(typeof(ulong), convertWriter); _formatters.Set(typeof(short), convertWriter); _formatters.Set(typeof(ushort), convertWriter); _formatters.Set(typeof(decimal), convertWriter); _formatters.Set(typeof(byte), convertWriter); _formatters.Set(typeof(sbyte), convertWriter); _formatters.Set(typeof(DateTime), (w, o) => convertWriter(w, Utils.ToUnixMilliseconds((DateTime)o))); _formatters.Set(typeof(float), (w, o) => w.WriteRaw(((float)o).ToString("R", System.Globalization.CultureInfo.InvariantCulture))); _formatters.Set(typeof(double), (w, o) => w.WriteRaw(((double)o).ToString("R", System.Globalization.CultureInfo.InvariantCulture))); _formatters.Set(typeof(byte[]), (w, o) => { w.WriteRaw("\""); w.WriteRaw(Convert.ToBase64String((byte[])o)); w.WriteRaw("\""); }); } public static Func> _formatterResolver; public static ThreadSafeCache> _formatters = new ThreadSafeCache>(); public static ThreadSafeCache> _keyFormatters = new ThreadSafeCache>(); static Action ResolveFormatter(Type type) { // Try `void FormatJson(IJsonWriter)` var formatJson = ReflectionInfo.FindFormatJson(type); if (formatJson != null) { if (formatJson.ReturnType==typeof(void)) return (w, obj) => formatJson.Invoke(obj, new Object[] { w }); if (formatJson.ReturnType == typeof(string)) return (w, obj) => w.WriteStringLiteral((string)formatJson.Invoke(obj, new Object[] { })); } var ri = ReflectionInfo.GetReflectionInfo(type); if (ri != null) return ri.Write; else return null; } public JsonWriter(TextWriter w, JsonOptions options) { _writer = w; _atStartOfLine = true; _needElementSeparator = false; _options = options; } private TextWriter _writer; private int IndentLevel; private bool _atStartOfLine; private bool _needElementSeparator = false; private JsonOptions _options; private char _currentBlockKind = '\0'; // Move to the next line public void NextLine() { if (_atStartOfLine) return; if ((_options & JsonOptions.WriteWhitespace)!=0) { WriteRaw("\n"); WriteRaw(new string('\t', IndentLevel)); } _atStartOfLine = true; } // Start the next element, writing separators and white space void NextElement() { if (_needElementSeparator) { WriteRaw(","); NextLine(); } else { NextLine(); IndentLevel++; WriteRaw(_currentBlockKind.ToString()); NextLine(); } _needElementSeparator = true; } // Write next array element public void WriteElement() { if (_currentBlockKind != '[') throw new InvalidOperationException("Attempt to write array element when not in array block"); NextElement(); } // Writes a dictionary key using custom formatter if available void WriteDictionaryKey(object value) { string str; Func formatter; if (_keyFormatters.TryGetValue(value.GetType(), out formatter)) { str = formatter(value); } else { str = value.ToString(); } WriteKey(str); } // Write next dictionary key public void WriteKey(string key) { if (_currentBlockKind != '{') throw new InvalidOperationException("Attempt to write dictionary element when not in dictionary block"); NextElement(); WriteStringLiteral(key); WriteRaw(((_options & JsonOptions.WriteWhitespace) != 0) ? ": " : ":"); _atStartOfLine = false; } // Write an already escaped dictionary key public void WriteKeyNoEscaping(string key) { if (_currentBlockKind != '{') throw new InvalidOperationException("Attempt to write dictionary element when not in dictionary block"); NextElement(); WriteRaw("\""); WriteRaw(key); WriteRaw("\""); WriteRaw(((_options & JsonOptions.WriteWhitespace) != 0) ? ": " : ":"); _atStartOfLine = false; } // Write anything public void WriteRaw(string str) { _writer.Write(str); _atStartOfLine = false; } static int IndexOfEscapeableChar(string str, int pos) { int length = str.Length; while (pos < length) { var ch = str[pos]; if (ch == '\\' || ch == '/' || ch == '\"' || (ch>=0 && ch <= 0x1f) || (ch >= 0x7f && ch <=0x9f) || ch==0x2028 || ch== 0x2029) return pos; pos++; } return -1; } public void WriteStringLiteral(string str) { _atStartOfLine = false; if (str == null) { _writer.Write("null"); return; } _writer.Write("\""); int pos = 0; int escapePos; while ((escapePos = IndexOfEscapeableChar(str, pos)) >= 0) { if (escapePos > pos) _writer.Write(str.Substring(pos, escapePos - pos)); switch (str[escapePos]) { case '\"': _writer.Write("\\\""); break; case '\\': _writer.Write("\\\\"); break; case '/': _writer.Write("\\/"); break; case '\b': _writer.Write("\\b"); break; case '\f': _writer.Write("\\f"); break; case '\n': _writer.Write("\\n"); break; case '\r': _writer.Write("\\r"); break; case '\t': _writer.Write("\\t"); break; default: _writer.Write(string.Format("\\u{0:x4}", (int)str[escapePos])); break; } pos = escapePos + 1; } if (str.Length > pos) _writer.Write(str.Substring(pos)); _writer.Write("\""); } // Write an array or dictionary block private void WriteBlock(string open, string close, Action callback) { var prevBlockKind = _currentBlockKind; _currentBlockKind = open[0]; var didNeedElementSeparator = _needElementSeparator; _needElementSeparator = false; callback(); if (_needElementSeparator) { IndentLevel--; NextLine(); } else { WriteRaw(open); } WriteRaw(close); _needElementSeparator = didNeedElementSeparator; _currentBlockKind = prevBlockKind; } // Write an array public void WriteArray(Action callback) { WriteBlock("[", "]", callback); } // Write a dictionary public void WriteDictionary(Action callback) { WriteBlock("{", "}", callback); } // Write any value public void WriteValue(object value) { // Special handling for null if (value == null) { _writer.Write("null"); return; } var type = value.GetType(); // Handle nullable types var typeUnderlying = Nullable.GetUnderlyingType(type); if (typeUnderlying != null) type = typeUnderlying; // Look up type writer Action typeWriter; if (_formatters.TryGetValue(type, out typeWriter)) { // Write it typeWriter(this, value); return; } // Enumerated type? if (type.IsEnum) { if (type.GetCustomAttributes(typeof(FlagsAttribute), false).Any()) WriteRaw(Convert.ToUInt32(value).ToString(CultureInfo.InvariantCulture)); else WriteStringLiteral(value.ToString()); return; } // Dictionary? var d = value as System.Collections.IDictionary; if (d != null) { WriteDictionary(() => { foreach (var key in d.Keys) { WriteDictionaryKey(key); WriteValue(d[key]); } }); return; } // Dictionary? var dso = value as IDictionary; if (dso != null) { WriteDictionary(() => { foreach (var key in dso.Keys) { WriteDictionaryKey(key); WriteValue(dso[key]); } }); return; } // Array? var e = value as System.Collections.IEnumerable; if (e != null) { WriteArray(() => { foreach (var i in e) { WriteElement(); WriteValue(i); } }); return; } // Resolve a formatter var formatter = _formatterResolver(type); if (formatter != null) { _formatters.Set(type, formatter); formatter(this, value); return; } // Give up throw new InvalidDataException(string.Format("Don't know how to write '{0}' to json", value.GetType())); } } }