jsonCompress.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. /**
  2. * json压缩工具
  3. */
  4. export default {
  5. /**
  6. * 压缩js对象成json字符串,并控制json字节大小,多余部分裁剪
  7. */
  8. compressObject(obj = {}, maxSize = 1024 * 10.24) {
  9. try {
  10. if (obj === undefined || obj === null) return obj;
  11. if (typeof obj == "string") {
  12. return this.truncateStrBySize(obj, maxSize);
  13. }
  14. if (typeof obj == "number") {
  15. return obj;
  16. }
  17. let t = new Date().getTime()
  18. const type = typeof obj;
  19. if (type === 'symbol') {
  20. obj = "Symbol->" + obj.toString();
  21. } else if (type === 'bigint') {
  22. obj = "bigint->" + obj.toString()
  23. } else if (typeof Error != "undefined" && obj instanceof Error) {
  24. obj = `Error->${obj.name}\n${obj.message}\n${obj.stack}`
  25. } else if (typeof Date != "undefined" && obj instanceof Date) {
  26. obj = "Date->" + obj.toISOString()
  27. } else if (typeof obj == "function") {
  28. obj = "Function->" + obj.toString()
  29. } else if (typeof RegExp != "undefined" && obj instanceof RegExp) {
  30. obj = "RegExp->" + obj.toString();
  31. } else if (typeof Map != "undefined" && obj instanceof Map) {
  32. obj = `Map->(${obj.size}) { ${Array.from(obj.entries()).map(([key, value]) => `${convertToString(key)} => ${convertToString(value)}`).join(', ')} }`;
  33. } else if (typeof Set != "undefined" && obj instanceof Set) {
  34. obj = `Set->(${obj.size}) { ${Array.from(obj.values()).map(value => convertToString(value)).join(', ')} }`;
  35. } else if (typeof Blob != "undefined" && obj instanceof Blob) {
  36. obj = `Blob->{ size: ${obj.size}, type: ${obj.type} }`;
  37. } else if (typeof File != "undefined" && obj instanceof File) {
  38. obj = `File->{ name: "${obj.name}", size: ${obj.size}, type: ${obj.type}, lastModified: ${new Date(obj.lastModified).toISOString()} }`;
  39. } else if (typeof URL != "undefined" && obj instanceof URL) {
  40. obj = `URL->{ href: "${obj.href}", protocol: "${obj.protocol}", host: "${obj.host}", pathname: "${obj.pathname}", search: "${obj.search}", hash: "${obj.hash}" }`;
  41. } else if (typeof FormData != "undefined" && obj instanceof FormData) {
  42. const entries = [];
  43. obj.forEach((key, item) => {
  44. entries.push(key)
  45. })
  46. obj = `FormData->{ ${entries.join(', ')} }`;
  47. } else if (typeof Location != "undefined" && obj instanceof Location) {
  48. obj = `Location->{ href: "${obj.href}", protocol: "${obj.protocol}", host: "${obj.host}", pathname: "${obj.pathname}", search: "${obj.search}", hash: "${obj.hash}" }`;
  49. } else if (typeof Document != "undefined" && obj instanceof Document) {
  50. obj = `Document->{ title: "${obj.title}", URL: "${obj.URL}" }`;
  51. } else if (typeof Window !== "undefined" && obj instanceof Window) {
  52. obj = `Window->{ location: ${this.compressObject(obj.location)}, document: ${this.compressObject(obj.document)} }`;
  53. } else if (typeof Element != "undefined" && obj instanceof Element) {
  54. obj = `Element->{ tagName: "${obj.tagName}", id: "${obj.id}", class: "${obj.className}" }`;
  55. } else if (typeof HTMLCanvasElement != "undefined" && obj instanceof HTMLCanvasElement) {
  56. obj = `Canvas->{ width: ${obj.width}, height: ${obj.height} }`;
  57. } else if (typeof HTMLAudioElement != "undefined" && obj instanceof HTMLAudioElement) {
  58. obj = `Audio->{ src: "${obj.src}", duration: ${obj.duration} }`;
  59. } else if (typeof HTMLVideoElement != "undefined" && obj instanceof HTMLVideoElement) {
  60. obj = `Video->{ src: "${obj.src}", width: ${obj.videoWidth}, height: ${obj.videoHeight}, duration: ${obj.duration} }`;
  61. } else if (typeof Storage != "undefined" && obj instanceof Storage) {
  62. obj = `Storage->{ length: ${obj.length} }`;
  63. }
  64. else if ((typeof Worker != "undefined" && typeof ServiceWorker != "undefined" && typeof SharedWorker != "undefined") && (obj instanceof Worker || obj instanceof ServiceWorker || obj instanceof SharedWorker)) {
  65. obj = `Worker->${obj.constructor.name} { scriptURL: "${obj.scriptURL}" }`;
  66. } else if (typeof WebSocket != "undefined" && obj instanceof WebSocket) {
  67. obj = `WebSocket->{ url: "${obj.url}", readyState: ${obj.readyState} }`;
  68. }
  69. else if (typeof XMLHttpRequest != "undefined" && obj instanceof XMLHttpRequest) {
  70. obj = `XMLHttpRequest->{ readyState: ${obj.readyState}, status: ${obj.status} }`;
  71. }
  72. else if (typeof EventSource != "undefined" && obj instanceof EventSource) {
  73. obj = `EventSource->{ url: "${obj.url}", readyState: ${obj.readyState} }`;
  74. }
  75. else if (typeof MediaStream != "undefined" && obj instanceof MediaStream) {
  76. obj = `MediaStream->{ id: "${obj.id}", active: ${obj.active} }`;
  77. }
  78. else if (typeof RTCPeerConnection != "undefined" && obj instanceof RTCPeerConnection) {
  79. obj = `RTCPeerConnection->{ connectionState: "${obj.connectionState}" }`;
  80. }
  81. else if (typeof AudioContext != "undefined" && obj instanceof AudioContext) {
  82. obj = `AudioContext->{ state: "${obj.state}" }`
  83. }
  84. else if (typeof Element != "undefined" && obj instanceof Element) {
  85. obj = `Element->{ tagName: "${obj.tagName}", id: "${obj.id}", class: "${obj.className}" }`
  86. }
  87. else if (typeof HTMLCanvasElement != "undefined" && obj instanceof HTMLCanvasElement) {
  88. obj = `Canvas->{ width: ${obj.width}, height: ${obj.height} }`
  89. }
  90. else if (typeof HTMLAudioElement != "undefined" && obj instanceof HTMLAudioElement) {
  91. obj = `Audio->{ src: "${obj.src}", duration: ${obj.duration} }`;
  92. }
  93. else if (typeof HTMLVideoElement != "undefined" && obj instanceof HTMLVideoElement) {
  94. obj = `Video->{ src: "${obj.src}", width: ${obj.videoWidth}, height: ${obj.videoHeight}, duration: ${obj.duration} }`;
  95. }
  96. else if (typeof Geolocation != "undefined" && obj instanceof Geolocation) {
  97. obj = `Geolocation->{ }`;
  98. }
  99. else if (typeof Performance != "undefined" && obj instanceof Performance) {
  100. obj = `Performance->{ now: ${obj.now()} }`
  101. }
  102. else if (typeof Event != "undefined" && obj instanceof Event) {
  103. obj = `Event->{ type: "${obj.type}", target: "${obj.target}" }`;
  104. }
  105. if (typeof obj == "string") {
  106. return this.truncateStrBySize(obj, maxSize);
  107. }
  108. if (typeof obj != "object") {
  109. return obj;
  110. }
  111. if (maxSize < 2) return {};
  112. let addEndOut = false;
  113. if (maxSize > 50) {
  114. let objSize = this.calculateStringByteSize(obj);
  115. if (objSize > maxSize) {
  116. maxSize = maxSize - 50;
  117. addEndOut = true;
  118. }
  119. }
  120. let sizeCount = 2;
  121. let str = this.safeJsonStringify(obj, (key, value) => {
  122. let keySize = this.calculateStringByteSize(key)
  123. if (typeof value == "object") {
  124. if (sizeCount + keySize + 6 > maxSize) {
  125. return;
  126. }
  127. sizeCount = sizeCount + keySize + 6;
  128. return value;
  129. }
  130. let valueSize = this.calculateStringByteSize(value)
  131. let rowSize = keySize + valueSize + 6;
  132. if (rowSize + sizeCount > maxSize) return;
  133. sizeCount = sizeCount + rowSize;
  134. return value;
  135. })
  136. let outPut = JSON.parse(str)
  137. if (addEndOut) {
  138. if (Array.isArray(outPut)) {
  139. outPut.push('(已截断其余部分)')
  140. } else if (typeof outPut == "object") {
  141. outPut["*注意"] = "(已截断其余部分)";
  142. }
  143. }
  144. // console.log("compressObject use time: " + (new Date().getTime() - t));
  145. return outPut;
  146. } catch (error) {
  147. console.log("compressObject error", error);
  148. return "";
  149. }
  150. },
  151. /**
  152. * 压缩数组不超过特定大小
  153. * @param {any[]} [arr=[]] 需要处理的数组
  154. * @param {string} [delType='start'] 数组超出后删除的开始位置
  155. * @param {number} [maxSize=1024 * 972] 数组最大字节数
  156. */
  157. compressArray(arr = [], delType = "start", maxSize = 1024 * 972) {
  158. let t = new Date().getTime()
  159. try {
  160. if (!arr || arr.length == 0 || !arr[0]) return [];
  161. let i = 0;
  162. while (true) {
  163. i = i + 1;
  164. if (i > 999999) return arr;
  165. if (!arr || arr.length == 0) {
  166. return [];
  167. }
  168. if (this.calculateStringByteSize(arr) <= maxSize) {
  169. // consoleLog("compressArray t=>" + (new Date().getTime() - t) + " i=>" + i)
  170. return arr;
  171. }
  172. if (delType == "start") {
  173. arr.splice(0, 1);
  174. } else {
  175. arr.splice(arr.length - 1, 1);
  176. }
  177. }
  178. } catch (error) {
  179. console.log("compressArray error", error);
  180. return [];
  181. }
  182. },
  183. /**
  184. * 计算对象或字符串占用的字节大小,传入对象将自动转json
  185. */
  186. calculateStringByteSize(str) {
  187. try {
  188. let type = typeof str;
  189. if (
  190. type == "bigint"
  191. || type == "number"
  192. || type == "boolean"
  193. ) {
  194. return str.toString().length;
  195. } else if (type == "function") {
  196. str = str.toString().length
  197. } else if (str === null || str === undefined) {
  198. return 0;
  199. } else {
  200. try {
  201. str = this.safeJsonStringify(str);
  202. if (str && str.hasOwnProperty) {
  203. return str.length;
  204. } else {
  205. return 1024 * 20;
  206. }
  207. } catch (error) {
  208. console.log("calculateStringByteSize error", error);
  209. return 1024 * 20;
  210. }
  211. }
  212. let size = 0;
  213. for (let i = 0; i < str.length; i++) {
  214. const charCode = str.charCodeAt(i);
  215. if (charCode < 0x0080) {
  216. size += 1;
  217. } else if (charCode < 0x0800) {
  218. size += 2;
  219. } else if (charCode >= 0xD800 && charCode <= 0xDFFF) {
  220. size += 4;
  221. i++;
  222. } else {
  223. size += 3;
  224. }
  225. }
  226. return size;
  227. } catch (error) {
  228. console.log("calculateStringByteSize error", error);
  229. return 1024 * 1024;
  230. }
  231. },
  232. /**
  233. * 安全的js对象转字符串
  234. */
  235. safeJsonStringify(obj, handleValue) {
  236. if (!obj) return "{}";
  237. try {
  238. if (handleValue) {
  239. return JSON.stringify(obj, (key, value) => {
  240. return handleValue(key, value)
  241. })
  242. } else {
  243. return JSON.stringify(obj, (key, value) => {
  244. return value;
  245. })
  246. }
  247. } catch (error) {
  248. // 尝试解析json失败,可能是变量循环引用的问题,继续尝试增加WeakSet解析
  249. }
  250. try {
  251. let seen = new WeakSet();
  252. let jsonStr = JSON.stringify(obj, (key, value) => {
  253. if (typeof value == "object") {
  254. try {
  255. if (value instanceof File) {
  256. value = "js:File";
  257. }
  258. if (value && value.constructor && value.constructor.name && typeof value.constructor.name == "string") {
  259. let className = value.constructor.name;
  260. if (className == "VueComponent") {
  261. return "js:Object:VueComponent";
  262. }
  263. }
  264. } catch (error) { }
  265. }
  266. if (typeof value == "function") {
  267. try {
  268. value = value.toString();
  269. } catch (error) {
  270. value = "js:function";
  271. }
  272. }
  273. if (typeof value === "object" && value !== null) {
  274. // 处理循环引用问题
  275. if (seen.has(value)) {
  276. return;
  277. }
  278. seen.add(value);
  279. }
  280. if (handleValue && typeof handleValue == "function") {
  281. try {
  282. return handleValue(key, value);
  283. } catch (error) {
  284. console.log("handleValue error", error);
  285. }
  286. return;
  287. }
  288. return value;
  289. });
  290. seen = null;
  291. return jsonStr;
  292. } catch (error) {
  293. return "{}";
  294. }
  295. },
  296. /**
  297. * 根据限制的字节大小截取字符串
  298. */
  299. truncateStrBySize(str = "", size = 20 * 1024) {
  300. try {
  301. if (size < 1) return "";
  302. if (this.calculateStringByteSize(str) <= size) return str;
  303. let endStr = "";
  304. if (size > 30) {
  305. endStr = "(已截断多余部分)"
  306. size = size - 30;
  307. }
  308. let low = 0, high = str.length, mid;
  309. while (low < high) {
  310. mid = Math.floor((low + high) / 2);
  311. let currentSize = this.calculateStringByteSize(str.substring(0, mid));
  312. if (currentSize > size) {
  313. // 如果大于限制值,减小高边界
  314. high = mid;
  315. } else {
  316. // 如果小于或等于限制值,增加低边界
  317. low = mid + 1;
  318. }
  319. }
  320. // 返回截断的字符串,注意low-1是因为low是最后一次检查超出大小时的位置
  321. return str.substring(0, low - 1) + endStr;
  322. } catch (error) {
  323. console.log("truncateStrBySize error", error);
  324. return "";
  325. }
  326. }
  327. }