PetaJsonEmit.cs 23 KB

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