Cross-Language Data Type Mapping Markdown Table Template
A type mapping table is the contract for every value that crosses a boundary in a polyglot stack: a TypeScript frontend, a Python service, a Rust worker and a PostgreSQL database, all talking JSON. Developers consult it when writing serialisers, ORM models, code-generation config or an OpenAPI schema, and reviewers use it to catch a column type that cannot hold what the API promises. The useful question it answers is not “what is this called in Rust?” but “what survives the round trip?”.
Edit This Template
Edit cells, add rows, or sort — the Markdown updates livePaste cells from Excel or Google Sheets into the first header to replace the table · Tab / Enter / arrows move · Shift+F10 opens row & column actions
When to Use This Table
Names alone make a poor mapping. Mapping “Integer” to TypeScript number looks fine until a PostgreSQL BIGINT identifier above 2⁵³ − 1 (9,007,199,254,740,991) passes through JSON.parse, which stores every number as an IEEE-754 double and silently rounds it. Python’s int has no upper bound at all, Rust makes you choose a width, and JSON Schema’s "integer" sets no range unless you add one. A good table records those limits next to the names.
So each row should name the concept with its constraints, then give the idiomatic type per language plus the edge that bites: precision loss, string length semantics, key ordering, or how absence differs from null. Where a mapping is lossy in one direction, say so in a footnote rather than letting the neat grid imply equivalence.
What Goes in Each Column
| Column | Alignment | What to put here |
|---|---|---|
| Conceptual Type | left :--- | A language-neutral name with its constraint attached: “Integer (64-bit signed)”, “Decimal (exact, 2 places)”, “UTF-8 text”. Left-aligned. Split a row whenever two languages would need different types for it, such as 32-bit versus 64-bit integers. |
| TypeScript | left :--- | The compile-time type in backticks, remembering it is erased at runtime and the value is a plain JavaScript value. Flag where number stops being exact, and note that bigint cannot be passed to JSON.stringify without a custom replacer — it throws a TypeError. |
| Python 3 | left :--- | The annotation you would write, such as list[int] or dict[str, float], plus the minimum version where syntax depends on it: built-in generics need 3.9, int | None needs 3.10. CPython float is a 64-bit double, and int is arbitrary precision, so range checks are your job. |
| PostgreSQL | left :--- | The column type you would actually declare, including size or precision: INTEGER is 4 bytes, BIGINT 8, NUMERIC(12,2) exact. Prefer TEXT over VARCHAR(255); in PostgreSQL they perform identically and the 255 limit is a habit carried over from other databases. |
| Rust | left :--- | The owned type for struct fields and the borrowed form where it differs (String / &str). Integer widths are explicit, so match the database column exactly. Name the crate when the standard library has no type, as for decimals, dates and UUIDs. |
| JSON Schema | left :--- | The type keyword value, plus the format, minimum/maximum or pattern that carries the real constraint. JSON has no integer width, date or decimal types of its own, so this column is where you document how such values are encoded on the wire. |
How to Fill It In
- 1Start from the wire format, because JSON is the narrowest common denominator. Every row must be expressible in your API schema; if it is not, decide the encoding (usually a string) before choosing language types.
- 2Take ranges and precision from primary documentation — the PostgreSQL manual’s data type chapters, MDN for JavaScript numbers, the Rust reference for primitive widths — not from another cheat sheet.
- 3Check what your drivers and ORMs actually produce. node-postgres returns
BIGINTandNUMERICcolumns as strings by default to avoid precision loss; SQLAlchemy’sNumericreturnsdecimal.Decimal. The table should describe the stack you run. - 4Prove each row with a round-trip test using boundary values: 2³¹, 2⁵³ + 1,
0.1 + 0.2, an emoji, an empty array,null, and a missing field. In JavaScript,JSON.parse("9007199254740993")returns9007199254740992, which is exactly the failure this table exists to prevent. - 5Escape every union pipe before publishing: TypeScript and Python unions such as
string | nullmust be written with\|inside table cells, even inside backticks, or the row gains a column. See escaping pipes.
Ready-to-Paste Variations
Numeric widths and wire encoding
The rows that cause most production bugs, with ranges and the JSON encoding that preserves the value across every language.
| Conceptual Type | Range / precision | TypeScript | PostgreSQL | Rust | On the wire (JSON) |
| :--- | :--- | :--- | :--- | :--- | :--- |
| Integer, 32-bit signed | −2,147,483,648 to 2,147,483,647 | `number` | `INTEGER` | `i32` | number |
| Integer, 64-bit signed | about ±9.22 × 10¹⁸ | `bigint` or `string` | `BIGINT` | `i64` | string (exceeds 2⁵³ − 1) |
| Exact decimal (money) | precision you declare | `string` | `NUMERIC(12,2)` | `rust_decimal::Decimal` (crate) | string |
| Floating point | IEEE-754 binary64 | `number` | `DOUBLE PRECISION` | `f64` | number |Null versus absent
Separates “present but null” from “not sent at all”, which TypeScript and JSON Schema distinguish and SQL does not. Note the escaped pipes in the union types.
| Situation | TypeScript | Python 3.11+ | PostgreSQL | Rust | JSON Schema |
| :--- | :--- | :--- | :--- | :--- | :--- |
| Required, never null | `string` | `str` | `TEXT NOT NULL` | `String` | `"type": "string"`, listed in `required` |
| Required, may be null | `string \| null` | `str \| None` | `TEXT` (nullable) | `Option<String>` | `"type": ["string", "null"]`, listed in `required` |
| Optional field | `name?: string` | `NotRequired[str]` in a `TypedDict` | no equivalent: columns always exist | `Option<String>` with `#[serde(default)]` | omitted from `required` |Dates, identifiers and binary data
Types the original template leaves out. None of them has a native JSON representation, so the schema column carries the format.
| Conceptual Type | TypeScript | Python 3 | PostgreSQL | Rust | JSON Schema |
| :--- | :--- | :--- | :--- | :--- | :--- |
| Instant in time | `Date` or ISO 8601 `string` | `datetime` (timezone-aware) | `TIMESTAMPTZ` | `chrono::DateTime<Utc>` | `"string"`, `format: date-time` |
| Calendar date | `string` (`YYYY-MM-DD`) | `datetime.date` | `DATE` | `chrono::NaiveDate` | `"string"`, `format: date` |
| UUID | `string` | `uuid.UUID` | `UUID` | `uuid::Uuid` (crate) | `"string"`, `format: uuid` |
| Raw bytes | `Uint8Array` | `bytes` | `BYTEA` | `Vec<u8>` | `"string"`, `contentEncoding: base64` |Common Mistakes
Sending 64-bit database IDs to browsers as JSON numbers, where anything above 2⁵³ − 1 is rounded and two records can end up sharing an ID.
Serialise
BIGINTIDs as strings at the API boundary and type themstringin TypeScript; convert withBigInt()only where arithmetic is needed.Mapping money to
number,floatandDOUBLE PRECISION, then wondering why0.1 + 0.2prints0.30000000000000004.Use
NUMERICwith declared scale,decimal.Decimalin Python, a decimal crate in Rust, and a string on the wire — or store integer minor units such as cents.Writing
string | nullin a table cell, which GFM splits into an extra column even inside backticks.Escape it as
\|. Tools that generate the table from code, such as this site’s editor, should do the escaping for you.Assuming Python
intfits wherever the table says “integer”.Python happily holds 2¹⁰⁰;
i64andBIGINTdo not. Putminimumandmaximumin the JSON Schema so out-of-range values are rejected at the API, not at insert time.Listing
"null"alone as the JSON Schema for a nullable value.A schema of
"type": "null"accepts only null. Use a type array such as["string", "null"]so the value can be either.
Related Markdown Table Templates
Browse all 50 templatesGraphQL Schema Types & Query Operations
Document GraphQL Queries, Mutations, Subscription types, input parameters, return payloads, authentication requirements, and deprecations.
Relational Database Table DDL Schema
Document database tables, column names, SQL types, nullability, keys (PK/FK), default values, and column semantics.
Classification Model Confusion Matrix
Binary classification confusion matrix comparing actual vs predicted positives and negatives, with row and column totals for deriving precision and recall.
Cross-Language Data Type Mapping: Frequently Asked Questions
How should I send a 64-bit integer to JavaScript?
As a JSON string. JSON.parse converts every numeric literal to a double, so by the time typical application code receives the value the extra digits are already gone. Receive the string, keep it as a string for IDs, and convert to bigint only for values you calculate with.
Is undefined the same as null in JSON?
JSON has no undefined. JSON.stringify drops object properties whose value is undefined and writes undefined array elements as null. Decide whether your API means “not sent” or “explicitly empty”, and map those to optional versus nullable fields.
Should a key-value map be JSON or JSONB in PostgreSQL?
Use JSONB unless you must preserve the exact input text. JSONB is stored decomposed, can be indexed with GIN, and discards whitespace, key order and duplicate keys (the last value wins). JSON keeps the original text but is reparsed on every access.
Why does a Python dict with integer keys come back with string keys?
JSON object keys are always strings, so json.dumps({1: "a"}) produces {"1": "a"} and loading it returns a string key. If keys must be integers, convert on load or represent the map as a list of key-value pairs.