PetaJsonEmit.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  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. // We'll create setters for each property/field
  239. var setters = new Dictionary<string, Action<IJsonReader, object>>();
  240. // These types we'll call <type>.Parse(reader.String) on
  241. var numericTypes = new Type[] {
  242. typeof(int), typeof(uint), typeof(long), typeof(ulong),
  243. typeof(short), typeof(ushort), typeof(decimal),
  244. typeof(byte), typeof(sbyte),
  245. typeof(double), typeof(float)
  246. };
  247. // Process all members
  248. foreach (var m in ri.Members)
  249. {
  250. // Ignore write only properties
  251. var pi = m.Member as PropertyInfo;
  252. if (pi != null && pi.GetSetMethod() == null)
  253. {
  254. continue;
  255. }
  256. // Create a dynamic method that can do the work
  257. var method = new DynamicMethod("dynamic_parser", null, new Type[] { typeof(IJsonReader), typeof(object) }, true);
  258. var il = method.GetILGenerator();
  259. if (m.KeepInstance)
  260. {
  261. throw new NotImplementedException("Emit KeepInstance not implemented");
  262. }
  263. // Load the target
  264. il.Emit(OpCodes.Ldarg_1);
  265. il.Emit(OpCodes.Castclass, type);
  266. Action<string> callHelper = helperName =>
  267. {
  268. // check we have a string
  269. il.Emit(OpCodes.Ldarg_0);
  270. il.Emit(OpCodes.Call, typeof(Emit).GetMethod(helperName, new Type[] { typeof(IJsonReader) }));
  271. il.Emit(OpCodes.Ldarg_0);
  272. il.Emit(OpCodes.Callvirt, typeof(IJsonReader).GetMethod("NextToken", new Type[] { }));
  273. };
  274. if (m.MemberType == typeof(string))
  275. {
  276. callHelper("GetLiteralString");
  277. }
  278. else if (m.MemberType == typeof(bool))
  279. {
  280. callHelper("GetLiteralBool");
  281. }
  282. else if (m.MemberType == typeof(char))
  283. {
  284. callHelper("GetLiteralChar");
  285. }
  286. else if (numericTypes.Contains(m.MemberType))
  287. {
  288. il.Emit(OpCodes.Ldarg_0);
  289. il.Emit(OpCodes.Call, typeof(Emit).GetMethod("GetLiteralNumber", new Type[] { typeof(IJsonReader) }));
  290. // Convert to a string
  291. il.Emit(OpCodes.Call, typeof(CultureInfo).GetProperty("InvariantCulture").GetGetMethod());
  292. il.Emit(OpCodes.Call, m.MemberType.GetMethod("Parse", new Type[] { typeof(string), typeof(IFormatProvider) }));
  293. il.Emit(OpCodes.Ldarg_0);
  294. il.Emit(OpCodes.Callvirt, typeof(IJsonReader).GetMethod("NextToken", new Type[] { }));
  295. }
  296. else
  297. {
  298. il.Emit(OpCodes.Ldarg_0);
  299. il.Emit(OpCodes.Ldtoken, m.MemberType);
  300. il.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) }));
  301. il.Emit(OpCodes.Callvirt, typeof(IJsonReader).GetMethod("Parse", new Type[] { typeof(Type) }));
  302. il.Emit(m.MemberType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, m.MemberType);
  303. }
  304. if (pi != null)
  305. {
  306. il.Emit(OpCodes.Callvirt, pi.GetSetMethod());
  307. }
  308. var fi = m.Member as FieldInfo;
  309. if (fi != null)
  310. {
  311. il.Emit(OpCodes.Stfld, fi);
  312. }
  313. il.Emit(OpCodes.Ret);
  314. // Store the handler in map
  315. setters.Add(m.JsonKey, (Action<IJsonReader, object>)method.CreateDelegate(typeof(Action<IJsonReader, object>)));
  316. }
  317. // Now create the parseInto delegate
  318. bool hasLoading = typeof(IJsonLoading).IsAssignableFrom(type);
  319. bool hasLoaded = typeof(IJsonLoaded).IsAssignableFrom(type);
  320. Action<IJsonReader, object> parseInto = (reader, obj) =>
  321. {
  322. if (hasLoading)
  323. {
  324. ((IJsonLoading)obj).OnJsonLoading(reader);
  325. }
  326. var lf = obj as IJsonLoadField;
  327. reader.ReadDictionary(key =>
  328. {
  329. if (lf != null)
  330. {
  331. if (lf.OnJsonField(reader, key))
  332. return;
  333. }
  334. Action<IJsonReader, object> setter;
  335. if (setters.TryGetValue(key, out setter))
  336. {
  337. setter(reader, obj);
  338. }
  339. });
  340. if (hasLoaded)
  341. {
  342. ((IJsonLoaded)obj).OnJsonLoaded(reader);
  343. }
  344. };
  345. // While we're at it, we might as well create a direct type converter too
  346. RegisterParser(type, parseInto);
  347. // Done
  348. return parseInto;
  349. }
  350. static void RegisterParser(Type type, Action<IJsonReader, object> parseInto)
  351. {
  352. // Create a dynamic method that can do the work
  353. var method = new DynamicMethod("dynamic_factory", typeof(object), new Type[] { typeof(IJsonReader), typeof(Action<IJsonReader, object>)}, true);
  354. var il = method.GetILGenerator();
  355. // Create the new object
  356. var locObj = il.DeclareLocal(type);
  357. il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null));
  358. il.Emit(OpCodes.Dup);
  359. il.Emit(OpCodes.Stloc, locObj);
  360. il.Emit(OpCodes.Ldarg_1); // parseinto delegate
  361. il.Emit(OpCodes.Ldarg_0); // IJsonReader
  362. il.Emit(OpCodes.Ldloc, locObj); // new object instance
  363. il.Emit(OpCodes.Callvirt, typeof(Action<IJsonReader, object>).GetMethod("Invoke"));
  364. il.Emit(OpCodes.Ret);
  365. var factory = (Func<IJsonReader, Action<IJsonReader,object>, object>)method.CreateDelegate(typeof(Func<IJsonReader, Action<IJsonReader, object>, object>));
  366. Json.RegisterParser(type, (reader, type2) =>
  367. {
  368. return factory(reader, parseInto);
  369. });
  370. }
  371. public static bool GetLiteralBool(IJsonReader r)
  372. {
  373. switch (r.GetLiteralKind())
  374. {
  375. case LiteralKind.True:
  376. return true;
  377. case LiteralKind.False:
  378. return false;
  379. default:
  380. throw new InvalidDataException("expected a boolean value");
  381. }
  382. }
  383. public static char GetLiteralChar(IJsonReader r)
  384. {
  385. if (r.GetLiteralKind() != LiteralKind.String)
  386. throw new InvalidDataException("expected a single character string literal");
  387. var str = r.GetLiteralString();
  388. if (str==null || str.Length!=1)
  389. throw new InvalidDataException("expected a single character string literal");
  390. return str[0];
  391. }
  392. public static string GetLiteralString(IJsonReader r)
  393. {
  394. if (r.GetLiteralKind() != LiteralKind.String)
  395. throw new InvalidDataException("expected a string literal");
  396. return r.GetLiteralString();
  397. }
  398. public static string GetLiteralNumber(IJsonReader r)
  399. {
  400. switch (r.GetLiteralKind())
  401. {
  402. case LiteralKind.SignedInteger:
  403. case LiteralKind.UnsignedInteger:
  404. case LiteralKind.FloatingPoint:
  405. return r.GetLiteralString();
  406. }
  407. throw new InvalidDataException("expected a numeric literal");
  408. }
  409. }
  410. }
  411. }