Библиотеки
@diasoft/qpalette-core
Обработка больших чисел

Большие числа

Доступно автоматическое преобразование больших чисел в специальный тип QBigInt и обратно при запросах к серверу с помощью HttpClient в формате JSON.

Если в ответе на запрос к серверу встречается число больше Number.MAX_SAFE_INTEGER (opens in a new tab), оно будет преобразовано в тип QBigInt. Если в запросе к серверу встречается тип QBigInt, оно будет преобразовано в число без кавычек (см. примеры далее).

Преобразование доступно, начиная с выпуска 7.3.12 и по умолчанию выключено.

Как включить автоматическое преобразование больших чисел

Оно может быть включено для всего стенда настройкой в конфигурации:

{
  "common": {
    "bigNumbers": {
      "autoConvert": true
    }
  }
}

Также его можно включить/выключить для конкретного продукта при подключении QCoreModule:

import {DoBootstrap, NgModule} from '@angular/core';
import {QCoreModule,QCoreService} from '@diasoft/qpalette-core';
 
@NgModule({
    imports: [
        QCoreModule.forRoot({
            ...QCoreService.config.common,
            bigNumbers: { autoConvert: false }
        })
    ]
})
export class AppModule implements DoBootstrap { /*...*/ }

Как преобразовать данные вручную

Для ручного преобразования JSON с большими числами можно использовать QJsonConverter:

import {inject} from "@angular/core";
import {QJsonConverter} from '@diasoft/qpalette-core';
 
const value = '{"bigInt":9223372036854775807}';
const jsonConverter = inject(QJsonConverter);
const withoutLosses = jsonConverter.parse(value);
 
console.log('Число без потерь:', withoutLosses['bigInt']); // Число без потерь: QBigInt{#rawValue: '9223372036854775807'}
console.log(withoutLosses['bigInt'].toString()); // 9223372036854775807
console.log(JSON.stringify(withoutLosses)); // {"bigInt":9223372036854775807}

При работе с HttpClient также можно выполнить преобразование вручную:

import {inject} from "@angular/core";
import {HttpClient} from "@angular/common/http";
import {QJsonConverter} from '@diasoft/qpalette-core';
 
const httpClient = inject(HttpClient);
const jsonConverter = inject(QJsonConverter);
 
httpClient.get('/some/bigint', {responseType: 'text'}) // Отключаем преобразование
  .subscribe((value => {
    value = jsonConverter.parse(value); // Преобразуем JSON самостоятельно
  }));

При этом неважно, включено ли автоматическое преобразование или нет.

Как это работает

Тип QBigInt – это специальный "промежуточный" тип, который хранит в себе строковое представление числа, полученного с сервера в формате JSON, если оно превышает Number.MAX_SAFE_INTEGER (opens in a new tab). Например, если сервера был получен JSON такого вида:

{ "bigInt": 9223372036854775807 }

То при попытке получить его с сервера стандартными средствами оно будет преобразовано в тип number с потерями:

import {HttpClient} from "@angular/common/http";
import {inject} from "@angular/core";
 
const httpClient = inject(HttpClient);
 
httpClient.get('/some/bigint') // HttpClient по умолчанию сам пытается преобразовать JSON
  .subscribe((value => {
    console.log('Число с потерями:', value['bigInt']); // Число с потерями: 9223372036854776000
    console.log(typeof value['bigInt']); // number
  }));
 
httpClient.get('/some/bigint', {responseType: 'text'}) // Можно отключить преобразование
  .subscribe((value => {
    value = JSON.parse(value); // ...и преобразовать JSON самостоятельно, но результат будет тот же
    console.log('Число с потерями:', value['bigInt']); // Число с потерями: 9223372036854776000
    console.log(typeof value['bigInt']); // number
  }));

Для решения этой проблемы и был придуман тип QBigInt. При включении преобразования подобные числа будут преобразованы в QBigInt:

import {HttpClient} from "@angular/common/http";
import {inject} from "@angular/core";
import {QBigInt} from '@diasoft/qpalette-core';
 
const httpClient = inject(HttpClient);
 
httpClient.get('/some/bigint') // HttpClient теперь пытается преобразовать JSON без потерь
  .subscribe((value => {
    console.log('Число без потерь:', value['bigInt']); // Число без потерь: QBigInt{#rawValue: '9223372036854775807'}
    console.log(typeof value['bigInt']); // object
    console.log(value['bigInt'] instanceof QBigInt); // true
    console.log(value['bigInt'].toString()); // 9223372036854775807
    console.log(JSON.stringify(value)); // {"bigInt":9223372036854775807}
  }));

Обратите внимание, что в случае запроса с {responseType: 'text'} автоматическое преобразование не будет выполнено:

import {HttpClient} from "@angular/common/http";
import {inject} from "@angular/core";
import {QBigInt,QJsonConverter} from '@diasoft/qpalette-core';
 
const httpClient = inject(HttpClient);
const jsonConverter = inject(QJsonConverter);
 
