Source: cjson.js

  1. "use strict";
  2. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
  3. if (k2 === undefined) k2 = k;
  4. var desc = Object.getOwnPropertyDescriptor(m, k);
  5. if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
  6. desc = { enumerable: true, get: function() { return m[k]; } };
  7. }
  8. Object.defineProperty(o, k2, desc);
  9. }) : (function(o, m, k, k2) {
  10. if (k2 === undefined) k2 = k;
  11. o[k2] = m[k];
  12. }));
  13. var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
  14. Object.defineProperty(o, "default", { enumerable: true, value: v });
  15. }) : function(o, v) {
  16. o["default"] = v;
  17. });
  18. var __importStar = (this && this.__importStar) || function (mod) {
  19. if (mod && mod.__esModule) return mod;
  20. var result = {};
  21. if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
  22. __setModuleDefault(result, mod);
  23. return result;
  24. };
  25. var __importDefault = (this && this.__importDefault) || function (mod) {
  26. return (mod && mod.__esModule) ? mod : { "default": mod };
  27. };
  28. Object.defineProperty(exports, "__esModule", { value: true });
  29. exports.Cjson = void 0;
  30. const file_1 = require("./utils/file");
  31. const path = __importStar(require("path"));
  32. const is_1 = require("./utils/is");
  33. const keywords_1 = __importDefault(require("./utils/keywords"));
  34. const json_1 = require("./utils/json");
  35. const errors_1 = require("./utils/errors");
  36. const refinery_1 = require("./utils/refinery");
  37. /**
  38. * Coded JSON is an extended format of JSON formatted data storage, which gives
  39. * you more previledge to organize data into more structured format.
  40. *
  41. * Here is an example for `CJSON` format:
  42. * @example
  43. *
  44. * {
  45. "source": $import "./source.json",
  46. "target": {
  47. "fruit": "Apple",
  48. "size": "Large",
  49. "color": "Red",
  50. "secColor": $.target.color,
  51. "colorList": [ $.target.color, $.target.secColor ],
  52. // You can add comments like this
  53. "digitCheck": 1.5,
  54. "digitImport": $.target.digitCheck,
  55. "digitArrayImport": [ $.target.digitCheck, $.target.digitImport ]
  56. }
  57. }
  58. *
  59. *
  60. * The above `CJSON` snipped will be deserialized in JSON format and can be used
  61. * as same as other JSON files.
  62. *
  63. * For other details, please refer to official page: {@link https://subhendushekhar.github.io/cjson/}
  64. */
  65. class Cjson extends is_1.Is {
  66. // private obj: any;
  67. filePath;
  68. content = "";
  69. json = undefined;
  70. isContentCJson;
  71. isContentJson = (isFilePath) => { return (0, json_1.isContentJson)(this.content, isFilePath); };
  72. /**
  73. * Coded JSON is an extended format of JSON formatted data storage, which gives
  74. * you more previledge to organize data into more structured format.
  75. *
  76. * Here is an example for `CJSON` format:
  77. * @example
  78. *
  79. * {
  80. "source": $import "./source.json",
  81. "target": {
  82. "fruit": "Apple",
  83. "size": "Large",
  84. "color": "Red",
  85. "secColor": $.target.color,
  86. "colorList": [ $.target.color, $.target.secColor ],
  87. // You can add comments like this
  88. "digitCheck": 1.5,
  89. "digitImport": $.target.digitCheck,
  90. "digitArrayImport": [ $.target.digitCheck, $.target.digitImport ]
  91. }
  92. }
  93. *
  94. *
  95. * The above `CJSON` snipped will be deserialized in JSON format and can be used
  96. * as same as other JSON files.
  97. *
  98. * For other details, please refer to official page: {@link https://subhendushekhar.github.io/cjson/}
  99. *
  100. * @param content Can be path to the CJSON file. In this case the second param can be optional
  101. * @param isContentCJson Set this true if you are passing raw CJSON content as `content`
  102. */
  103. constructor(content, isContentCJson) {
  104. super();
  105. this.obj = undefined;
  106. if ((0, file_1.isAbsolutePath)(content) && isContentCJson)
  107. throw (0, errors_1.CJSONContentInsteadOfFilePath)("CJSON flag is true, but got file path");
  108. if (isContentCJson) {
  109. this.filePath = __dirname;
  110. this.content = content;
  111. this.isContentCJson = isContentCJson;
  112. }
  113. else {
  114. this.filePath = content;
  115. this.content = (0, file_1.read)(this.filePath);
  116. }
  117. this.decodeKeywords();
  118. }
  119. /**
  120. * Root function for decoding keywords
  121. * Need to improve performance. `v1.0.0`
  122. */
  123. decodeKeywords() {
  124. var isChanged = false;
  125. while (true) {
  126. isChanged = false;
  127. if (this.isImport(this.content)) {
  128. this.content = this.decodeImport(this.content, path.dirname(this.filePath));
  129. isChanged = true;
  130. }
  131. if (this.isSingleLineComment(this.content)) {
  132. this.decodeSingleLineComment(this.content);
  133. isChanged = true;
  134. }
  135. this.content = this.decodeRuntimeKeys(this.content);
  136. if (!isChanged)
  137. break;
  138. }
  139. if (this.isRelativeJPath(this.content)) {
  140. this.content = this.decodeRelativePaths(this.content);
  141. isChanged = true;
  142. }
  143. }
  144. refineObj(content) {
  145. if (content)
  146. this.content = content;
  147. this.json = new json_1.Json(this.content, false);
  148. this.obj = JSON.parse(this.content);
  149. }
  150. /**
  151. * Import functions path to relative file is deocded.
  152. * Modifies `content`
  153. */
  154. decodeRelativePaths(content) {
  155. var uniqueKeys = content.match(keywords_1.default.relativeJPathRegex)?.filter((value, index, array) => { return array.indexOf(value) === index; });
  156. if (uniqueKeys)
  157. content = (0, refinery_1.refineRelativePaths)(content, uniqueKeys);
  158. this.refineObj(content);
  159. return content;
  160. }
  161. /**
  162. * Deserializes the keywords.
  163. * @returns `JSON` if no errors. Else `undefined`
  164. */
  165. deserialize() {
  166. this.content = this.decodeRelativePathValues(this.content);
  167. this.refineObj(this.content);
  168. return this.obj;
  169. }
  170. /**
  171. * Returns file path from `import` keyword
  172. * @param lineItem Comma separated line item in string
  173. * @returns File path in string
  174. */
  175. getFilePath(lineItem) {
  176. return lineItem.split(keywords_1.default.importKey)[1].split("\"")[0];
  177. }
  178. /**
  179. * Decodes `import` keyword
  180. * @param lineItem Comma separated line item in string
  181. */
  182. decodeImport(content, curPath) {
  183. var filePath = this.getFilePath(content);
  184. var importFilePath;
  185. if ((0, file_1.isAbsolutePath)(filePath))
  186. importFilePath = filePath;
  187. else if (!(0, file_1.isAbsolutePath)(filePath) && this.isContentCJson)
  188. throw (0, errors_1.AbsolutePathConstraintError)("Only absolute path is supported in import statements");
  189. else
  190. var importFilePath = path.join(curPath, filePath);
  191. var innerContent = (0, file_1.read)(importFilePath);
  192. var qr = "";
  193. if (this.isImport(innerContent))
  194. qr = this.decodeImport(innerContent, path.dirname(importFilePath));
  195. else
  196. qr = innerContent;
  197. content = content.replace(keywords_1.default.importKey + filePath + "\"", qr);
  198. if (this.isImport(content))
  199. content = this.decodeImport(content, curPath);
  200. return content;
  201. }
  202. /**
  203. * Identifies comment lines. Can identify multiple lined comments
  204. * @param lineItem Comma separated line item in string
  205. */
  206. decodeSingleLineComment(lineItem) {
  207. let lineSplit = lineItem.split("\r\n");
  208. for (let i = 0; i < lineSplit.length; i++) {
  209. if (lineSplit[i].trim() !== "" && lineSplit[i].trim().startsWith(keywords_1.default.singleLineComment))
  210. this.content = this.content.replace(lineSplit[i], "");
  211. }
  212. }
  213. decodeRuntimeVals(content, uniqueKeys, injectingObj) {
  214. if (uniqueKeys !== undefined) {
  215. content = (0, refinery_1.refineRuntimeVals)(content, uniqueKeys);
  216. uniqueKeys?.map(eachKey => {
  217. eachKey = eachKey.split("<")[1].split(">")[0];
  218. let keyRegex = new RegExp("<-" + eachKey + "->", 'g');
  219. if (typeof injectingObj[eachKey] === "string")
  220. content = content.replace(keyRegex, injectingObj[eachKey]);
  221. else {
  222. keyRegex = new RegExp("\"<-" + eachKey + "->\"", 'g');
  223. content = content.replace(keyRegex, JSON.stringify(injectingObj[eachKey]));
  224. }
  225. });
  226. }
  227. return content;
  228. }
  229. /**
  230. * Use this for injecting variable at runtime.
  231. *
  232. * Value need to be replaced can be stored with a key like `<key>`.
  233. *
  234. * Pass the value as json object in `injectingObj`
  235. * @param injectingObj Runtime values to be injected in json format
  236. * @returns `JSON` if no errors. Else `undefined`
  237. */
  238. inject(injectingObj) {
  239. let content = this.content;
  240. var uniqueKeys = this.content.match(keywords_1.default.decodedRuntimeKeys)?.filter((value, index, array) => { return array.indexOf(value) === index; });
  241. uniqueKeys = uniqueKeys?.flatMap(eachElem => eachElem.split("<-")[1].split("->")[0]);
  242. var injectObjKeys = Object.keys(injectingObj);
  243. for (let i = 0; i < injectObjKeys.length; i++) {
  244. if (uniqueKeys?.includes(injectObjKeys[i])) {
  245. if (typeof injectingObj[injectObjKeys[i]] === "boolean" ||
  246. typeof injectingObj[injectObjKeys[i]] === "bigint" ||
  247. typeof injectingObj[injectObjKeys[i]] === "number")
  248. content = content.replace(new RegExp("\"<-" + injectObjKeys[i] + "->\"", "g"), injectingObj[injectObjKeys[i]]);
  249. else if (typeof injectingObj[injectObjKeys[i]] === "object")
  250. content = content.replace(new RegExp("\"<-" + injectObjKeys[i] + "->\"", "g"), JSON.stringify(injectingObj[injectObjKeys[i]]));
  251. else
  252. content = content.replace(new RegExp("<-" + injectObjKeys[i] + "->", "g"), injectingObj[injectObjKeys[i]]);
  253. if (this.decodedRuntimeKeyList !== undefined)
  254. this.removeFromStringArray(this.decodedRuntimeKeyList, injectObjKeys[i]);
  255. else
  256. console.error("Cannot update Base.decodedRuntimeKeyList as it is undefined");
  257. }
  258. }
  259. if (this.content.match(keywords_1.default.decodedRuntimeKeys)?.filter((value, index, array) => { return array.indexOf(value) === index; }).length !== 0)
  260. console.warn("Still some runtime keys are expecting values... Run cjson.getAllRuntimeKeys()");
  261. else
  262. this.isInjectExist = false;
  263. content = this.decodeRelativePathValues(content);
  264. this.refineObj(content);
  265. return this;
  266. }
  267. /**
  268. * Converts JSON object to string. Just a wrapper over `JSON.stringify()`
  269. * @param obj JSON object
  270. * @returns JSON string
  271. */
  272. static toString(obj) {
  273. if (obj === null)
  274. return "{}";
  275. else if (!(0, json_1.isContentJson)(JSON.stringify(obj)))
  276. throw new Error("Object is not a JSON");
  277. else
  278. return JSON.stringify(obj);
  279. }
  280. /**
  281. * Deserializes `CJSON` content and returns content as string.
  282. *
  283. * Content will be of pure JSON content and can be parsed as `JSON`
  284. * @returns `JSON` equivalent of `CJSON` content in `string`
  285. */
  286. deserializeAsString() {
  287. this.deserialize();
  288. return this.content;
  289. }
  290. /**
  291. * Removes a key:value from the CJSON context. Key will be JPath in `$.full.path` format
  292. *
  293. * The function automatically deserializes before removing. So, no need to explicitely deserialize it.
  294. * @param key JPath to the key to be removed.
  295. * @returns Resultant content in `JSON` object
  296. */
  297. remove(key) {
  298. this.obj = this.json?.removeWithKey(key, this.content);
  299. this.content = Cjson.toString(this.obj);
  300. return this;
  301. }
  302. /**
  303. * Replace a JPath to a specified value. The function context is of JSON and cannot be used in CJSON context.
  304. *
  305. * In order to use this in CJSON context, follow below steps:
  306. * <ol>
  307. * <li>Create CJson object</li>
  308. * <li>Deserialize</li>
  309. * <li>Deserialize</li>
  310. * <li>cjson.json?.replace("$.jpath", value, object)</li>
  311. * </ol>
  312. * @param key
  313. * @param value
  314. * @param jsonObject
  315. * @returns
  316. */
  317. replace = (jPath, value) => {
  318. if (jPath.startsWith("$."))
  319. jPath = jPath.split("$.")[1];
  320. this.obj = this.json?.replace(jPath, value, this.obj);
  321. this.refineObj(Cjson.toString(this.obj));
  322. return this;
  323. };
  324. decodeRuntimeKeys(content) {
  325. var runtimeKeys = content.match(keywords_1.default.runtimeVals)?.filter((value, index, array) => { return array.indexOf(value) === index; });
  326. if (runtimeKeys !== undefined && runtimeKeys !== null) {
  327. this.isInjectExist = true;
  328. for (let i = 0; i < runtimeKeys.length; i++) {
  329. let variable = runtimeKeys[i].split("<")[1].split(">")[0];
  330. let ignoreUnderQuotes = content.match("\".*" + runtimeKeys[i] + ".*\"");
  331. if (ignoreUnderQuotes === null) {
  332. variable = "\"<-" + variable + "->\"";
  333. content = content.replace(new RegExp(runtimeKeys[i], "g"), variable);
  334. }
  335. }
  336. }
  337. return content;
  338. }
  339. decodeRelativePathValues(content) {
  340. var encodedKeys = content.match(keywords_1.default.encodedRelativeJPathRegex)?.filter((value, index, array) => { return array.indexOf(value) === index; });
  341. if (encodedKeys) {
  342. for (let i = 0; i < encodedKeys.length; i++) {
  343. let key = encodedKeys[i].split("<")[1].split(">")[0];
  344. let value = this.json?.parse(key);
  345. if (value !== null) {
  346. while (JSON.stringify(value).includes("$."))
  347. value = this.json?.parse(value.split("<")[1].split(">")[0]);
  348. if (typeof value === "string")
  349. content = content.replace(new RegExp(encodedKeys[i].replace("$", "\\$"), "g"), value);
  350. else
  351. content = content.replace(new RegExp("\"" + encodedKeys[i].replace("$", "\\$") + "\"", "g"), value);
  352. }
  353. else
  354. content = content.replace(new RegExp("\"" + encodedKeys[i].replace("$", "\\$") + "\"", "g"), "null");
  355. }
  356. }
  357. return content;
  358. }
  359. }
  360. exports.Cjson = Cjson;