Skip to content

Commit 785da96

Browse files
more work on bigints
1 parent f1ecd90 commit 785da96

1 file changed

Lines changed: 95 additions & 0 deletions

File tree

content/docs/specta/rfc/flightscience.mdx

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1808,6 +1808,101 @@ I have some concerns about this approach with [rspc](https://github.com/specta-r
18081808

18091809
### BigInt and special-float support in Tauri
18101810

1811+
BigInt's have a different constraint, there JSON representation is lossly. Mainly:
1812+
- When `JSON.parse` is run large integers are truncated.
1813+
- `NaN`, `Infinity` and `-Infinity` all become `null`
1814+
1815+
So at it's core, doing `commandResult.then((v) => BigInt(v))` will still result in truncation if `v` is a `number`. So you need a way of serializing these correctly in Rust.
1816+
1817+
#### Jsone
1818+
1819+
[Jsone](https://github.com/specta-rs/jsone) is a new crate I built out of my work looking at solutions here.
1820+
1821+
At it's core it's one simple API: `pub struct Jsone<T>(pub T);`
1822+
1823+
However internally it wraps the `Serializer` and `Deserializer` with it's own as it passes through the `Serialize for Jsone<T>` and `Deserialize for Jsone<T>` implementations.
1824+
1825+
Using Serde methods we can capture large numbers, `NaN`, `Infinity` and `-Infinity` and encode them in a lossless way.
1826+
1827+
Below is the example from the repository which shows it pretty clearly:
1828+
1829+
```rs
1830+
use std::f64;
1831+
1832+
use jsone::Jsone;
1833+
use serde::{Deserialize, Serialize};
1834+
1835+
#[derive(Debug, Deserialize, PartialEq, Serialize)]
1836+
struct Payload<N> {
1837+
id: N,
1838+
}
1839+
1840+
fn main() {
1841+
{
1842+
// wrap your root type in `Jsone` and we will take care of the rest!
1843+
let json = serde_json::to_string(&Jsone(Payload { id: 42 })).unwrap();
1844+
assert_eq!(json, r#"{"id":42}"#);
1845+
println!("{json}");
1846+
1847+
let payload: Jsone<Payload<i32>> = serde_json::from_str(&json).unwrap();
1848+
assert_eq!(payload.0.id, 42);
1849+
println!("{payload:?}");
1850+
}
1851+
1852+
{
1853+
// wrap your root type in `Jsone` and we will take care of the rest!
1854+
let json = serde_json::to_string(&Jsone(Payload { id: f64::MAX })).unwrap();
1855+
assert_eq!(
1856+
json,
1857+
format!(r#"{{"id":{{"$$jsone$remap$$":"{}"}}}}"#, f64::MAX)
1858+
);
1859+
println!("\n{json}");
1860+
1861+
let payload: Jsone<Payload<f64>> = serde_json::from_str(&json).unwrap();
1862+
assert_eq!(payload.0.id, f64::MAX);
1863+
println!("{payload:?}");
1864+
}
1865+
1866+
{
1867+
let json = serde_json::to_string(&Jsone(Payload { id: f64::NAN })).unwrap();
1868+
assert_eq!(json, r#"{"id":{"$$jsone$remap$$":1}}"#);
1869+
println!("\n{json}");
1870+
1871+
let payload: Jsone<Payload<f64>> = serde_json::from_str(&json).unwrap();
1872+
assert!(payload.0.id.is_nan());
1873+
println!("{payload:?}");
1874+
}
1875+
}
1876+
```
1877+
1878+
Main things to note:
1879+
- large numbers become `{"$$jsone$remap$$": "{number}" }` in JSON
1880+
- Stringifying preserves it through `JSON.parse`.
1881+
- The JS runtime will do `BigInt({number})` automatically so you effectively get `number | bigint` for all fields.
1882+
- float special cases become `{"$$jsone$remap$$": {constant} }` where constant is:
1883+
- `1` for `NaN`
1884+
- `2` for `Infinity`
1885+
- `3` for `-Infinity`
1886+
1887+
This requires both the Rust wrapper on serialization and deserialization and a small utility in JS to run within the `reviver`/`replacer` param for `JSON.parse`/`JSON.stringify` (although it can be used later).
1888+
1889+
This makes the transport layer lossless. Now we need the Specta integration, this is done using `specta_typescript::semantic`.
1890+
1891+
This is built in so in practice it's as easy as:
1892+
- `semantic::Configuration::enable_loseless_bigints`
1893+
- This will make all large numbers (`u64`/`i64` and bigger) become `bigint` in types.
1894+
- Wrap the runtime in `(v) => BigInt(v)` so it's consistent with the datatype.
1895+
- With Jsone for example this layer this would be in practice `(v: number | bigint) => BigInt(v)`.
1896+
- `semantic::Configuration::enable_loseless_bigints`
1897+
- This remaps the type of floats like `f32` and `f64` from `number | null` to `number`.
1898+
- If `NaN`, `Infinity` and `-Infinity` are handled properly they will be preserved and hence `null` won't occur.
1899+
1900+
#### Jsone and Tauri?
1901+
1902+
The biggest issue is that Tauri takes // TODO
1903+
1904+
1905+
18111906
TODO:
18121907
- Is hard - Explain how `IpcResponse` is effectivly seal from HTTP headers
18131908
- `Emitter::emit_str` literally can't work

0 commit comments

Comments
 (0)