httpClient.get('/some/bigint', {responseType: 'text'}) // В таком случае преобразование не будет выполнено
  .subscribe((value => {
    const jsonWithLosses = JSON.parse(value); // Так тоже не сработает
    console.log('Число с потерями:', jsonWithLosses['bigInt']); // Число с потерями: 9223372036854776000
    console.log(typeof jsonWithLosses['bigInt']); // number
    console.log(jsonWithLosses['bigInt'] instanceof QBigInt); // false
 
    const jsonWithoutLosses = jsonConverter.parse(value); // Но можно преобразовать JSON с помощью конвертера без потерь
    console.log('Число без потерь:', jsonWithoutLosses['bigInt']); // Число без потерь: QBigInt{#rawValue: '9223372036854775807'}
  }));

Почему не встроенный тип BigInt?

BigInt не имеет встроенной реализации метода toJSON, поэтому для автоматической конвертации JSON в BigInt и обратно требуется переопределять прототип (см. раздел на MDN (opens in a new tab)).

Поскольку Q.Palette работает в сложной среде микрофронтендов, в каждом из которых может быть своя реализация метода toJSON, было решено ввести свой тип с заранее заданным прототипом.

Помимо этого, BigInt не поддерживает числа с плавающей точкой, QBigInt решает эту проблему.

Как работать с QBigInt

Класс имеет свои реализации методов toJSON и toString, поэтому его объекты можно приводить к строке и к формату JSON:

import {QBigInt} from '@diasoft/qpalette-core';
const bigInt = new QBigInt("9223372036854775807");
 
console.log('Число без потерь:', bigInt); // Число без потерь: QBigInt{#rawValue: '9223372036854775807'}
console.log(bigInt.toString()); // 9223372036854775807
console.log(JSON.stringify({"bigInt": bigInt})); // {"bigInt":9223372036854775807}

Обратите внимание, что сменить реализацию toJSON и toString класса QBigInt не получится, так как его прототип защищён от изменений:

import {QBigInt} from '@diasoft/qpalette-core';
const bigInt = new QBigInt("9223372036854775807");
bigInt.toJSON = function () { /*...*/ } // ошибка
bigInt.toString = function () { /*...*/ } // ошибка

Операции с большими числами

QBigInt не поддерживает операции над числами. Для выполнения операций с числом преобразуйте объект QBigInt самостоятельно во встроенный BigInt или используйте библиотеку bignumber.js (opens in a new tab) (не входит в состав Q.Palette).

При преобразовании в BigInt убедитесь, что число не является числом с плавающей точкой, иначе будет ошибка (см. следующий раздел).

const bigInt = new QBigInt("9223372036854775807");
const a = BigInt(bigInt.toString());
const b = BigInt(1);
const sum = a + b;
 
console.log(sum); // 9223372036854775808n
 
const bigFloat = new QBigInt("1.234567890123456789");
const floatToInt = BigInt(bigInt.toString()); // ошибка

Числа с плавающей точкой

Встроенный тип BigInt не поддерживает числа с плавающей точкой. Однако QBigInt не имеет такой проблемы. При включенной конвертации большие числа с плавающей точкой также будут преобразованы в QBigInt и будут храниться в строковом виде.

Поддерживаются как числа в стандартной форме, так и экспоненциальной.

import {QBigInt} from '@diasoft/qpalette-core';
 
// обычная форма
const bigFloat = new QBigInt("1.234567890123456789");
 
console.log(bigFloat); // QBigInt{#rawValue: '1.234567890123456789'}
console.log(bigFloat.toString()); // 1.234567890123456789
console.log(JSON.stringify({"bigFloat": bigFloat})); // {"bigInt":1.234567890123456789}
 
// экспоненциальная форма
const bigFloatExp = new QBigInt("1.234567890123456789e+20");
 
console.log(bigFloatExp); // QBigInt{#rawValue: '1.234567890123456789e+20'}
console.log(bigFloatExp.toString()); // 1.234567890123456789e+20
console.log(JSON.stringify({"bigFloat": bigFloatExp})); // {"bigInt":1.234567890123456789e+20}

QBigInt не поддерживает операции над числами с плавающей точкой, для операций с такими числами можно использовать библиотеку bignumber.js (opens in a new tab) (не входит в состав Q.Palette).

Поддержка компонентами Q.Palette и PrimeNG

Обратите внимание, что компоненты PrimeNG не поддерживают (и не планируют поддерживать (opens in a new tab)) большие числа на момент написания этой статьи. В компонентах Q.Palette также пока не планируется поддержка больших чисел.

При работе с палитрой убедитесь, что большие числа корректно преобразуются из QBigInt в строки (при передаче в компонент) и обратно (при передаче на сервер).

Пример

Предположим, с сервера пришел JSON:

[
  1,
  8172638172638716283761,
  1.234567890123456789,
  1.234567890123456789e+20,
  1.23e+20,
  {
    "normalInt": 123,
    "bigInt": 9223372036854775807,
    "justString": "9223372036854775807",
    "nullValue": null,
    "innerObj": {
      "normalInt": 234,
      "bigInt" : 18276387162397129873,
      "someString": "test"
    }
  }
 ]

При включенной конвертации следующие числа будут преобразованы в QBigInt:

[
  8172638172638716283761,
  1.234567890123456789,
  1.234567890123456789e+20,
  9223372036854775807,
  18276387162397129873
]

Следующие числа будут распознаны как обычный number:

[
  1,
  123000000000000000000,
  123,
  234
]