PetaJsonEmit.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Reflection.Emit;
  6. using System.Reflection;
  7. using System.Globalization;
  8. using System.IO;
  9. namespace PetaJson
  10. {
  11. public static class JsonEmit
  12. {
  13. public static void Init()
  14. {
  15. Json.SetTypeFormatterResolver(Internal.Emit.MakeFormatter);
  16. Json.SetTypeIntoParserResolver(Internal.Emit.MakeParser);
  17. }
  18. }
  19. namespace Internal
  20. {
  21. static class Emit
  22. {
  23. public static Action<IJsonWriter, object> MakeFormatter(Type type)
  24. {
  25. // Get the reflection info for this type
  26. var ri = ReflectionInfo.GetReflectionInfo(type);
  27. if (ri == null)
  28. return null;
  29. // Create a dynamic method that can do the work
  30. var method = new DynamicMethod("dynamic_formatter", null, new Type[] { typeof(IJsonWriter), typeof(object) }, true);
  31. var il = method.GetILGenerator();
  32. // Cast/unbox the target object and store in local variable
  33. var locTypedObj = il.DeclareLocal(type);
  34. il.Emit(OpCodes.Ldarg_1);
  35. il.Emit(type.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, type);
  36. il.Emit(OpCodes.Stloc, locTypedObj);
  37. // Get Invariant CultureInfo
  38. var locInvariant = il.DeclareLocal(typeof(IFormatProvider));
  39. il.Emit(OpCodes.Call, typeof(CultureInfo).GetProperty("InvariantCulture").GetGetMethod());
  40. il.Emit(OpCodes.Stloc, locInvariant);
  41. // These are the types we'll call .ToString(Culture.InvariantCulture) on
  42. var toStringTypes = new Type[] {
  43. typeof(int), typeof(uint), typeof(long), typeof(ulong),
  44. typeof(short), typeof(ushort), typeof(decimal),
  45. typeof(byte), typeof(sbyte)
  46. };
  47. // Theses types we also generate for
  48. var otherSupportedTypes = new Type[] {
  49. typeof(double), typeof(float), typeof(string), typeof(char), typeof(bool)
  50. };
  51. // Call IJsonWriting if implemented
  52. if (typeof(IJsonWriting).IsAssignableFrom(type))
  53. {
  54. il.Emit(OpCodes.Ldloc, locTypedObj);
  55. if (type.IsValueType)
  56. il.Emit(OpCodes.Box, type);
  57. il.Emit(OpCodes.Castclass, typeof(IJsonWriting));
  58. il.Emit(OpCodes.Ldarg_0);
  59. il.Emit(OpCodes.Callvirt, typeof(IJsonWriting).GetMethod("OnJsonWriting", new Type[] { typeof(IJsonWriter) }));
  60. }
  61. // Process all members
  62. foreach (var m in ri.Members)
  63. {
  64. // Ignore write only properties
  65. var pi = m.Member as PropertyInfo;
  66. if (pi!=null && pi.GetGetMethod() == null)
  67. {
  68. continue;
  69. }
  70. // Write the Json key
  71. il.Emit(OpCodes.Ldarg_0);
  72. il.Emit(OpCodes.Ldstr, m.JsonKey);
  73. il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteKeyNoEscaping", new Type[] { typeof(string) }));
  74. // Load the writer
  75. il.Emit(OpCodes.Ldarg_0);
  76. // Get the member type
  77. var memberType = m.MemberType;
  78. // Load the target object
  79. if (type.IsValueType)
  80. {
  81. il.Emit(OpCodes.Ldloca, locTypedObj);
  82. }
  83. else
  84. {
  85. il.Emit(OpCodes.Ldloc, locTypedObj);
  86. }
  87. // Work out if we need to the value or it's address on the stack
  88. bool NeedValueAddress = (memberType.IsValueType && (toStringTypes.Contains(memberType) || otherSupportedTypes.Contains(memberType)));
  89. if (Nullable.GetUnderlyingType(memberType) != null)
  90. {
  91. NeedValueAddress = true;
  92. }
  93. // Property?
  94. if (pi != null)
  95. {
  96. // Call property's get method
  97. if (type.IsValueType)
  98. il.Emit(OpCodes.Call, pi.GetGetMethod());
  99. else
  100. il.Emit(OpCodes.Callvirt, pi.GetGetMethod());
  101. // If we need the address then store in a local and take it's address
  102. if (NeedValueAddress)
  103. {
  104. var locTemp = il.DeclareLocal(memberType);
  105. il.Emit(OpCodes.Stloc, locTemp);
  106. il.Emit(OpCodes.Ldloca, locTemp);
  107. }
  108. }
  109. // Field?
  110. var fi = m.Member as FieldInfo;
  111. if (fi != null)
  112. {
  113. if (NeedValueAddress)
  114. {
  115. il.Emit(OpCodes.Ldflda, fi);
  116. }
  117. else
  118. {
  119. il.Emit(OpCodes.Ldfld, fi);
  120. }
  121. }
  122. Label? lblFinished = null;
  123. // Is it a nullable type?
  124. var typeUnderlying = Nullable.GetUnderlyingType(memberType);
  125. if (typeUnderlying != null)
  126. {
  127. // Duplicate the address so we can call get_HasValue() and then get_Value()
  128. il.Emit(OpCodes.Dup);
  129. // Define some labels
  130. var lblHasValue = il.DefineLabel();
  131. lblFinished = il.DefineLabel();
  132. // Call has_Value
  133. il.Emit(OpCodes.Call, memberType.GetProperty("HasValue").GetGetMethod());
  134. il.Emit(OpCodes.Brtrue, lblHasValue);
  135. // No value, write "null:
  136. il.Emit(OpCodes.Pop);
  137. il.Emit(OpCodes.Ldstr, "null");
  138. il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteRaw", new Type[] { typeof(string) }));
  139. il.Emit(OpCodes.Br_S, lblFinished.Value);
  140. // Get it's value
  141. il.MarkLabel(lblHasValue);
  142. il.Emit(OpCodes.Call, memberType.GetProperty("Value").GetGetMethod());
  143. // Switch to the underlying type from here on
  144. memberType = typeUnderlying;
  145. NeedValueAddress = (memberType.IsValueType && (toStringTypes.Contains(memberType) || otherSupportedTypes.Contains(memberType)));
  146. // Work out again if we need the address of the value
  147. if (NeedValueAddress)
  148. {
  149. var locTemp = il.DeclareLocal(memberType);
  150. il.Emit(OpCodes.Stloc, locTemp);
  151. il.Emit(OpCodes.Ldloca, locTemp);
  152. }
  153. }
  154. // ToString()
  155. if (toStringTypes.Contains(memberType))
  156. {
  157. // Convert to string
  158. il.Emit(OpCodes.Ldloc, locInvariant);
  159. il.Emit(OpCodes.Call, memberType.GetMethod("ToString", new Type[] { typeof(IFormatProvider) }));
  160. il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteRaw", new Type[] { typeof(string) }));
  161. }
  162. // ToString("R")
  163. else if (memberType == typeof(float) || memberType == typeof(double))
  164. {
  165. il.Emit(OpCodes.Ldstr, "R");
  166. il.Emit(OpCodes.Ldloc, locInvariant);
  167. il.Emit(OpCodes.Call, memberType.GetMethod("ToString", new Type[] { typeof(string), typeof(IFormatProvider) }));
  168. il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteRaw", new Type[] { typeof(string) }));
  169. }
  170. // String?
  171. else if (memberType == typeof(string))
  172. {
  173. il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteStringLiteral", new Type[] { typeof(string) }));
  174. }
  175. // Char?
  176. else if (memberType == typeof(char))
  177. {
  178. il.Emit(OpCodes.Call, memberType.GetMethod("ToString", new Type[] { }));
  179. il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteStringLiteral", new Type[] { typeof(string) }));
  180. }
  181. // Bool?
  182. else if (memberType == typeof(bool))
  183. {
  184. var lblTrue = il.DefineLabel();
  185. var lblCont = il.DefineLabel();
  186. il.Emit(OpCodes.Brtrue_S, lblTrue);
  187. il.Emit(OpCodes.Ldstr, "false");
  188. il.Emit(OpCodes.Br_S, lblCont);
  189. il.MarkLabel(lblTrue);
  190. il.Emit(OpCodes.Ldstr, "true");
  191. il.MarkLabel(lblCont);
  192. il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteRaw", new Type[] { typeof(string) }));
  193. }
  194. // NB: We don't support DateTime as it's format can be changed
  195. else
  196. {
  197. // Unsupported type, pass through
  198. if (memberType.IsValueType)
  199. {
  200. il.Emit(OpCodes.Box, memberType);
  201. }
  202. il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteValue", new Type[] { typeof(object) }));
  203. }
  204. if (lblFinished.HasValue)
  205. il.MarkLabel(lblFinished.Value);
  206. }
  207. // Call IJsonWritten
  208. if (typeof(IJsonWritten).IsAssignableFrom(type))
  209. {
  210. il.Emit(OpCodes.Ldloc, locTypedObj);
  211. if (type.IsValueType)
  212. il.Emit(OpCodes.Box, type);
  213. il.Emit(OpCodes.Castclass, typeof(IJsonWritten));
  214. il.Emit(OpCodes.Ldarg_0);
  215. il.Emit(OpCodes.Callvirt, typeof(IJsonWritten).GetMethod("OnJsonWritten", new Type[] { typeof(IJsonWriter) }));
  216. }
  217. // Done!
  218. il.Emit(OpCodes.Ret);
  219. // Create delegate to our IL code
  220. var impl = (Action<IJsonWriter, object>)method.CreateDelegate(typeof(Action<IJsonWriter, object>));
  221. // Wrap it in a call to WriteDictionary
  222. return (w, obj) =>
  223. {
  224. w.WriteDictionary(() =>
  225. {
  226. impl(w, obj);
  227. });
  228. };
  229. }
  230. public static Action<IJsonReader, object> MakeParser(Type type)
  231. {
  232. if (type.IsValueType)
  233. throw new NotImplementedException("Value types aren't supported through reflection");
  234. // Get the reflection info for this type
  235. var ri = ReflectionInfo.GetReflectionInfo(type);
  236. if (ri == null)
  237. return null;
  238. var setters = new Dictionary<string, Action<IJsonReader, object>>();
  239. // These types we'll call <type>.Parse(reader.String) on
  240. var numericTypes = new Type[] {
  241. typeof(int), typeof(uint), typeof(long), typeof(ulong),
  242. typeof(short), typeof(ushort), typeof(decimal),
  243. typeof(byte), typeof(sbyte),
  244. typeof(double), typeof(float)
  245. };
  246. // Process all members
  247. foreach (var m in ri.Members)
  248. {
  249. // Ignore write only properties
  250. var pi = m.Member as PropertyInfo;
  251. if (pi != null && pi.GetSetMethod() == null)
  252. {
  253. continue;
  254. }
  255. // Create a dynamic method that can do the work
  256. var method = new DynamicMethod("dynamic_parser", null, new Type[] { typeof(IJsonReader), typeof(object) }, true);
  257. var il = method.GetILGenerator();
  258. if (m.KeepInstance)
  259. {
  260. throw new NotImplementedException("Emit KeepInstance not implemented");
  261. }
  262. // Load the target
  263. il.Emit(OpCodes.Ldarg_1);
  264. il.Emit(OpCodes.Castclass, type);
  265. Action<string> callHelper = helperName =>
  266. {
  267. // check we have a string
  268. il.Emit(OpCodes.Ldarg_0);
  269. il.Emit(OpCodes.Call, typeof(Emit).GetMethod(helperName, new Type[] { typeof(IJsonReader) }));
  270. il.Emit(OpCodes.Ldarg_0);
  271. il.Emit(OpCodes.Callvirt, typeof(IJsonReader).GetMethod("NextToken", new Type[] { }));
  272. };
  273. if (m.MemberType == typeof(string))
  274. {
  275. callHelper("GetLiteralString");
  276. }
  277. else if (m.MemberType == typeof(bool))
  278. {
  279. callHelper("GetLiteralBool");
  280. }
  281. else if (m.MemberType == typeof(char))
  282. {
  283. callHelper("GetLiteralChar");
  284. }
  285. else if (numericTypes.Contains(m.MemberType))
  286. {
  287. il.Emit(OpCodes.Ldarg_0);
  288. il.Emit(OpCodes.Call, typeof(Emit).GetMethod("GetLiteralNumber", new Type[] { typeof(IJsonReader) }));
  289. // Convert to a string
  290. il.Emit(OpCodes.Call, typeof(CultureInfo).GetProperty("InvariantCulture").GetGetMethod());
  291. il.Emit(OpCodes.Call, m.MemberType.GetMethod("Parse", new Type[] { typeof(string), typeof(IFormatProvider) }));
  292. il.Emit(OpCodes.Ldarg_0);
  293. il.Emit(OpCodes.Callvirt, typeof(IJsonReader).GetMethod("NextToken", new Type[] { }));
  294. }
  295. else
  296. {
  297. il.Emit(OpCodes.Ldarg_0);
  298. il.Emit(OpCodes.Ldtoken, m.MemberType);
  299. il.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) }));
  300. il.Emit(OpCodes.Callvirt, typeof(IJsonReader).GetMethod("Parse", new Type[] { typeof(Type) }));
  301. il.Emit(m.MemberType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, m.MemberType);
  302. }
  303. if (pi != null)
  304. {
  305. il.Emit(OpCodes.Callvirt, pi.GetSetMethod());
  306. }
  307. var fi = m.Member as FieldInfo;
  308. if (fi != null)
  309. {
  310. il.Emit(OpCodes.Stfld, fi);
  311. }
  312. il.Emit(OpCodes.Ret);
  313. // Store the handler in map
  314. setters.Add(m.JsonKey, (Action<IJsonReader, object>)method.CreateDelegate(typeof(Action<IJsonReader, object>)));
  315. }
  316. return (reader, obj) =>
  317. {
  318. reader.ReadDictionary(key =>
  319. {
  320. var lf = obj as IJsonLoadField;
  321. if (lf != null)
  322. {
  323. if (lf.OnJsonField(reader, key))
  324. return;
  325. }
  326. Action<IJsonReader, object> setter;
  327. if (setters.TryGetValue(key, out setter))
  328. {
  329. setter(reader, obj);
  330. }
  331. });
  332. };
  333. }
  334. public static bool GetLiteralBool(IJsonReader r)
  335. {
  336. switch (r.GetLiteralKind())
  337. {
  338. case LiteralKind.True:
  339. return true;
  340. case LiteralKind.False:
  341. return false;
  342. default:
  343. throw new InvalidDataException("expected a boolean value");
  344. }
  345. }
  346. public static char GetLiteralChar(IJsonReader r)
  347. {
  348. if (r.GetLiteralKind() != LiteralKind.String)
  349. throw new InvalidDataException("expected a single character string literal");
  350. var str = r.GetLiteralString();
  351. if (str==null || str.Length!=1)
  352. throw new InvalidDataException("expected a single character string literal");
  353. return str[0];
  354. }
  355. public static string GetLiteralString(IJsonReader r)
  356. {
  357. if (r.GetLiteralKind() != LiteralKind.String)
  358. throw new InvalidDataException("expected a string literal");
  359. return r.GetLiteralString();
  360. }
  361. public static string GetLiteralNumber(IJsonReader r)
  362. {
  363. switch (r.GetLiteralKind())
  364. {
  365. case LiteralKind.SignedInteger:
  366. case LiteralKind.UnsignedInteger:
  367. case LiteralKind.FloatingPoint:
  368. return r.GetLiteralString();
  369. }
  370. throw new InvalidDataException("expected a numeric literal");
  371. }
  372. }
  373. }
  374. }