TypeScript Converters 2026: End Nested JSON Nightmares
Almost every article about JSON to TypeScript conversion uses the word "securely" in the headline and then never defines it. The word is doing real work, though, because the moment you paste an API response into a converter you have made a decision about where that response is allowed to travel. The second half of the problem gets even less attention: the interfaces a converter hands back are an inference drawn from your sample, and some of that inference is wrong in ways that only surface at runtime.
"Securely" Is a Claim You Can Test
Security in this context is not a promise the tool makes about its intentions. It is a property of the network traffic the page generates, and that is observable.
A client-side converter runs entirely as JavaScript in the page you already loaded. Paste a payload, and no request carries that payload anywhere. A server-side converter posts your text to an endpoint, which means the payload transits the network, lands in a log somewhere, and is subject to whatever retention policy that operator happens to have. The two are not variations on a theme; they are different data-handling events.
The useful consequence is that you do not have to take anyone's word for it.
What Client-Side Has to Mean, Operationally
There are two checks worth running once on any converter you intend to use with real data, and together they take about ninety seconds.
First, open the browser's developer tools, switch to the Network tab, clear it, paste a sample payload, and run the conversion. On a genuinely client-side tool the request list stays empty. Not short — empty. If you see a POST carrying your text, the tool is not what its headline said.
Second, disconnect from the network entirely and run the conversion again. A client-side tool keeps working, because the parsing and interface generation are pure functions over a string. A tool that fails, or that stalls waiting for a response, was never doing the work locally. This second test is the stronger one, because it cannot be satisfied by caching.
Neither check requires reading the tool's source, though reading the source is a reasonable third step if the payload is genuinely sensitive. What matters is that the claim is falsifiable rather than rhetorical.
The Dangerous Artifact Is the Payload, Not the Tool
It helps to be precise about what is actually at risk. A JSON response captured from a staging API is usually harmless. The same response captured from production frequently contains customer names, email addresses, internal hostnames, session tokens, feature-flag identifiers that reveal unreleased work, or numeric identifiers that map directly to real accounts.
That payload is the sensitive artifact. Its structure is what you need in order to write a type definition, and its values are the part you do not want to publish. Which is why the local-only property matters most in exactly the case where it is least convenient: when you are debugging production data and the temptation is to reach for the fastest available tool.
A client-side converter also makes the payload trimming step cheap. You can replace every value with a placeholder, keep one representative object per array, and still get a complete type definition — but only if the transformation never leaves your machine, which is precisely the property you verified above.
Where the Question Marks Actually Come From
Now the part almost nobody explains. Optional fields in a generated interface are not detected from null values, and they are not detected from comments or conventions. They are detected by merging objects.
The typical implementation collects every object encountered at the same level, builds a map of key to the list of values seen for that key, and then marks a key optional when it appeared in fewer objects than the group contains. In other words, the rule is: this key is optional because at least one of the siblings I merged did not have it.
That rule has a direct and frequently surprising consequence.
One Sample Cannot Produce a Single Optional Field
If your payload contains exactly one object at some level, then every key in that object appears in exactly one object out of one, and the count matches. No key can be marked optional, no matter how obviously optional it is in reality.
Optionality only appears where objects are merged, which in practice means arrays of objects whose members have different shapes. An array of two order line items, where the second one happens to be missing a tracking number, is what produces tracking?: string. A single object with a key you know full well is sometimes absent produces tracking: string, and the converter is not being careless — it has no evidence to the contrary.
The practical implication is that sampling strategy determines the quality of the types. If you want the generated interface to reflect reality, capture a response that contains a representative variety of the objects, not the smallest possible example.
A Worked Payload, Line by Line
Here is a small shipping response that exercises all three of the traps at once.
{
"shipmentId": "SH-4471",
"carrier": "DHL",
"deliveredAt": null,
"packages": [
{ "weight": 1.2, "tracking": "1Z999", "signature": true },
{ "weight": 3.8, "tracking": "1Z998" }
],
"meta": { "retry-count": 0 }
}
Convert it with the root name Shipment and the output is:
interface Shipment {
shipmentId: string;
carrier: string;
deliveredAt: null;
packages: PackagesItem[];
meta: Meta;
}
interface PackagesItem {
weight: number;
tracking: string;
signature?: boolean;
}
interface Meta {
"retry-count": number;
}
Three details in that output deserve to be read carefully rather than skimmed.
Null Is Not Optional
deliveredAt: null is the most dangerous line in the file, and it is easy to miss because it looks correct. The value in the sample is null, so the inferred type is the literal type null, which accepts exactly one value and rejects everything else. The moment the shipment is delivered and the API returns a timestamp, that field is no longer assignable and the compiler rejects it.
Null and optional are two different concepts that a single sample cannot distinguish. A field that is present and null is not the same as a field that is absent, and neither of them is the same as a field that may sometimes hold a value. When a payload carries null, the type you almost always want is the union null | string rather than the literal null, and getting there requires either a second sample where the field is populated or a manual edit. Anything that consumes the generated type before that edit is written will compile perfectly and then fail on the first delivered shipment.
The Names and Keys You Will Have to Live With
Two more details are worth knowing before you paste the output into a file.
The nested interface is called PackagesItem, not Package. It is derived from the property name with an item suffix, which reads awkwardly for plural keys and means you should plan to rename it before anyone imports it. Type names are part of your public API once other modules reference them, so the rename is cheapest now.
The key retry-count is emitted with quotes: "retry-count": number;. That is correct, since a hyphen is not legal in an unquoted TypeScript identifier, and it saves you from a compiler error on the very first build. But it also means you cannot use dot notation to reach the value — you will be writing bracket access, and it is worth deciding whether to normalize that key in your own mapping layer instead.
Three Silent Holes in Generated Types
Beyond the specific lines above, three classes of inference are systematically weaker than they look.
The first is the empty array. An array with no elements carries no information about what it will contain, so the element type becomes unknown[]. That compiles, and it also means every read from it requires a narrowing check before you can use the result. It is the honest answer; it is not a useful one.
The second is the heterogeneous array. Given an array holding two objects with different keys, every key appears in only one of the merged objects, which means every property ends up marked optional. The interface is technically accurate and practically useless, because everything is optional and nothing is guaranteed.
The third is the null literal described above, which is really a special case of the same problem: a single sample cannot distinguish presence, absence, and nullability, and the converter has to pick one interpretation.
Auditing the Output Before You Commit It
Given all of that, the generated interface should be treated as a first draft that requires a short review, not as a finished artifact. The review is quick if you know what you are looking for.
- Find every
nullliteral type and decide whether the real type is a union. - Find every
unknown[]and replace it with the element type you actually expect. - Check whether any interface has every property optional — that usually means the source array was too heterogeneous to infer from.
- Confirm the optional fields match what you know about the API rather than what one response happened to contain.
- Rename generated type names to something you are willing to export.
That is a five-minute pass on a small payload, and it is the difference between types that document your API and types that merely compile against one captured response. The same discipline applies to deciding which fields should be optional in the first place, which is a modeling decision the converter cannot make for you.
The Rule to Remember
Use a converter you have verified sends nothing over the network, because the payload is the sensitive part and the structure is the part you need. Then treat its output as evidence rather than truth: optionality is inferred from merged objects and disappears entirely in a single-sample payload, null becomes a literal type that will break on the first populated response, and empty or uneven arrays produce types too loose to be useful.
Generating the interface is a five-second step. Auditing it against what you know about the API is the step that determines whether the types protect you or merely decorate the file — and a converter that maps nested structures to named interfaces with union types gives you a much better starting point than one that flattens everything into a single signature. Start from what a local-only converter produces, then apply the judgement the sample could not supply.
Frequently Asked Questions
How do I convert JSON to a TypeScript interface?
You can use a client-side converter tool that analyzes your JSON structure and automatically generates the corresponding TypeScript interfaces. These tools parse the JSON object's keys and value types, then create interface definitions that match your data structure exactly.
How do I generate TypeScript interfaces from nested JSON objects?
When converting nested JSON, a good TypeScript converter will recursively traverse each level of the object and create separate interfaces for nested structures. This ensures that deeply nested data is properly typed with named interfaces rather than inline anonymous types.
How do I mark TypeScript fields as optional when converting from JSON?
Most TypeScript interface generators allow you to specify optional fields by either detecting undefined or null values in your JSON sample, or by manually configuring which properties should be optional. The converter will then append the "?" operator to those property names in the generated interface.
Is it safe to use online JSON to TypeScript converters?
Online converters may pose security risks if you paste sensitive or proprietary JSON data into them, as that data could be stored or logged on external servers. Using a client-side converter that runs entirely in your browser ensures your JSON data never leaves your machine.
What is the best client-side JSON to TypeScript converter?
The best client-side converters run entirely in your browser without sending data to external servers, ensuring privacy and security. Look for tools that support nested objects, optional fields, array detection, and export to multiple TypeScript naming conventions.
How do I securely convert JSON to TypeScript interfaces?
To securely convert JSON to TypeScript interfaces, use a client-side tool that processes your data locally in the browser without transmitting it to any external server. This approach prevents sensitive data from being exposed while still generating accurate TypeScript type definitions.
How do I handle optional properties in TypeScript interfaces from JSON?
When generating TypeScript interfaces from JSON, optional properties are typically denoted with a question mark (?) after the property name. A good converter will either infer optionality from missing keys across multiple JSON samples or allow you to explicitly mark fields as optional.
Can I convert JSON to TypeScript interface without sending data to a server?
Yes, client-side JSON to TypeScript converters run entirely in your browser using JavaScript, ensuring your data remains private and secure. These tools parse your JSON locally and generate TypeScript interfaces without making any network requests to external servers.
How do I generate TypeScript types from JSON with unknown structure?
When working with JSON of unknown structure, use a converter that performs runtime type inference to detect strings, numbers, booleans, arrays, and nested objects automatically. The tool should also handle edge cases like mixed-type arrays and null values to generate accurate TypeScript interfaces.
How do I convert JSON arrays to TypeScript interfaces?
When converting JSON arrays to TypeScript interfaces, the converter should analyze the array elements and generate an interface for the array item type. For arrays with objects, it creates a named interface and outputs the type as "InterfaceName[]" in the final TypeScript definition.