JsonWriter.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. // JsonKit v0.5 - A simple but flexible Json library in a single .cs file.
  2. //
  3. // Copyright (C) 2014 Topten Software (contact@toptensoftware.com) All rights reserved.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product
  6. // except in compliance with the License. You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software distributed under the
  11. // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
  12. // either express or implied. See the License for the specific language governing permissions
  13. // and limitations under the License.
  14. // Define JsonKit_NO_DYNAMIC to disable Expando support
  15. // Define JsonKit_NO_EMIT to disable Reflection.Emit
  16. // Define JsonKit_NO_DATACONTRACT to disable support for [DataContract]/[DataMember]
  17. using System;
  18. using System.Collections.Generic;
  19. using System.Linq;
  20. using System.IO;
  21. using System.Globalization;
  22. namespace Topten.JsonKit
  23. {
  24. class JsonWriter : IJsonWriter
  25. {
  26. static JsonWriter()
  27. {
  28. _formatterResolver = ResolveFormatter;
  29. // Register standard formatters
  30. _formatters.Add(typeof(string), (w, o) => w.WriteStringLiteral((string)o));
  31. _formatters.Add(typeof(char), (w, o) => w.WriteStringLiteral(((char)o).ToString()));
  32. _formatters.Add(typeof(bool), (w, o) => w.WriteRaw(((bool)o) ? "true" : "false"));
  33. Action<IJsonWriter, object> convertWriter = (w, o) => w.WriteRaw((string)Convert.ChangeType(o, typeof(string), System.Globalization.CultureInfo.InvariantCulture));
  34. _formatters.Add(typeof(int), convertWriter);
  35. _formatters.Add(typeof(uint), convertWriter);
  36. _formatters.Add(typeof(long), convertWriter);
  37. _formatters.Add(typeof(ulong), convertWriter);
  38. _formatters.Add(typeof(short), convertWriter);
  39. _formatters.Add(typeof(ushort), convertWriter);
  40. _formatters.Add(typeof(decimal), convertWriter);
  41. _formatters.Add(typeof(byte), convertWriter);
  42. _formatters.Add(typeof(sbyte), convertWriter);
  43. _formatters.Add(typeof(DateTime), (w, o) => convertWriter(w, Utils.ToUnixMilliseconds((DateTime)o)));
  44. _formatters.Add(typeof(float), (w, o) => w.WriteRaw(((float)o).ToString("R", System.Globalization.CultureInfo.InvariantCulture)));
  45. _formatters.Add(typeof(double), (w, o) => w.WriteRaw(((double)o).ToString("R", System.Globalization.CultureInfo.InvariantCulture)));
  46. _formatters.Add(typeof(byte[]), (w, o) =>
  47. {
  48. w.WriteRaw("\"");
  49. w.WriteRaw(Convert.ToBase64String((byte[])o));
  50. w.WriteRaw("\"");
  51. });
  52. }
  53. public static Func<Type, Action<IJsonWriter, object>> _formatterResolver;
  54. public static Dictionary<Type, Action<IJsonWriter, object>> _formatters = new Dictionary<Type, Action<IJsonWriter, object>>();
  55. static Action<IJsonWriter, object> ResolveFormatter(Type type)
  56. {
  57. // Try `void FormatJson(IJsonWriter)`
  58. var formatJson = ReflectionInfo.FindFormatJson(type);
  59. if (formatJson != null)
  60. {
  61. if (formatJson.ReturnType==typeof(void))
  62. return (w, obj) => formatJson.Invoke(obj, new Object[] { w });
  63. if (formatJson.ReturnType == typeof(string))
  64. return (w, obj) => w.WriteStringLiteral((string)formatJson.Invoke(obj, new Object[] { }));
  65. }
  66. var ri = ReflectionInfo.GetReflectionInfo(type);
  67. if (ri != null)
  68. return ri.Write;
  69. else
  70. return null;
  71. }
  72. public JsonWriter(TextWriter w, JsonOptions options)
  73. {
  74. _writer = w;
  75. _atStartOfLine = true;
  76. _needElementSeparator = false;
  77. _options = options;
  78. }
  79. private TextWriter _writer;
  80. private int IndentLevel;
  81. private bool _atStartOfLine;
  82. private bool _needElementSeparator = false;
  83. private JsonOptions _options;
  84. private char _currentBlockKind = '\0';
  85. // Move to the next line
  86. public void NextLine()
  87. {
  88. if (_atStartOfLine)
  89. return;
  90. if ((_options & JsonOptions.WriteWhitespace)!=0)
  91. {
  92. WriteRaw("\n");
  93. WriteRaw(new string('\t', IndentLevel));
  94. }
  95. _atStartOfLine = true;
  96. }
  97. // Start the next element, writing separators and white space
  98. void NextElement()
  99. {
  100. if (_needElementSeparator)
  101. {
  102. WriteRaw(",");
  103. NextLine();
  104. }
  105. else
  106. {
  107. NextLine();
  108. IndentLevel++;
  109. WriteRaw(_currentBlockKind.ToString());
  110. NextLine();
  111. }
  112. _needElementSeparator = true;
  113. }
  114. // Write next array element
  115. public void WriteElement()
  116. {
  117. if (_currentBlockKind != '[')
  118. throw new InvalidOperationException("Attempt to write array element when not in array block");
  119. NextElement();
  120. }
  121. // Write next dictionary key
  122. public void WriteKey(string key)
  123. {
  124. if (_currentBlockKind != '{')
  125. throw new InvalidOperationException("Attempt to write dictionary element when not in dictionary block");
  126. NextElement();
  127. WriteStringLiteral(key);
  128. WriteRaw(((_options & JsonOptions.WriteWhitespace) != 0) ? ": " : ":");
  129. _atStartOfLine = false;
  130. }
  131. // Write an already escaped dictionary key
  132. public void WriteKeyNoEscaping(string key)
  133. {
  134. if (_currentBlockKind != '{')
  135. throw new InvalidOperationException("Attempt to write dictionary element when not in dictionary block");
  136. NextElement();
  137. WriteRaw("\"");
  138. WriteRaw(key);
  139. WriteRaw("\"");
  140. WriteRaw(((_options & JsonOptions.WriteWhitespace) != 0) ? ": " : ":");
  141. _atStartOfLine = false;
  142. }
  143. // Write anything
  144. public void WriteRaw(string str)
  145. {
  146. _writer.Write(str);
  147. _atStartOfLine = false;
  148. }
  149. static int IndexOfEscapeableChar(string str, int pos)
  150. {
  151. int length = str.Length;
  152. while (pos < length)
  153. {
  154. var ch = str[pos];
  155. if (ch == '\\' || ch == '/' || ch == '\"' || (ch>=0 && ch <= 0x1f) || (ch >= 0x7f && ch <=0x9f) || ch==0x2028 || ch== 0x2029)
  156. return pos;
  157. pos++;
  158. }
  159. return -1;
  160. }
  161. public void WriteStringLiteral(string str)
  162. {
  163. _atStartOfLine = false;
  164. if (str == null)
  165. {
  166. _writer.Write("null");
  167. return;
  168. }
  169. _writer.Write("\"");
  170. int pos = 0;
  171. int escapePos;
  172. while ((escapePos = IndexOfEscapeableChar(str, pos)) >= 0)
  173. {
  174. if (escapePos > pos)
  175. _writer.Write(str.Substring(pos, escapePos - pos));
  176. switch (str[escapePos])
  177. {
  178. case '\"': _writer.Write("\\\""); break;
  179. case '\\': _writer.Write("\\\\"); break;
  180. case '/': _writer.Write("\\/"); break;
  181. case '\b': _writer.Write("\\b"); break;
  182. case '\f': _writer.Write("\\f"); break;
  183. case '\n': _writer.Write("\\n"); break;
  184. case '\r': _writer.Write("\\r"); break;
  185. case '\t': _writer.Write("\\t"); break;
  186. default:
  187. _writer.Write(string.Format("\\u{0:x4}", (int)str[escapePos]));
  188. break;
  189. }
  190. pos = escapePos + 1;
  191. }
  192. if (str.Length > pos)
  193. _writer.Write(str.Substring(pos));
  194. _writer.Write("\"");
  195. }
  196. // Write an array or dictionary block
  197. private void WriteBlock(string open, string close, Action callback)
  198. {
  199. var prevBlockKind = _currentBlockKind;
  200. _currentBlockKind = open[0];
  201. var didNeedElementSeparator = _needElementSeparator;
  202. _needElementSeparator = false;
  203. callback();
  204. if (_needElementSeparator)
  205. {
  206. IndentLevel--;
  207. NextLine();
  208. }
  209. else
  210. {
  211. WriteRaw(open);
  212. }
  213. WriteRaw(close);
  214. _needElementSeparator = didNeedElementSeparator;
  215. _currentBlockKind = prevBlockKind;
  216. }
  217. // Write an array
  218. public void WriteArray(Action callback)
  219. {
  220. WriteBlock("[", "]", callback);
  221. }
  222. // Write a dictionary
  223. public void WriteDictionary(Action callback)
  224. {
  225. WriteBlock("{", "}", callback);
  226. }
  227. // Write any value
  228. public void WriteValue(object value)
  229. {
  230. // Special handling for null
  231. if (value == null)
  232. {
  233. _writer.Write("null");
  234. return;
  235. }
  236. var type = value.GetType();
  237. // Handle nullable types
  238. var typeUnderlying = Nullable.GetUnderlyingType(type);
  239. if (typeUnderlying != null)
  240. type = typeUnderlying;
  241. // Look up type writer
  242. Action<IJsonWriter, object> typeWriter;
  243. if (_formatters.TryGetValue(type, out typeWriter))
  244. {
  245. // Write it
  246. typeWriter(this, value);
  247. return;
  248. }
  249. // Enumerated type?
  250. if (type.IsEnum)
  251. {
  252. if (type.GetCustomAttributes(typeof(FlagsAttribute), false).Any())
  253. WriteRaw(Convert.ToUInt32(value).ToString(CultureInfo.InvariantCulture));
  254. else
  255. WriteStringLiteral(value.ToString());
  256. return;
  257. }
  258. // Dictionary?
  259. var d = value as System.Collections.IDictionary;
  260. if (d != null)
  261. {
  262. WriteDictionary(() =>
  263. {
  264. foreach (var key in d.Keys)
  265. {
  266. WriteKey(key.ToString());
  267. WriteValue(d[key]);
  268. }
  269. });
  270. return;
  271. }
  272. // Dictionary?
  273. var dso = value as IDictionary<string,object>;
  274. if (dso != null)
  275. {
  276. WriteDictionary(() =>
  277. {
  278. foreach (var key in dso.Keys)
  279. {
  280. WriteKey(key.ToString());
  281. WriteValue(dso[key]);
  282. }
  283. });
  284. return;
  285. }
  286. // Array?
  287. var e = value as System.Collections.IEnumerable;
  288. if (e != null)
  289. {
  290. WriteArray(() =>
  291. {
  292. foreach (var i in e)
  293. {
  294. WriteElement();
  295. WriteValue(i);
  296. }
  297. });
  298. return;
  299. }
  300. // Resolve a formatter
  301. var formatter = _formatterResolver(type);
  302. if (formatter != null)
  303. {
  304. _formatters[type] = formatter;
  305. formatter(this, value);
  306. return;
  307. }
  308. // Give up
  309. throw new InvalidDataException(string.Format("Don't know how to write '{0}' to json", value.GetType()));
  310. }
  311. }
  312. }