1/*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16const typeErrorCode = 401;
17class BusinessError extends Error {
18  code: number;
19  constructor(msg: string) {
20    super(msg);
21    this.name = 'BusinessError';
22    this.code = typeErrorCode;
23  }
24}
25
26type TransformsFunc = (this: Object, key: string, value: Object) => Object | undefined | null;
27type ReplacerType = (number | string)[] | null | TransformsFunc;
28
29const enum BigIntMode {
30  DEFAULT = 0,
31  PARSE_AS_BIGINT = 1,
32  ALWAYS_PARSE_AS_BIGINT = 2,
33}
34
35interface ParseOptions {
36  bigIntMode?: BigIntMode;
37}
38
39export interface JSON {
40  parseBigInt(text: string, reviver?: TransformsFunc, options?: ParseOptions): Object | null;
41  stringifyBigInt(value: Object, replacer?: TransformsFunc, space?: string | number): string;
42  stringifyBigInt(value: Object, replacer?: (number | string)[] | null, space?: string | number): string;
43}
44
45function parse(text: string, reviver?: TransformsFunc, options?: ParseOptions): Object | null {
46  if (typeof text !== 'string') {
47    let error = new BusinessError(`Parameter error. The type of ${text} must be string`);
48    throw error;
49  }
50  if (reviver) {
51    if (typeof reviver !== 'function') {
52      let error = new BusinessError(`Parameter error. The type of ${reviver} must be a method`);
53      throw error;
54    }
55  }
56
57  try {
58    return (JSON as unknown as JSON).parseBigInt(text, reviver, options);
59  } catch (e) {
60    let error = new BusinessError(`e.message: ` + (e as Error).message + `, e.name: ` + (e as Error).name);
61    throw error;
62  }
63}
64
65function stringfyFun(value: Object, replacer?: (this: Object, key: string, value: Object) => Object,
66    space?: string | number): string {
67  if (arguments.length === 1) {
68    return JSON.stringify(value);
69  } else {
70    return JSON.stringify(value, replacer, space);
71  }
72}
73
74function stringfyArr(value: Object, replacer?: (number | string)[] | null, space?: string | number): string {
75  if (arguments.length === 1) {
76    return (JSON as unknown as JSON).stringifyBigInt(value);
77  } else {
78    return (JSON as unknown as JSON).stringifyBigInt(value, replacer, space);
79  }
80}
81
82function isParameterType(self: unknown): boolean {
83  return (Array.isArray(self) && self.every((item) => typeof item === 'number' || typeof item === 'string') ||
84  self === null || typeof self === 'function');
85}
86
87function isSpaceType(self: unknown): boolean {
88  return (typeof self === 'string' || typeof self === 'number');
89}
90
91function isCirculateReference(value: Object, seenObjects: Set<Object> = new Set()): boolean {
92  if (seenObjects.has(value)) {
93    return true;
94  }
95  seenObjects.add(value);
96  if (value === null || typeof value !== 'object') {
97    return false;
98  }
99  for (const key in value) {
100    if (Object.prototype.hasOwnProperty.call(value, key)) {
101      const temp = value[key];
102      if (temp !== null && typeof temp === 'object' &&
103        (seenObjects.has(temp) || isCirculateReference(temp, seenObjects))) {
104        return true;
105      }
106    }
107  }
108  seenObjects.delete(value);
109  return false;
110}
111
112function stringify(value: Object, replacer?: ReplacerType, space?: string | number): string {
113  if (isCirculateReference(value)) {
114    let error = new BusinessError(`Parameter error. The object circular Reference`);
115    throw error;
116  }
117  if (replacer) {
118    if (!isParameterType(replacer)) {
119      let error = new BusinessError(`Parameter error. The type of ${replacer} must be a method or array`);
120      throw error;
121    }
122    if (space) {
123      if (!isSpaceType(space)) {
124        let error = new BusinessError(`Parameter error. The type of ${space} must be a string or number`);
125        throw error;
126      }
127    } else if (space === null) {
128      let error = new BusinessError(`Parameter error. The type of ${space} must be a string or number`);
129      throw error;
130    }
131  }
132
133  try {
134    if (arguments.length === 1) {
135      return stringfyArr(value);
136    } else {
137      if (typeof replacer === 'function') {
138        return stringfyFun(value, replacer, space);
139      } else {
140        return stringfyArr(value, replacer, space);
141      }
142    }
143  } catch (e) {
144    let error = new BusinessError((e as Error).message + `, e.name: ` + (e as Error).name);
145    throw error;
146  }
147}
148
149function has(value: object, key: string): boolean {
150  if (typeof value !== 'object' || typeof value === 'undefined' || value === null) {
151    let error = new BusinessError(`Parameter error. The type of ${value} must be object`);
152    throw error;
153  }
154  if (value instanceof Array) {
155    let error = new BusinessError(`Parameter error. The type of ${value} must be json object`);
156    throw error;
157  }
158  if (!(typeof key === 'string' && key.length)) {
159    let error = new BusinessError(`Parameter error. The type of ${key} must be string and not empty`);
160    throw error;
161  }
162  return Object.prototype.hasOwnProperty.call(value, key);
163}
164
165function remove(value: object, key: string): void {
166  if (typeof value !== 'object' || typeof value === 'undefined' || value === null) {
167    let error = new BusinessError(`Parameter error. The type of ${value} must be object`);
168    throw error;
169  }
170  if (value instanceof Array) {
171    let error = new BusinessError(`Parameter error. The type of ${value} must be json object`);
172    throw error;
173  }
174  if (!(typeof key === 'string' && key.length)) {
175    let error = new BusinessError(`Parameter error. The type of ${key} must be string and not empty`);
176    throw error;
177  }
178  if (Object.prototype.hasOwnProperty.call(value, key)) {
179    delete value[key];
180  }
181}
182
183export default {
184  parse: parse,
185  stringify: stringify,
186  has: has,
187  remove: remove,
188};
189