JSON Extension Library

When to Use

The JSON extension library extends the native JSON functionality, providing additional error handling, circular reference detection, BigInt processing, and strict type checking for different input types. It relies on the native JSON.parse and JSON.stringify methods but adds custom logic and additional methods such as has and remove. For details, see @arkts.json.

The JSON extension library mainly applies to the following scenarios:

  • JSON parsing or serialization that involves BigInt handling.

  • Situations requiring stricter parameter verification and error handling.

  • Scenarios where circular references need to be detected when objects are serialized.

  • Safe object operations (for example, has or remove).

This library is suitable for scenarios where enhanced JSON functionality is needed, especially when dealing with BigInt and strict parameter verification.

JSON Extension Description

parse

parse(text: string, reviver?: Transformer, options?: ParseOptions): Object | null

Parses a JSON string and supports BigInt mode.

Differences from native

Feature Native parse Current parse
BigInt support Not supported (throws TypeError) Supported (via parseBigInt extension)
Parameter verification Weak verification Strong verification (throws BusinessError)
Error message Native error (such as SyntaxError) Custom BusinessError
reviver parameter Supported Supported, with strict type checking

stringify

stringify(value: Object, replacer?: (number | string)[] | null, space?: string | number): string

Converts an object to a JSON string and supports BigInt mode.

Differences from native

Feature Native stringify Current stringify
BigInt support Not supported (throws TypeError) Supported (via stringifyBigInt extension)
Circular reference detection Throws TypeError Detects and throws BusinessError
Parameter verification Weak verification Strong verification (replacer must be a function or an array)
Error message Native error Custom BusinessError

has

has(obj: object, property: string): boolean

Checks whether an object contains a specified property, ensuring the input is an object and the property key is a valid string.

Differences from native

Feature Native (obj.hasOwnProperty) Current has
Parameter verification No verification (potential misuse) Enforces obj as a regular object and property as a non-empty string
Error handling May fail silently Throws BusinessError

remove

remove(obj: object, property: string): void

Removes a specified property from an object.

Feature Native (delete obj.key) Current remove
Parameter verification No verification (potential accidental deletion) Enforces obj as a regular object and property as a non-empty string
Error handling May fail silently Throws BusinessError

Summary

Feature Native JSON This Library
Strict parameter verification Not supported Supported
Circular reference detection Not supported Supported
BigInt processing Not supported Supported
Enhanced error handling (BusinessError) Not supported Supported
Additional methods (has or remove) Not supported Supported

Development Scenario

Parsing JSON Strings Containing Nested Quotation Marks

Nested quotation marks in a JSON string break its structure, which will cause parsing failure.

// For example, the following JSON string is damaged due to nested quotation marks. If JSON.parse is executed, an exception will be thrown.
// let jsonStr = `{"info": "{"name": "zhangsan", "age": 18}"}`;

The following provides two methods to solve this problem:

Method 1: Avoid nested quotation marks.

import { JSON } from '@kit.ArkTS';

interface Info {
  name: string;
  age: number;
}

interface TestObj {
  info: Info;
}

interface TestStr {
  info: string;
}
// ...
  /*
   * Convert `{"info": "{"name": "zhangsan", "age": 18}"}` in the original JSON string
   * to `{"info": {"name": "zhangsan", "age": 18}}`.
   * */
  let jsonStr = `{"info": {"name": "zhangsan", "age": 18}}`;
  let obj1  = JSON.parse(jsonStr) as TestObj;
  console.info(JSON.stringify(obj1));    //{"info":{"name":"zhangsan","age":18}}
  // Obtain the name property in the JSON string.
  console.info(obj1.info.name); // zhangsan

Method 2: Escape the nested quotation marks in the JSON string to restore the proper JSON structure.

import { JSON } from '@kit.ArkTS';

interface Info {
  name: string;
  age: number;
}

interface TestObj {
  info: Info;
}

