JsonWriter.cs 12 KB

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