JsonSerializer.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using Jint.Native.Global;
  4. using Jint.Native.Object;
  5. using Jint.Runtime;
  6. using Jint.Runtime.Descriptors;
  7. namespace Jint.Native.Json
  8. {
  9. public class JsonSerializer
  10. {
  11. private readonly Engine _engine;
  12. public JsonSerializer(Engine engine)
  13. {
  14. _engine = engine;
  15. }
  16. private Stack<object> _stack;
  17. private string _indent, _gap;
  18. private List<JsValue> _propertyList;
  19. private JsValue _replacerFunction = Undefined.Instance;
  20. public JsValue Serialize(JsValue value, JsValue replacer, JsValue space)
  21. {
  22. _stack = new Stack<object>();
  23. // for JSON.stringify(), any function passed as the first argument will return undefined
  24. // if the replacer is not defined. The function is not called either.
  25. if (value.IsCallable && ReferenceEquals(replacer, Undefined.Instance))
  26. {
  27. return Undefined.Instance;
  28. }
  29. if (replacer.IsObject())
  30. {
  31. if (replacer is ICallable)
  32. {
  33. _replacerFunction = replacer;
  34. }
  35. else
  36. {
  37. var replacerObj = replacer.AsObject();
  38. if (replacerObj.Class == ObjectClass.Array)
  39. {
  40. _propertyList = new List<JsValue>();
  41. }
  42. foreach (var property in replacerObj.GetOwnProperties().Select(x => x.Value))
  43. {
  44. JsValue v = _engine.GetValue(property, false);
  45. string item = null;
  46. if (v.IsString())
  47. {
  48. item = v.ToString();
  49. }
  50. else if (v.IsNumber())
  51. {
  52. item = TypeConverter.ToString(v);
  53. }
  54. else if (v.IsObject())
  55. {
  56. var propertyObj = v.AsObject();
  57. if (propertyObj.Class == ObjectClass.String || propertyObj.Class == ObjectClass.Number)
  58. {
  59. item = TypeConverter.ToString(v);
  60. }
  61. }
  62. if (item != null && !_propertyList.Contains(item))
  63. {
  64. _propertyList.Add(item);
  65. }
  66. }
  67. }
  68. }
  69. if (space.IsObject())
  70. {
  71. var spaceObj = space.AsObject();
  72. if (spaceObj.Class == ObjectClass.Number)
  73. {
  74. space = TypeConverter.ToNumber(spaceObj);
  75. }
  76. else if (spaceObj.Class == ObjectClass.String)
  77. {
  78. space = TypeConverter.ToJsString(spaceObj);
  79. }
  80. }
  81. // defining the gap
  82. if (space.IsNumber())
  83. {
  84. var number = ((JsNumber) space)._value;
  85. if (number > 0)
  86. {
  87. _gap = new string(' ', (int) System.Math.Min(10, number));
  88. }
  89. else
  90. {
  91. _gap = string.Empty;
  92. }
  93. }
  94. else if (space.IsString())
  95. {
  96. var stringSpace = space.ToString();
  97. _gap = stringSpace.Length <= 10 ? stringSpace : stringSpace.Substring(0, 10);
  98. }
  99. else
  100. {
  101. _gap = string.Empty;
  102. }
  103. var wrapper = _engine.Realm.Intrinsics.Object.Construct(Arguments.Empty);
  104. wrapper.DefineOwnProperty(JsString.Empty, new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable));
  105. return Str(JsString.Empty, wrapper);
  106. }
  107. private JsValue Str(JsValue key, JsValue holder)
  108. {
  109. var value = holder.Get(key, holder);
  110. if (value.IsObject())
  111. {
  112. var toJson = value.AsObject().Get("toJSON", value);
  113. if (toJson.IsObject())
  114. {
  115. if (toJson.AsObject() is ICallable callableToJson)
  116. {
  117. value = callableToJson.Call(value, Arguments.From(key));
  118. }
  119. }
  120. }
  121. if (!ReferenceEquals(_replacerFunction, Undefined.Instance))
  122. {
  123. var replacerFunctionCallable = (ICallable)_replacerFunction.AsObject();
  124. value = replacerFunctionCallable.Call(holder, Arguments.From(key, value));
  125. }
  126. if (value.IsObject())
  127. {
  128. var valueObj = value.AsObject();
  129. switch (valueObj.Class)
  130. {
  131. case ObjectClass.Number:
  132. value = TypeConverter.ToNumber(value);
  133. break;
  134. case ObjectClass.String:
  135. value = TypeConverter.ToString(value);
  136. break;
  137. case ObjectClass.Boolean:
  138. value = TypeConverter.ToPrimitive(value);
  139. break;
  140. case ObjectClass.Array:
  141. value = SerializeArray(value);
  142. return value;
  143. case ObjectClass.Object:
  144. value = SerializeObject(value.AsObject());
  145. return value;
  146. }
  147. }
  148. if (ReferenceEquals(value, Null.Instance))
  149. {
  150. return "null";
  151. }
  152. if (value.IsBoolean())
  153. {
  154. return ((JsBoolean) value)._value ? "true" : "false";
  155. }
  156. if (value.IsString())
  157. {
  158. return Quote(value.ToString());
  159. }
  160. if (value.IsNumber())
  161. {
  162. var isFinite = GlobalObject.IsFinite(Undefined.Instance, Arguments.From(value));
  163. if (((JsBoolean) isFinite)._value)
  164. {
  165. return TypeConverter.ToJsString(value);
  166. }
  167. return "null";
  168. }
  169. var isCallable = value.IsObject() && value.AsObject() is ICallable;
  170. if (value.IsObject() && isCallable == false)
  171. {
  172. return value.AsObject().Class == ObjectClass.Array
  173. ? SerializeArray(value)
  174. : SerializeObject(value.AsObject());
  175. }
  176. return JsValue.Undefined;
  177. }
  178. private static string Quote(string value)
  179. {
  180. var sb = new System.Text.StringBuilder("\"");
  181. foreach (char c in value)
  182. {
  183. switch (c)
  184. {
  185. case '\"':
  186. sb.Append("\\\"");
  187. break;
  188. case '\\':
  189. sb.Append("\\\\");
  190. break;
  191. case '\b':
  192. sb.Append("\\b");
  193. break;
  194. case '\f':
  195. sb.Append("\\f");
  196. break;
  197. case '\n':
  198. sb.Append("\\n");
  199. break;
  200. case '\r':
  201. sb.Append("\\r");
  202. break;
  203. case '\t':
  204. sb.Append("\\t");
  205. break;
  206. default:
  207. if (c < 0x20)
  208. {
  209. sb.Append("\\u");
  210. sb.Append(((int) c).ToString("x4"));
  211. }
  212. else
  213. sb.Append(c);
  214. break;
  215. }
  216. }
  217. sb.Append("\"");
  218. return sb.ToString();
  219. }
  220. private string SerializeArray(JsValue value)
  221. {
  222. EnsureNonCyclicity(value);
  223. _stack.Push(value);
  224. var stepback = _indent;
  225. _indent = _indent + _gap;
  226. var partial = new List<string>();
  227. var len = TypeConverter.ToUint32(value.Get(CommonProperties.Length, value));
  228. for (int i = 0; i < len; i++)
  229. {
  230. var strP = Str(TypeConverter.ToString(i), value);
  231. if (strP.IsUndefined())
  232. strP = "null";
  233. partial.Add(strP.ToString());
  234. }
  235. if (partial.Count == 0)
  236. {
  237. _stack.Pop();
  238. return "[]";
  239. }
  240. string final;
  241. if (_gap == "")
  242. {
  243. var separator = ",";
  244. var properties = System.String.Join(separator, partial.ToArray());
  245. final = "[" + properties + "]";
  246. }
  247. else
  248. {
  249. var separator = ",\n" + _indent;
  250. var properties = System.String.Join(separator, partial.ToArray());
  251. final = "[\n" + _indent + properties + "\n" + stepback + "]";
  252. }
  253. _stack.Pop();
  254. _indent = stepback;
  255. return final;
  256. }
  257. private void EnsureNonCyclicity(object value)
  258. {
  259. if (value == null)
  260. {
  261. ExceptionHelper.ThrowArgumentNullException(nameof(value));
  262. }
  263. if (_stack.Contains(value))
  264. {
  265. ExceptionHelper.ThrowTypeError(_engine.Realm, "Cyclic reference detected.");
  266. }
  267. }
  268. private string SerializeObject(ObjectInstance value)
  269. {
  270. string final;
  271. EnsureNonCyclicity(value);
  272. _stack.Push(value);
  273. var stepback = _indent;
  274. _indent += _gap;
  275. var k = _propertyList ?? value.GetOwnProperties()
  276. .Where(x => x.Value.Enumerable)
  277. .Select(x => x.Key)
  278. .ToList();
  279. var partial = new List<string>();
  280. foreach (var p in k)
  281. {
  282. var strP = Str(p, value);
  283. if (!strP.IsUndefined())
  284. {
  285. var member = Quote(p.ToString()) + ":";
  286. if (_gap != "")
  287. {
  288. member += " ";
  289. }
  290. member += strP.AsString(); // TODO:This could be undefined
  291. partial.Add(member);
  292. }
  293. }
  294. if (partial.Count == 0)
  295. {
  296. final = "{}";
  297. }
  298. else
  299. {
  300. if (_gap == "")
  301. {
  302. var separator = ",";
  303. var properties = System.String.Join(separator, partial.ToArray());
  304. final = "{" + properties + "}";
  305. }
  306. else
  307. {
  308. var separator = ",\n" + _indent;
  309. var properties = System.String.Join(separator, partial.ToArray());
  310. final = "{\n" + _indent + properties + "\n" + stepback + "}";
  311. }
  312. }
  313. _stack.Pop();
  314. _indent = stepback;
  315. return final;
  316. }
  317. }
  318. }