interface TestStr {
  info: string;
}
// ...
  /*
   * Double-escape `{"info": "{"name": "zhangsan", "age": 18}"}` in the original JSON string
   * and convert it to `{"info": "{\\"name\\": \\"zhangsan\\", \\"age\\": 18}"}`.
   * */
  let jsonStr = `{"info": "{\\"name\\": \\"zhangsan\\", \\"age\\": 18}"}`;
  let obj2 = JSON.parse(jsonStr) as TestStr;
  console.info(JSON.stringify(obj2));    // {"info":"{\"name\": \"zhangsan\", \"age\": 18}"}
  // Obtain the name property in the JSON string.
  let obj3 = JSON.parse(obj2.info) as Info;
  console.info(obj3.name); // zhangsan

Parsing JSON Strings Containing BigInts

If the JSON string contains an integer less than -(2^53-1) or greater than (2^53-1), the data precision is lost or incorrect after parsing. In this scenario, BigIntMode must be specified to parse big integers as BigInt.

import { JSON } from '@kit.ArkTS';
// ...
  let numberText = '{"number": 10, "largeNumber": 112233445566778899}';

  let numberObj1 = JSON.parse(numberText) as Object;
  console.info((numberObj1 as object)?.['largeNumber']);    // 112233445566778900

  // Use the BigInt mode of PARSE_AS_BIGINT for parsing to avoid parsing errors.
  let options: JSON.ParseOptions = {
    bigIntMode: JSON.BigIntMode.PARSE_AS_BIGINT,
  }

  let numberObj2 = JSON.parse(numberText, null, options) as Object;

  console.info(typeof (numberObj2 as object)?.['number']);   // number
  console.info((numberObj2 as object)?.['number']);    // 10

  console.info(typeof (numberObj2 as object)?.['largeNumber']);    // bigint
  console.info((numberObj2 as object)?.['largeNumber']);    // 112233445566778899

Serializing BigInt Objects

To address the issue that native JSON cannot serialize BigInt objects, this library provides the following two JSON serialization methods:

Method 1: Serialize a BigInt object without a custom conversion function.

import { JSON } from '@kit.ArkTS';
// ...
  let bigIntObject = BigInt(112233445566778899n)

  console.info(JSON.stringify(bigIntObject)); // 112233445566778899

Method 2: Use a custom conversion function to serialize a BigInt object.

import { JSON } from '@kit.ArkTS';
// ...
  let bigIntObject = BigInt(112233445566778899n)

  // Incorrect serialization approach: Directly return a BigInt object in the custom function.
  // Error case: JSON.stringify(bigIntObject, (key: string, value: Object): Object =>{ return value; });

  // Correct serialization approach: Preprocess the BigInt object as a string in the custom function.
  let result: string = JSON.stringify(bigIntObject, (key: string, value: Object): Object => {
    if (typeof value === 'bigint') {
      return value.toString();
    }
    return value;
  });
  console.info('result:', result); // result: "112233445566778899"

Serializing Floating-Point Numbers

In JSON serialization, floating-point numbers undergo special handling: When the fractional part is 0, it will be automatically omitted for concision. This may result in loss of precision information and affect scenarios where accurate representation of floating-point numbers is required (such as financial calculation and scientific measurement). The following example provides a solution to this scenario:

import { JSON } from '@kit.ArkTS';
// ...
  // Serializing a floating-point number with a non-zero fractional part works as expected.
  let floatNumber1 = 10.12345;
  console.info(JSON.stringify(floatNumber1)); // 10.12345

  // Serializing a floating-point number with a zero fractional part results in the loss of fractional precision for a more concise representation.
  let floatNumber2 = 10.00;
  console.info(JSON.stringify(floatNumber2)); // 10

  // The following is a method to prevent the loss of floating-point precision:
  let result = JSON.stringify(floatNumber2, (key: string, value: Object): Object => {
    if (typeof value === 'number') {
      // Customize the fixed precision as needed for your specific use case.
      return value.toFixed(2);
    }
    return value;
  });
  console.info(result); // "10.00"