JsonWriter.cs 12 KB

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