Skip to content
Data & AI Template6 columns · 7 sample rows· Updated

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 live
Row
1
string
str
TEXT / VARCHAR(n)
String / &str
"string"
2
number / bigint
int
INTEGER / BIGINT
i32 / i64
"integer"
3
number
float
DOUBLE PRECISION
f64
"number"
4
boolean
bool
BOOLEAN
bool
"boolean"
5
T[] / Array<T>
list[T]
T[] (ARRAY)
Vec<T>
"array"
6
Record<string, T>
dict[str, T]
JSONB / HSTORE
HashMap<String, T>
"object"
7
null | undefined
None (Optional[T])
NULL
Option<T>
["T", "null"]
Output
| Conceptual Type | TypeScript          | Python 3               | PostgreSQL            | Rust                 | JSON Schema     |
| :-------------- | :------------------ | :--------------------- | :-------------------- | :------------------- | :-------------- |
| Text String     | `string`            | `str`                  | `TEXT` / `VARCHAR(n)` | `String` / `&str`    | `"string"`      |
| Integer Number  | `number` / `bigint` | `int`                  | `INTEGER` / `BIGINT`  | `i32` / `i64`        | `"integer"`     |
| Floating Point  | `number`            | `float`                | `DOUBLE PRECISION`    | `f64`                | `"number"`      |
| Boolean Flag    | `boolean`           | `bool`                 | `BOOLEAN`             | `bool`               | `"boolean"`     |
| Ordered List    | `T[]` / `Array<T>`  | `list[T]`              | `T[]` (ARRAY)         | `Vec<T>`             | `"array"`       |
| Key-Value Map   | `Record<string, T>` | `dict[str, T]`         | `JSONB` / `HSTORE`    | `HashMap<String, T>` | `"object"`      |
| Nullable Empty  | `null \| undefined` | `None` (`Optional[T]`) | `NULL`                | `Option<T>`          | `["T", "null"]` |
7 rows × 6 columns
Rendered Preview
Conceptual TypeTypeScriptPython 3PostgreSQLRustJSON Schema
Text StringstringstrTEXT / VARCHAR(n)String / &str"string"
Integer Numbernumber / bigintintINTEGER / BIGINTi32 / i64"integer"
Floating PointnumberfloatDOUBLE PRECISIONf64"number"
Boolean FlagbooleanboolBOOLEANbool"boolean"
Ordered ListT[] / Array<T>list[T]T[] (ARRAY)Vec<T>"array"
Key-Value MapRecord<string, T>dict[str, T]JSONB / HSTOREHashMap<String, T>"object"
Nullable Emptynull | undefinedNone (Optional[T])NULLOption<T>["T", "null"]

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

ColumnAlignmentWhat to put here
Conceptual Typeleft :---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.
TypeScriptleft :---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 3left :---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.
PostgreSQLleft :---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.
Rustleft :---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 Schemaleft :---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

  1. 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.
  2. 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.
  3. 3Check what your drivers and ORMs actually produce. node-postgres returns BIGINT and NUMERIC columns as strings by default to avoid precision loss; SQLAlchemy’s Numeric returns decimal.Decimal. The table should describe the stack you run.
  4. 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") returns 9007199254740992, which is exactly the failure this table exists to prevent.
  5. 5Escape every union pipe before publishing: TypeScript and Python unions such as string | null must 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 BIGINT IDs as strings at the API boundary and type them string in TypeScript; convert with BigInt() only where arithmetic is needed.

  • Mapping money to number, float and DOUBLE PRECISION, then wondering why 0.1 + 0.2 prints 0.30000000000000004.

    Use NUMERIC with declared scale, decimal.Decimal in Python, a decimal crate in Rust, and a string on the wire — or store integer minor units such as cents.

  • Writing string | null in 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 int fits wherever the table says “integer”.

    Python happily holds 2¹⁰⁰; i64 and BIGINT do not. Put minimum and maximum in 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.

Browse all 50 templates
FAQ

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.