The six ways at a glance
Before the details, here is the whole field in one pass, because the fastest way to pick is to eliminate five options. Way 1, the client-side converter page, needs nothing installed and keeps the file on your machine, but you repeat the clicks for every file. Way 2, Python with only the standard library, needs Python 3 and nothing else, handles flat data perfectly, and leaves nested values as Python repr text. Way 3, Python with pandas, flattens nested objects into dot-notation columns automatically, but needs pandas installed and still needs a one-line fix for lists. Way 4, jq, is a single terminal line for flat data and fails loudly, instead of wrongly, on nested data. Way 5, a Node script, flattens nested objects and joins lists with a helper you can read in full, and fits naturally into a project. Way 6, VS Code or Excel, needs no new install if you already use the tool, and trades control for convenience.
Two questions eliminate most of the list before you read further. First, is your data flat or nested? Flat data, one level of keys per object, works with all six ways. Nested data, objects inside objects or lists inside objects, rules out the plain jq one-liner and the plain Python standard library script unless you add flattening. Second, will you do this again? A one-off conversion favors the page or the tool you already have. A weekly export favors a script, because the same command gives the same columns in the same order every time. Keep those two questions in mind and the rest of the guide becomes a lookup table.
Every claim in this guide was tested on the same sample before publication: three orders, each with an id, a nested customer object with name and city, a tags list, and a total. Flat variants of the same file were used where a method only handles flat data. When a method fails on nested data, this guide says so and shows the exact failure instead of smoothing it over, because a failure you can see is safer than a wrong table you cannot.
Way 1: the client-side converter page
The simplest local way is the converter on this page, and it counts as local even though it lives on a website. When you paste JSON or upload a file, the page parses it, flattens it, and builds the CSV text inside your browser tab, then hands you the result as a downloaded file. There is no upload step and no server waiting for your data, because the conversion code ships with the page and runs on your machine. The only button on the page that touches the network is the optional fetch-from-URL button, which loads JSON from a web address you type in. Paste or upload instead of fetching, and the network is never used.
You can verify that claim yourself in about thirty seconds. Open the converter, disconnect from the network or turn on airplane mode, then paste JSON and convert it. It still works, because nothing about the conversion needs the network. That test is worth doing once, because a privacy claim is worth checking rather than trusting, and this one passes the check.
The page is the right way for a one-off file, for a machine where you cannot install anything, and for a file you want to convert before committing to a script. Its limits are the same as any page: you repeat the clicks for every file, and a very large file will slow the tab down. It also decides some things for you, like column order and flattening behavior, through its options rather than through code you control. For a single file those limits do not matter. For a weekly export, pick a script below and keep the page as your quick check.
Way 2: Python with only the standard library
Python is the most useful local way for most people, because it is already installed on most developer machines and the standard library alone is enough. No pandas, no packages, nothing to download. The script below reads a JSON file of objects, takes the column names from the first object, and writes a CSV with the standard csv module, which handles quoting correctly by itself. Save it as json2csv.py and run it with python3 json2csv.py orders.json orders.csv.
import json, csv, sys
with open(sys.argv[1]) as f:
rows = json.load(f)
with open(sys.argv[2], 'w', newline='') as f:
writer = csv.DictWriter(f, fieldnames=rows[0].keys())
writer.writeheader()
writer.writerows(rows)Tested on the three-order sample, this script produced a header row plus three data rows, and the result shows its one honest limit in plain sight. The nested customer object came out as a Python repr string, like {'name': 'Ana', 'city': 'Lima'}, and the tags list came out as a repr list, like ['new', 'vip']. A plain DictWriter does not flatten nested values, so flat data converts perfectly and nested data converts visibly wrong, which is at least easy to spot. The fix is a short recursive function that turns nested objects into dot-notation keys, customer.name and customer.city, before writing, and that is exactly what the Node script in Way 5 does.
The csv module is doing real work for you here. It quotes cells that contain the delimiter, doubles inner quotes, and writes line endings your spreadsheet expects. Do not build CSV by joining values with commas yourself. Hand-rolled joining breaks on the first comma inside a value, and that break is silent: the file looks fine until a column shifts. The standard library version also runs the same on every machine with Python 3, which is why it is the method to reach for when a colleague needs to run your conversion too.
Way 3: Python with pandas json_normalize
If Way 2 is the honest minimum, Way 3 is the upgrade most people actually want. The pandas function json_normalize takes nested objects and turns them into dot-notation columns automatically, so customer becomes customer.name and customer.city without any helper you write yourself. The script below reads the same orders file, normalizes it, and writes a CSV. It needs pandas installed, which is one install, and then the whole job is three lines.
import json, pandas as pd
with open('orders.json') as f:
rows = json.load(f)
df = pd.json_normalize(rows)
df.to_csv('orders.csv', index=False)Tested on the same three-order sample, json_normalize produced the columns id, tags, total, customer.name, and customer.city, with three rows. That is the behavior to expect: nested objects flatten, flat fields pass through, and the flattened nested columns come after the flat ones. Lists are the remaining catch. The tags list came out as Python repr text, just as in Way 2, because json_normalize does not guess what a list means. The one-line fix is to join short lists before writing, for example df['tags'] = df['tags'].apply(lambda v: '; '.join(v) if isinstance(v, list) else v), which turns the list into a readable cell. For a list where each item should become its own row, that is a different operation, exploding, and pandas has a separate explode step for it. The rule is simple: flattening handles objects, joining handles short lists, exploding handles lists that mean rows.
Pandas is the right way when your data is nested, when you will clean or filter it before writing, and when you already work in Python. It is the wrong way when installing a package is not possible, or when the job is a single flat file that jq or the page would finish faster. The install is the only real cost, and it pays off the second time you convert.
Way 4: jq, the one-liner for flat data
If you already use the terminal, jq turns the whole job into one line, for flat data. It reads JSON, picks the keys from the first object as the header, then emits every row as a CSV line, with quoting handled by its @csv formatter. No script file, no loop, no manual quoting. The command below reads a flat orders file, id, city, and total per order, and writes orders.csv. Note the limit, stated plainly: the @csv formatter rejects nested objects and lists, so this one-liner is for flat exports only, and the nested sample from the earlier sections fails on it with an error instead of a wrong table, which is the safe kind of failure.
jq -r '(.[0] | keys_unsorted) as $keys | $keys, map([.[ $keys[] ]])[] | @csv' orders-flat.json > orders.csvTested on a flat three-order file, the output was a quoted header row, "id","city","total", followed by three data rows with string cells quoted and numbers bare, exactly the shape @csv produces. The keys_unsorted detail matters: plain keys would sort the columns alphabetically, which changes the column order from your file. If your data has nested objects, jq needs a flattening filter first, and the one-liner grows into a small script. For flat exports, which is what most API list responses already are once unwrapped, the one-liner is the whole job, and it runs offline on any machine with jq installed.
One caution: jq reads the entire file into memory, so a multi-gigabyte export will strain it just as it strains a browser tab. For ordinary exports, up to tens of megabytes, it is instant. For very large files, split the file first or use a streaming tool, and treat that as a separate problem from conversion.
Way 5: Node, for when the conversion lives inside a project
Node is the right local way when the conversion is part of a project rather than a one-off task: a build step, a data pipeline, a script the team already runs. Node ships with JSON parsing built in, so the only work is turning rows into CSV text, and the small script below does it with a dot-flattening helper so nested objects become dot-notation columns, matching the converter page behavior. Save it as json2csv.mjs and run it with node json2csv.mjs orders.json orders.csv.
import { readFileSync, writeFileSync } from 'node:fs';
const flatten = (obj, prefix = '', out = {}) => {
for (const [k, v] of Object.entries(obj)) {
const key = prefix ? prefix + '.' + k : k;
if (v && typeof v === 'object' && !Array.isArray(v)) flatten(v, key, out);
else out[key] = Array.isArray(v) ? v.join('; ') : v;
}
return out;
};
const rows = JSON.parse(readFileSync(process.argv[2], 'utf8')).map(r => flatten(r));
const cols = [...new Set(rows.flatMap(r => Object.keys(r)))];
const esc = v => v == null ? '' : /[",\n]/.test(String(v)) ? '"' + String(v).replaceAll('"', '""') + '"' : String(v);
const lines = [cols.join(','), ...rows.map(r => cols.map(c => esc(r[c])).join(','))];
writeFileSync(process.argv[3], lines.join('\n'));Tested on the same sample, the script produced the header id,customer.name,customer.city,tags,total and three rows, with the nested customer object flattened to two columns and the tags list joined with a semicolon and a space. The escaping function follows the same rule the converter page uses: quote a cell only when it contains the delimiter, a quote, or a line break, and double any inner quotes. That rule is the whole of CSV quoting, and getting it right is the difference between a file that opens cleanly and a file that silently shifts columns.
The script is deliberately short so you can read it before running it. It takes the union of all columns across rows, so a row missing a field gets an empty cell, which matches the converter page. If you need streaming for huge files, or column renaming, those are additions to this script, not replacements for it.
Way 6: the tools you already have, VS Code and Excel
Two more local ways need no new install if you already use the tools. In VS Code, an extension converts the open JSON file to CSV from the command palette, which is the fastest local path when you are already editing the file. The full walkthrough, including which extension and the exact commands, is in the VS Code guide linked below. In Excel, Power Query reads a JSON file from your disk and turns it into a table without the file ever leaving the machine: Data, Get Data, From File, From JSON, then expand the columns and load. Both are fully offline once the tool is installed, and both keep the file on your machine throughout.
These ways trade control for convenience. VS Code and Excel decide column handling for you, and their defaults are usually fine for flat data and occasionally surprising for nested data. If the output looks wrong, the diagnosis is the same as everywhere else: check whether nested values were flattened or left as JSON text, and check the delimiter on open. For a file you convert once, either way beats installing something new. For a file you convert weekly, a script you own beats both, because the script does not depend on an extension version or a dialog you must click through again.
There is one more honest note about Way 6. Excel and VS Code are local, but they are not minimal: they are large programs with their own settings, and a setting can change the result, for example a locale that expects semicolons instead of commas. If the output differs between two machines, check the tool settings before blaming the data. Scripts do not have this problem, because the script is the setting.
Side by side: which way for which situation
With all six ways tested, the choice becomes a short decision list. One-off file, nothing to install, privacy matters: the converter page. Flat file, you live in the terminal, you want one command: jq. Flat or nested file, you already use Python, you may repeat the job: Python with pandas. No packages allowed, or a colleague must run it too: Python standard library plus a flattening helper for nested data. Conversion inside a project or a build step: the Node script. Already editing the file, or already in a spreadsheet: VS Code or Excel.
Two situations deserve a direct answer because they come up constantly. Sensitive data, where the file must stay on the machine: any way on this page qualifies, but a short script you read yourself is the strongest option, because there is nothing hidden to trust. Read the script before running it on sensitive data, keep the original JSON, and prefer the page or a script over any tool that uploads the file. Weekly exports, where the same shape arrives again and again: a script, Python with pandas or Node, because the same command gives the same columns every time, and the script can be scheduled.
And two situations where no way on this page is the right answer. Several people need the same conversion with the same settings: a shared page beats five copies of a script that drift apart. A file bigger than memory: every way here strains, and the fix is splitting the file or streaming it row by row, which is a different job from conversion. Naming those limits is part of the comparison, not a gap in it.
What every way loses, and why you keep the original
Every way on this page loses the same information, because the loss is in the format change, not in the tool. Types become text: a number, a true/false value, and a null all become characters in a cell, and a spreadsheet may reinterpret them on open. Nested objects become dot-notation columns or joined cells, and lists become joined text or extra rows, depending on the way you picked. There is no way back to the original tree from the CSV alone, because the CSV does not record which columns were nested, which cells were lists, or which values were numbers.
That is why the first rule of local conversion is to keep the original JSON. Never convert in place by overwriting the source. Name the output so it sorts, for example orders-2026-10-26.csv, and keep the JSON beside it. If the CSV turns out wrong, you regenerate it from the JSON instead of debugging a file that has already lost information. This rule costs nothing and saves the one failure you cannot undo.
The second rule is to count the rows. The CSV should have one header row plus one row per object in the JSON, and a quick count catches the most common failure, which is a row finder picking the wrong list from a wrapped response. If the counts do not match, point the tool at the list itself, or unwrap the response first, before trusting any row.
Troubleshooting across all six ways
The same four problems show up in every way, and each has one fix. A single-column open means a delimiter mismatch on the opening side, not broken data: re-import and pick the delimiter the file was written with. Nested data appearing as unreadable repr or JSON text in cells means flattening was skipped: the plain Python script and the jq one-liner do not flatten, so use the pandas version, the Node script, or the page with its flattening toggle on.
Columns in a different order than the file means a tool sorted the keys: jq needs keys_unsorted, and the Node script preserves first-seen order. Rows missing entirely means the row finder picked a different list than you intended, which happens with wrapped API responses: the converter page has a row detection step, and a script needs the list passed in explicitly. File not found means the terminal is in a different folder than the file; check the path before anything else. Invalid JSON means the source file is broken near the reported position, usually a missing quote or a trailing comma; validate it and fix the source, because no converter should guess at broken input.
Related guides
- Convert JSON to CSV Locally: Keep the File on Your Machine (2026 Guide)
- JSON to CSV Converter Download: What You Should Actually Download (2026 Guide)
- How to Flatten JSON to CSV: What the Toggle Actually Does (2026 Guide)
- Why Your CSV Opens in One Column in Excel, and How to Fix It (2026 Guide)
- Why Your CSV Shows Weird Characters in Excel, and How to Fix the Encoding (2026 Guide)
- Why Excel Drops the Leading Zeros From Your CSV Codes, and How to Keep Them (2026 Guide)
- Why Excel Changes Your CSV Dates, and How to Keep Them (2026 Guide)
- Why Saving Your CSV in Excel Changes It, and How to Keep the Original Safe (2026 Guide)
- Why Excel Won't Open Your Big CSV File, and What to Do Instead (2026 Guide)
Frequently asked questions
What are ways to convert JSON to CSV locally?
Six ways: a client-side converter page that runs in your browser tab, a Python script using only the standard library, Python with pandas json_normalize, a jq one-liner in the terminal, a small Node script with a dot-flattening helper, and the tools you already have, a VS Code extension or Excel Power Query. All six keep the file on your own machine.
Which local way is best for a one-off file?
The converter page. Paste or upload the JSON, convert in the tab, download the CSV, and nothing is installed. You can verify it is local by disconnecting from the network and converting anyway: it still works, because the conversion runs in your browser.
Which local way handles nested JSON best?
Python with pandas json_normalize, the Node script with a dot-flattening helper, or the converter page with its flattening toggle on. All three turn nested objects into dot-notation columns like customer.name. The plain Python script leaves nested values as repr text, and the plain jq one-liner fails on nested data instead of producing a wrong table.
Do I need to install anything to convert JSON to CSV locally?
No, if you use the converter page or a tool you already have, like VS Code or Excel. Python 3 is already installed on most developer machines and its standard library alone handles flat data. Pandas, jq, and Node are one install each and all run fully offline once installed.
Why does my local CSV open as a single column?
That is a delimiter mismatch on the opening side, not broken data. Your spreadsheet split the file on a different delimiter than the one it was written with. Re-import the file and choose the matching delimiter, and the columns will appear.
Why do nested values show up as strange text in my CSV?
Because flattening was skipped. The plain Python standard library script writes nested objects as Python repr text, and pandas leaves lists as repr text unless you join them. Use pandas json_normalize for objects plus a join for short lists, or the Node script, or the converter page with flattening on.
Does converting locally lose any information?
Yes, the same information any conversion loses. Values are preserved but types become text, nested objects become dot-notation columns, and lists become joined cells or extra rows. There is no way back to the original tree from the CSV alone, so always keep the original JSON file.
Pick your way and convert now
Start with the fastest local way: paste any JSON into the converter on this page and watch it convert inside your browser tab, no upload, no sign-up, nothing leaves your machine. Or copy the Python, jq, or Node version and run it on your own files.
Open the JSON to CSV converter