JSON to TypeScript, Go, and Zod Type Mapping
JSON has six value kinds and no type system of its own, so every generator has to decide what each one becomes in the target language. These tables are the mapping the JSON to TypeScript, Go & Zod converter uses. They also cover the cases one sample cannot settle: an empty array, a key that was only ever null, an integer past the JavaScript safe-integer limit.
JSON value to target type
| JSON value | TypeScript | Go | Zod |
|---|---|---|---|
| "hello" | string | string | z.string() |
| 42 (integral) | number | int64 | z.number() |
| 1.5 (fractional) | number | float64 | z.number() |
| true / false | boolean | bool | z.boolean() |
| null | null | any | z.null() |
| { ... } | A named interface or type alias | A named struct | A named z.object schema |
| [1, 2, 3] | number[] | []int64 | z.array(z.number()) |
| [1, "a"] | (string | number)[] | []any | z.array(z.union([z.string(), z.number()])) |
| [] (empty in every sample) | unknown[] | []any | z.array(z.unknown()) |
| {} (no keys) | An interface with no members | A struct with no fields | z.object({}) |
| 9007199254740993 (past MAX_SAFE_INTEGER) | number, already rounded | int64, already rounded | z.number(), already rounded |
JSON.parse rounds an integer larger than Number.MAX_SAFE_INTEGER before any converter sees it, so the exact value is gone by then. Keep such ids as strings in the payload if you need them intact.
Optional versus nullable
| What the samples showed | TypeScript | Go | Zod |
|---|---|---|---|
| Key in every sample, never null | field: T | Field T `json:"field"` | z.T() |
| Key missing from some samples | field?: T | Field T `json:"field,omitempty"` | z.T().optional() |
| Key always present, sometimes null | field: T | null | Field *T `json:"field"` | z.T().nullable() |
| Key missing somewhere AND null somewhere | field?: T | null | Field *T `json:"field,omitempty"` | z.T().nullish() |
An absent key and a null value are different runtime facts: "key" in obj is false for the first and true for the second. The converter keeps them apart by default, and can collapse both into one style if your codebase prefers that.
Type alias versus interface
| Capability | interface | type |
|---|---|---|
| Describe an object shape | Yes | Yes |
| Declaration merging (two declarations combine) | Yes | No |
| Union types (A | B) | No | Yes |
| Mapped and conditional types | No | Yes |
| Combine with another shape | extends | Intersection (&) |
| Editor tooltip rendering | Shows the name | Often expands the members |
Frequently asked questions
What is the difference between an optional key and a nullable key?
An optional key may be absent from the object entirely, so a lookup returns undefined and Object.keys does not list it. A nullable key is present, with the value null. TypeScript writes the first as field?: T and the second as field: T | null. Go writes the first as omitempty in the json tag and the second as a pointer field. Zod writes .optional() and .nullable(), plus .nullish() for a key that is both.
Why did my array of objects become one type instead of two?
Because the objects are merged rather than compared. Every element contributes its keys to one shape. Keys that only some elements had are marked optional, and a key whose value differed in kind becomes a union. That is why a list of 200 records produces one named type instead of 200 near-identical ones.
Why does Go get int64 when TypeScript gets number?
JavaScript has one numeric type, so every JSON number is a number in TypeScript. Go separates integers from floating point: an integral value maps to int64, and a value with a fractional part maps to float64. If a field is integral in one sample and fractional in another, the merged type widens to float64.