Files
xo-cash-utils/source/extended-json.ts

77 lines
3.0 KiB
TypeScript

import { binToHex, hexToBin } from '@bitauth/libauth';
/**
* Matches a bigint encoded in Extended JSON format: `<bigint: 123n>`.
*/
const EXTENDED_JSON_BIGINT_PATTERN = /^<bigint: (?<bigint>[+-]?[0-9]+)n>$/u;
/**
* Matches a Uint8Array encoded in Extended JSON format: `<uint8array: abcd>`.
*/
const EXTENDED_JSON_UINT8ARRAY_PATTERN = /^<uint8array: (?<hex>[0-9a-f]*)>$/u;
/**
* The JSON replacer that encodes `bigint` and `Uint8Array` values in Extended JSON format,
* compatible with the format expected by `extendedJsonReviver`.
*
* - BigInts are encoded as `<bigint: 123n>`.
* - Uint8Arrays are encoded as `<uint8array: abcd>`.
* All other values pass through unchanged and if any incompatible type is encountered, an error is thrown.
*
* Note: To use this function, pass it as the second argument to `JSON.stringify` when serializing data.
*
* Note to developers: Libauth's `stringify` is the replacer. It also serializes functions and symbols,
* which we do not support. Passing it would let templates include those values, but revival would then fail.
* This module provides a dedicated replacer and reviver so serialization and deserialization stay aligned.
*
* @param _propertyKey The property key being serialized, required by the `JSON.stringify` replacer but not used here.
* @param value The value to encode or pass through unchanged.
* @returns The encoded string
*/
export const extendedJsonReplacer = (_propertyKey: string, value: unknown): unknown => {
if (value instanceof Uint8Array) {
return `<uint8array: ${binToHex(value)}>`;
}
if (typeof value === 'bigint') {
return `<bigint: ${value.toString()}n>`;
}
return value;
};
/**
* The JSON reviver that reconstructs `bigint` and `Uint8Array` values encoded by `extendedJsonReplacer`.
*
* Note: To use this function, pass it as the second argument to `JSON.parse` when deserializing data.
*
* @param _propertyKey The property key being deserialized, required by the `JSON.parse` reviver but not used here.
* @param value The value to reconstruct or pass through unchanged.
* @returns The reconstructed value
*/
export const extendedJsonReviver = (_propertyKey: string, value: unknown): unknown => {
// If the value is not a string, return the original value
if (typeof value !== 'string') {
return value;
}
// Match the bigint pattern
const bigintPatternMatch = value.match(EXTENDED_JSON_BIGINT_PATTERN);
// If the value matches the bigint pattern, return the reconstructed bigint
if (bigintPatternMatch) {
return BigInt(bigintPatternMatch.groups!.bigint);
}
// Match the Uint8Array pattern
const uint8arrayPatternMatch = value.match(EXTENDED_JSON_UINT8ARRAY_PATTERN);
// If the value matches the Uint8Array pattern, return the reconstructed Uint8Array
if (uint8arrayPatternMatch) {
return hexToBin(uint8arrayPatternMatch.groups!.hex);
}
// If the value does not match either pattern, return the original value
return value;
};