在 JavaScript 中处理 ASP.NET Core Web API 的整型类型的大整数
克服 JavaScript 53 位数字的限制,同时保留 .NET 强类型整型。第一部分。
- GitHub 上的 JsLargeIntegralDemo,分支为 "DefaultWay"
背景
在 JavaScript 代码中处理大整数仍然是一个棘手的问题,因为存在 53 位限制,而 BigInt 存在其他问题。
有几个不错的 JavaScript 学习资源
- MDN Web 文档 - JavaScript
- D. Crockford 的《JavaScript: The Good Parts》
- Alex Claus 的《我们不信任输入… 在 JavaScript 中,也不信任输出 ;)》
显然,JavaScript 中有很多糟糕的地方,我们应该牢记并与之共存,尽管自 TypeScript 诞生以来,JavaScript 在语法和一些错误/缺陷修复方面发展迅速。
有 lint 工具可以帮助我们。如果您使用 TypeScript,IDE、TS 编译器和 TS lint 工具将为您提供更多便利。
引言
本文旨在介绍如何在与 ASP.NET Core Web API 进行交互时,在不丢失精度的情况下处理大整数。
您使用整数是因为您希望保留一定的精度。C# .NET 提供了丰富的整数类型:
- sbyte, byte, short, ushort, int, uint
- long, ulong
- 大整数
- Int128, UInt128(.NET 7 及更高版本可用)
虽然 JavaScript 的 BigInt
可能解决了 53 位限制引起的一些问题,但在数据序列化过程中存在一些问题。
- 由于 Google Chrome 和 Mozilla Firefox 可能存在的 bug,导致精度丢失。
- “不知道如何序列化
BigInt
”
如果您搜索“javascript bigint serialize”,您会找到针对各种场景的许多解决方案。正如许多经验丰富的 JavaScript 开发人员所说,没有万能的解决方案。一些相关的帖子:
- https://stackoverflow.com/questions/65152373/typescript-serialize-bigint-in-json
- https://github.com/GoogleChromeLabs/jsbi/issues/30
我看到许多帖子建议以某种方式使用 JavaScript 的 toString()
,然而,当数字大于 53 位时,这种方法会丢失精度或遗漏一位。
您可能已经使用了一些下面描述的解决方案,并且可能已经知道没有万能的解决方案来处理 JavaScript 的这些不足。然而,本文试图为常见场景介绍一些解决方案,并通过生成的代码将这些解决方案进行封装。
Using the Code
克隆或 fork GitHub 上的 JsLargeIntegralDemo 以获取本地工作副本。
必备组件
- .NET 7/8
- Visual Studio 2022
步骤
- 切换到分支 "
DefaultWay
" - 生成 sln 文件。
- 在 Test Explorer 或 "DotNet Test" 中运行 "
IntegrationTestsCore
"。该测试套件将启动 Web API "DemoCoreWeb
",并在完成运行测试用例后关闭。 - 运行 StartDemoCoreWeb.ps1 来启动 Web API "
DemoCoreWeb
"。
文件夹 "HeroesDemo" 包含一个修改版的 "Tour of Heroes",这是一个与 "DemoCoreWeb
" 通信的 Angular 应用。通过运行 "npm install
" 安装包后,运行 "ng test
",您将看到
ASP.NET Core Web API
[DataContract(Namespace = Constants.DataNamespace)]
public class BigNumbers
{
[DataMember]
public long Signed64 { get; set; }
[DataMember]
public ulong Unsigned64 { get; set; }
[DataMember]
public Int128 Signed128 { get; set; }
[DataMember]
public UInt128 Unsigned128 { get; set; }
[DataMember()]
public BigInteger BigInt { get; set; }
}
[Route("api/[controller]")]
public class NumbersController : ControllerBase
{
[HttpPost]
[Route("BigNumbers")]
public BigNumbers PostBigNumbers([FromBody] BigNumbers bigNumbers)
{
return bigNumbers;
}
[HttpPost]
[Route("int64")]
public long PostInt64([FromBody] long int64)
{
return int64;
}
[HttpPost]
[Route("bigIntegralAsStringForJs")]
public string PostBigIntegralAsStringForJs([FromBody] string bigIntegral)
{
return bigIntegral;
}
[HttpPost]
[Route("uint64")]
public ulong PostUint64([FromBody] ulong uint64)
{
return uint64;
}
[HttpPost]
[Route("int128")]
public Int128 PostInt128([FromBody] Int128 int128)
{
return int128;
}
[HttpPost]
[Route("uint128")]
public UInt128 PostUint128([FromBody] UInt128 uint128)
{
return uint128;
}
[HttpPost]
[Route("bigInteger")]
public BigInteger PostBigInteger([FromBody] BigInteger bigInteger)
{
return bigInteger;
}
...
[HttpPost]
[Route("long")]
public long Post([FromBody] long d)
{
return d;
}
[HttpPost]
[Route("ulong")]
public ulong Post([FromBody] ulong d)
{
return d;
}
}
我希望客户端程序能够处理整数而不丢失精度或遗漏一位。
与 .NET 客户端的集成测试
[Collection(TestConstants.LaunchWebApiAndInit)]
public partial class NumbersApiIntegration : IClassFixture<NumbersFixture>
{
public NumbersApiIntegration(NumbersFixture fixture)
{
api = fixture.Api;
}
readonly DemoWebApi.Controllers.Client.Numbers api;
[Fact]
public void TestPostBigNumbers()
{
var d = new DemoWebApi.DemoData.Client.BigNumbers
{
Signed64 = 9223372036854775807, // long.MaxValue,
Unsigned64 = 18446744073709551615, // ulong.MaxValue,
Signed128 = new Int128
(0x7FFF_FFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF), // Int128.MaxValue,
Unsigned128 = new UInt128
(0xFFFF_FFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF), // UInt128.MaxValue
BigInt = new BigInteger(18446744073709551615) *
new BigInteger(18446744073709551615) *
new BigInteger(18446744073709551615),
};
// {"BigInt":6277101735386680762814942322444851025767571854389858533375,
// "Signed128":"170141183460469231731687303715884105727",
// "Signed64":9223372036854775807,"Unsigned128":
// "340282366920938463463374607431768211455","Unsigned64":18446744073709551615}
var r = api.PostBigNumbers(d);
// {"signed64":9223372036854775807,"unsigned64":18446744073709551615,
// "signed128":"170141183460469231731687303715884105727",
// "unsigned128":"340282366920938463463374607431768211455",
// "bigInt":6277101735386680762814942322444851025767571854389858533375}
Assert.Equal(d.Signed64, r.Signed64);
Assert.Equal(d.Unsigned64, r.Unsigned64);
Assert.Equal(d.Signed128, r.Signed128);
Assert.Equal(d.Unsigned128, r.Unsigned128);
Assert.Equal(d.BigInt, r.BigInt);
Assert.NotEqual(d.BigInt, r.BigInt -1);
}
[Fact]
public void TestPostLong()
{
var r = api.PostInt64(long.MaxValue);
Assert.Equal(long.MaxValue, r);
}
[Fact]
public void TestPostULong()
{
var r = api.PostUint64(ulong.MaxValue);
Assert.Equal(ulong.MaxValue, r);
}
[Fact]
public void TestPostInt128()
{
var r = api.PostInt128(Int128.MaxValue);
Assert.Equal(Int128.MaxValue, r);
}
[Fact]
public void TestPostUInt128()
{
var r = api.PostUint128(UInt128.MaxValue);
Assert.Equal(UInt128.MaxValue, r);
}
[Fact]
public void TestPostBigIntegerWith128bits()
{
BigInteger bigInt = new BigInteger(18446744073709551615) *
new BigInteger(18446744073709551615); // 128-bit unsigned
Assert.Equal("340282366920938463426481119284349108225", bigInt.ToString());
var r = api.PostBigInteger(bigInt);
Assert.Equal(bigInt, r);
Assert.Equal("340282366920938463426481119284349108225", r.ToString());
}
[Fact]
public void TestPostBigIntegerWith192bits()
{
BigInteger bigInt = new BigInteger(18446744073709551615) *
new BigInteger(18446744073709551615) *
new BigInteger(18446744073709551615); // 192-bit unsigned
Assert.Equal("6277101735386680762814942322444851025767571854389858533375",
bigInt.ToString());
var r = api.PostBigInteger(bigInt);
Assert.Equal(bigInt, r);
Assert.Equal("6277101735386680762814942322444851025767571854389858533375",
r.ToString());
}
[Fact]
public void TestPostBigIntegerWith80bits()
{
byte[] bytes = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F };
BigInteger bigInt = new BigInteger(bytes); // 192-bit unsigned
Assert.Equal("604462909807314587353087", bigInt.ToString());
var r = api.PostBigInteger(bigInt);
Assert.Equal(bigInt, r);
Assert.Equal("604462909807314587353087", r.ToString());
Assert.True(r.ToByteArray().SequenceEqual(bytes));
}
[Fact]
public void TestPostUIntAsBigInteger()
{
BigInteger bigInt = UInt128.MaxValue;
var r = api.PostBigInteger(bigInt);
Assert.Equal(bigInt, r);
Assert.Equal("340282366920938463463374607431768211455", r.ToString());
}
特别是对于 .NET 7 客户端,没有问题,因为双方通过可靠的数据绑定和序列化精确匹配数据类型,例如 客户端 API
public System.Int128 PostInt128(System.Int128 int128,
Action<System.Net.Http.Headers.HttpRequestHeaders> handleHeaders = null)
{
var requestUri = "api/Numbers/int128";
using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, requestUri);
using var requestWriter = new System.IO.StringWriter();
var requestSerializer = JsonSerializer.Create(jsonSerializerSettings);
requestSerializer.Serialize(requestWriter, int128);
var content = new StringContent(requestWriter.ToString(),
System.Text.Encoding.UTF8, "application/json");
httpRequestMessage.Content = content;
handleHeaders?.Invoke(httpRequestMessage.Headers);
var responseMessage = client.SendAsync(httpRequestMessage).Result;
try
{
responseMessage.EnsureSuccessStatusCodeEx();
var stream = responseMessage.Content.ReadAsStreamAsync().Result;
using JsonReader jsonReader = new JsonTextReader
(new System.IO.StreamReader(stream));
var serializer = JsonSerializer.Create(jsonSerializerSettings);
return serializer.Deserialize<System.Int128>(jsonReader);
}
finally
{
responseMessage.Dispose();
}
}
与 JavaScript / TypeScript 客户端的集成测试
此测试套件在与 ASP.NET Core Web API 通信时,使用 string
来表示 64 位、128 位整数以及 BigInt
,ASP.NET Core Web API 对 JSON 数字对象和表示数字的 JSON string
对象提供了良好的 Web API 数据绑定。
备注
- 您应该找出您用 PHP、Java、Go 或 Python 等开发的后端是否能够提供类似的 Web API 数据绑定能力,可能通过类似的测试套件。
以下测试用例基于 Angular 5+ 代码和 Karma。
请注意那些后缀为 "Incorrect
" 的测试用例以及代码中的注释。
describe('Numbers API without customized serialization', () => {
...
it('postBigNumbersIncorrect', (done) => {
const d: DemoWebApi_DemoData_Client.BigNumbers = {
unsigned64: '18446744073709551615', //2 ^ 64 -1,
signed64: '9223372036854775807', //2 ^ 63 -1,
unsigned128: '340282366920938463463374607431768211455',
signed128: '170141183460469231731687303715884105727',
bigInt: '6277101735386680762814942322444851025767571854389858533375', // 3
// unsigned64, 192bits
};
/**
request:
{
"unsigned64":"18446744073709551615",
"signed64":"9223372036854775807",
"unsigned128":"340282366920938463463374607431768211455",
"signed128":"170141183460469231731687303715884105727",
"bigInt":"6277101735386680762814942322444851025767571854389858533375"
}
response:
{
"signed64": 9223372036854775807,
"unsigned64": 18446744073709551615,
"signed128": "170141183460469231731687303715884105727",
"unsigned128": "340282366920938463463374607431768211455",
"bigInt": 6277101735386680762814942322444851025767571854389858533375
}
*/
service.postBigNumbers(d).subscribe(
r => {
expect(BigInt(r.unsigned64!)).not.toBe
(BigInt('18446744073709551615')); // BigInt can not handle the
// conversion from json
// number form correctly.
expect(BigInt(r.unsigned64!)).toEqual
(BigInt('18446744073709551616')); // actually incorrect
// during deserialization
expect(BigInt(r.signed64!)).not.toBe(BigInt('9223372036854775807'));
expect(BigInt(r.signed64!)).toEqual(BigInt('9223372036854775808'));
expect(BigInt(r.unsigned128!)).toBe(BigInt
(340282366920938463463374607431768211455n));
expect(BigInt(r.signed128!)).toEqual(BigInt
(170141183460469231731687303715884105727n));
expect(BigInt(r.bigInt!)).not.toEqual
(BigInt(6277101735386680762814942322444851025767571854389858533375n));
expect(BigInt(r.bigInt!)).toEqual
(BigInt(6277101735386680763835789423207666416102355444464034512896n));// how wrong
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
/**
* Even though the request payload is 9223372036854776000
* (loosing precision, cause of the 53bit issue),
* or "9223372036854776123", the response is 0 as shown in Chrome's
* console and Fiddler.
* And the Web API has received actually 0. Not sure if the Web API binding
* had turned the request payload into 0 if the client is a Web browser.
*/
it('postInt64ButIncorrect', (done) => {
service.postInt64('9223372036854775807').subscribe(
r => {
expect(BigInt(9223372036854775807n).toString()).toBe
('9223372036854775807');
expect(BigInt(r)).toBe(BigInt('9223372036854775808')); //reponse is
// 9223372036854775807,
//but BigInt(r) gives last 3 digits 808
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
/**
postBigIntegerForJs(bigInteger?: string | null,
headersHandler?: () => HttpHeaders): Observable<string> {
return this.http.post<string>(this.baseUri + 'api/Numbers/bigIntegerForJs',
JSON.stringify(bigInteger),
{ headers: headersHandler ? headersHandler().append
('Content-Type', 'application/json;charset=UTF-8') : new HttpHeaders
({ 'Content-Type': 'application/json;charset=UTF-8' }) });
}
*/
it('postBigIntegralAsStringForJs', (done) => {
service.postBigIntegralAsStringForJs('9223372036854775807').subscribe(
r => {
expect(BigInt(9223372036854775807n).toString()).toBe
('9223372036854775807');
expect(BigInt('9223372036854775807').toString()).toBe
('9223372036854775807');
expect(BigInt(r)).toBe(BigInt('9223372036854775807'));
expect(BigInt(r)).toBe(BigInt(9223372036854775807n));
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
it('postBigIntegralAsStringForJs2', (done) => {
service.postBigIntegralAsStringForJs
('6277101735386680762814942322444851025767571854389858533375').subscribe(
r => {
expect(BigInt
(6277101735386680762814942322444851025767571854389858533375n).toString()).toBe
('6277101735386680762814942322444851025767571854389858533375');
expect(BigInt
('6277101735386680762814942322444851025767571854389858533375').toString()).
toBe('6277101735386680762814942322444851025767571854389858533375');
expect(BigInt(r)).toBe(BigInt
('6277101735386680762814942322444851025767571854389858533375'));
expect(BigInt(r)).toBe(BigInt
(6277101735386680762814942322444851025767571854389858533375n));
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
it('postInt64SmallerInCorrect', (done) => {
service.postInt64('9223372036854775123').subscribe(
r => {
expect(BigInt(r)).not.toBe(BigInt('9223372036854775123')); //reponse is
// 9223372036854775123,
//but BigInt(r) gives l9223372036854774784
expect(BigInt(r)).toBe(BigInt('9223372036854774784')); //many digits
//wrong
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
it('postLongAsBigIntButIncorrect', (done) => {
// request: "9223372036854775807"
// response: 9223372036854775807
service.postBigInteger('9223372036854775807').subscribe(
r => {
expect(BigInt(9223372036854775807n).toString()).toBe
('9223372036854775807');
expect(BigInt(r)).toBe(BigInt('9223372036854775808')); //reponse is
9223372036854775807,
// but BigInt(r) gives last 3 digits 808, since the returned
// value does not have the n suffix.
expect(r.toString()).toBe('9223372036854776000'); //the response
// is a big int which JS could not handle
// in toString(), 53bit gets in the way.
expect(BigInt(r).toString()).toBe('9223372036854775808');
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
it('postLongAsBigIntWithSmallNumber', (done) => {
service.postBigInteger('123').subscribe(
r => {
expect(BigInt(r)).toBe(BigInt(123n));
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
it('postReallyBigInt192bitsButIncorrect', (done) => {
// request: "6277101735386680762814942322444851025767571854389858533375"
// response: 6277101735386680762814942322444851025767571854389858533375
service.postBigInteger
('6277101735386680762814942322444851025767571854389858533375').subscribe(
r => {
expect(BigInt(r)).toBe(BigInt
(6277101735386680762814942322444851025767571854389858533375)); //this
// time, it is correct, but...
expect(BigInt(r).valueOf()).not.toBe
(6277101735386680762814942322444851025767571854389858533375n); // not
// really,
expect(BigInt(r).valueOf()).not.toBe(BigInt
('6277101735386680762814942322444851025767571854389858533375')); // not
// really, because what returned is lack of n
expect(BigInt(r)).toBe
(6277101735386680763835789423207666416102355444464034512896n); // many
// many digits wrong
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
it('postReallyBigInt80bitsButIncorect', (done) => {
service.postBigInteger('604462909807314587353087').subscribe(
r => {
expect(BigInt(r)).toBe(BigInt(604462909807314587353087)); //this time,
// it is correct, but...
expect(BigInt(r).valueOf()).not.toBe(604462909807314587353087n); // not
// really,
expect(BigInt(r).valueOf()).not.toBe
(BigInt('604462909807314587353087')); // not really, because
// what returned is lack of n
expect(BigInt(r).valueOf()).toBe(604462909807314587353088n); // last
// digit wrong
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
it('postReallyBigInt128bitsButIncorect', (done) => {
service.postBigInteger('340282366920938463463374607431768211455').subscribe(
r => {
expect(BigInt(r)).toBe(BigInt
(340282366920938463463374607431768211455)); //this time,
// it is correct, but...
expect(BigInt(r).valueOf()).not.toBe
(340282366920938463463374607431768211455n); // not really,
expect(BigInt(r).valueOf()).not.toBe(BigInt
('340282366920938463463374607431768211455')); // not really,
// because what returned is lack of n
expect(BigInt(r)).toBe(340282366920938463463374607431768211456n); // last
// digit wrong,
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
/**
* Correct.
* Request as string: "170141183460469231731687303715884105727",
* Response: "170141183460469231731687303715884105727" ,
* Content-Type: application/json; charset=utf-8
*/
it('postInt128', (done) => {
service.postInt128('170141183460469231731687303715884105727').subscribe(
r => {
expect(BigInt(r)).toBe(BigInt('170141183460469231731687303715884105727'));
expect(BigInt(r)).toBe(BigInt(170141183460469231731687303715884105727n));
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
/**
* Correct.
* Request as string: "340282366920938463463374607431768211455",
* Response: "340282366920938463463374607431768211455" ,
* Content-Type: application/json; charset=utf-8
*/
it('postUInt128', (done) => {
service.postUint128('340282366920938463463374607431768211455').subscribe(
r => {
expect(BigInt(r)).toBe(BigInt('340282366920938463463374607431768211455'));
expect(BigInt(r)).toBe(BigInt(340282366920938463463374607431768211455n));
expect(BigInt(r).valueOf()).toBe(BigInt
('340282366920938463463374607431768211455'));
expect(BigInt(r).valueOf()).toBe(BigInt
(340282366920938463463374607431768211455n));
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
export interface BigNumbers {
/** BigInteger */
bigInt?: string | null;
/** Int128, -170141183460469231731687303715884105728 to
170141183460469231731687303715884105727 */
signed128?: string | null;
/** long, -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 */
signed64?: string | null;
/** UInt128, 0 to 340282366920938463463374607431768211455 */
unsigned128?: string | null;
/** ulong, 0 to 18,446,744,073,709,551,615 */
unsigned64?: string | null;
}
/**
* POST api/Numbers/long
* @param {string} d long, -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
* @return {string} long, -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
*/
postByDOfInt64(d?: string | null, headersHandler?: () =>
HttpHeaders): Observable<string> {
return this.http.post<string>(this.baseUri + 'api/Numbers/long',
JSON.stringify(d), { headers: headersHandler ? headersHandler().append
('Content-Type', 'application/json;charset=UTF-8') :
new HttpHeaders({ 'Content-Type': 'application/json;charset=UTF-8' }) });
}
/**
* POST api/Numbers/ulong
* @param {string} d ulong, 0 to 18,446,744,073,709,551,615
* @return {string} ulong, 0 to 18,446,744,073,709,551,615
*/
postByDOfUInt64(d?: string | null, headersHandler?: () =>
HttpHeaders): Observable<string> {
return this.http.post<string>(this.baseUri + 'api/Numbers/ulong',
JSON.stringify(d), { headers: headersHandler ? headersHandler().append('Content-Type',
'application/json;charset=UTF-8') : new HttpHeaders
({ 'Content-Type': 'application/json;charset=UTF-8' }) });
}
/**
* POST api/Numbers/bigInteger
* @param {string} bigInteger BigInteger
* @return {string} BigInteger
*/
postBigInteger(bigInteger?: string | null, headersHandler?: () =>
HttpHeaders): Observable<string> {
return this.http.post<string>(this.baseUri + 'api/Numbers/bigInteger',
JSON.stringify(bigInteger), { headers: headersHandler ? headersHandler().append
('Content-Type', 'application/json;charset=UTF-8') :
new HttpHeaders({ 'Content-Type': 'application/json;charset=UTF-8' }) });
}
您是否通过浏览器的开发者控制台或 Fiddler 注意到以下情况?
- 在后缀为 "
Incorrect
" 的测试用例中,JavaScript 客户端使用了 JSON 字符串对象,但是如果 ASP.NET Web API 以 JSON 数字对象响应,JavaScript 会 INCORRECTLY 读取大整数。您知道,这是 JavaScript 的特性。 - 对于
Int128
和UInt128
,ASP.NET Core Web API 以 JSONstring
对象响应,然后 JavaScript 的BigInt
可以正确读取。
在测试用例 "bigIntegralAsStringForJs
" 中,Web API 函数 "postBigIntegralAsStringForJs
" 肯定可以处理大整数,然而,C# 客户端开发人员可能会讨厌这种弱类型设计,尽管 JavaScript 开发人员可能不介意。
[HttpPost]
[Route("bigIntegralAsStringForJs")]
public string PostBigIntegralAsStringForJs([FromBody] string bigIntegral)
{
return bigIntegral;
}
ASP.NET Core Web API 默认返回一个 string
,
Content-Type: text/plain
ABCabc
除非客户端只接受 "application/json
"。
C# 客户端和 TypeScript 客户端的“通用”解决方案
为了让 C# 客户端开发人员和 TypeScript 客户端开发人员满意,并避免为大整数提供两套 Web API 函数,在设计 Web API 时,请考虑以下几点:
- 在 JavaScript 客户端端,在请求负载和响应负载中使用
string
对象来表示 54 位及以上的整数。 - 在服务端,如果整数大于 53 位且不大于 128 位,请使用
Int128
或UInt128
。换句话说,不要在 Web API 层使用long
、ulong
。 - 对于大于 128 位的整数,您可以考虑自定义
BigInteger
的序列化,使您的 Web API 具有与处理UInt128
相同的行为。如果您编写了JsonConverter
来处理BigInteger
,您可能也想为long
和ulong
编写相同的转换器。这样,作为 Web API 开发人员,您将享受丰富的整数类型数据约束,并忽略解决方案 2。好消息是,我已经开发了此类序列化的 JSON 转换器,请参阅下面的相关文章。
备注
- 自定义 Web API 的序列化是针对现有客户端应用程序的重大破坏性更改。在尝试使用本文中提到的解决方案时,您应评估您自己的具体情况。
关注点
如果您正在使用 jQuery、AXIOS、Fetch API 或 Aurelia,您应该会发现上面带有 Angular 5+ 和 Karma 的 TypeScript 测试套件能够代表 JavaScript 的行为,您可以查看以下各种 JavaScript 库的测试套件,并在代码中搜索 "Numbers API"。
虽然 Int128
和 UInt128
的引入为整数提供了更多位数,与 C++ 等其他语言相匹配,但如上面的演示所示,ASP.NET Core 7 已经提供了良好的序列化来克服 JavaScript 的不足。
参考文献
历史
- 2024 年 2 月 23 日:初始版本