{"slug":"json-to-csv-flatten","name":"JSON to CSV: Flatten Nested Records, Keep Every Value","version":"1.0.0","updated_at":"2026-10-08T16:16:06.887Z","use_when":"Turns JSON that holds a list of records, with nested objects and arrays, into one flat CSV table that a standard parser reads, and answers with the CSV only, no code fence. Nested keys become dotted column names such as address.town, array elements become index columns such as tags.0 and tags.1, or one cell joined with a separator, or one row per element when asked to explode. Numbers are copied as the JSON token (1.10 stays 1.10, 1e5 stays 1e5, -0 stays -0), true and false stay lowercase text, null and a missing key become an empty cell, a string such as 007 or 1,250 stays exactly as written. Columns follow the order in which keys first appear, so a key that shows up only in the last record still gets its column. A field that is a string in one record and an object in another, a dotted key that collides with a nested path, a list holding something other than records and JSON that is cut off get a fixed one-line CANNOT FLATTEN answer instead of a guess. Use when asked to convert, export, flatten or tabulate JSON or an API response as CSV for a sheet, a database import or a report.","not_for":"Repairing broken JSON, converting dates or currencies, summing or sorting, Excel files, or CSV to JSON. A shape clash between records, a dotted key that collides with a nested path, non-record rows and cut-off JSON get a CANNOT FLATTEN line, not a guess. CSV rules follow RFC 4180; default line break LF.","languages":["any"],"tags":["json","csv","json-to-csv","flatten","rfc-4180","api-export","no-guessing"],"category":"data","category_url":"https://aiskills402.com/categories/data","keywords":["tabulate JSON","dotted column names","one row per element"],"faq":[{"q":"Why does 1.10 stay 1.10 and 1e5 stay 1e5?","a":"They are copied as the JSON token, character for character. RFC 8259 defines a number as text with an optional fraction and exponent, and nothing in it says a trailing zero or the letter e may be rewritten, so 1.10 stays 1.10, 0.50 stays 0.50, 2E3 stays 2E3 and -0 stays -0. A string such as 007 or 1,250 is not a number at all: it is written as it is, with quotes only where the comma needs them. true and false stay lowercase words, and null or a missing key is an empty cell. A sheet or database then sees the same digits the API sent."},{"q":"Which columns do I get, and in what order?","a":"Every path that appears in any record, in the order of first appearance: scan the records in order and each record's keys as written. A key that exists only in the last record still gets a column, empty above. Nested objects add a dot (address.town), array elements add an index counted from zero (tags.0, items.1.sku). The task can ask for an array joined into one cell, or for one row per element. Records of different length fill the index columns of the longest one, and a record that lacks a path gets an empty cell, so every row has as many fields as the header."},{"q":"When does it refuse instead of producing a table?","a":"When a column name would hide a difference in the data: a field that is a string in one record and an object or array in another, or a key containing a dot that collides with a nested path. It also refuses a list that contains a number or a string among the records, JSON that is cut off, and prose with no JSON, because dropping a row or inventing a value loads without an error and is wrong for ever. The refusal is one line that starts with CANNOT FLATTEN and names the key, so a program can detect it."},{"q":"Does it help Claude Sonnet?","a":"Slightly. Without the skill Sonnet got 21 of 24 flattening setups right, with it 23 of 24. It already knew dotted headers, index columns, numbers copied as written, lowercase true and false, late keys and a planted instruction left as text. It missed the refusals: a dotted key colliding with a nested path, cut-off JSON (it kept the half-read name Bo) and a 5 among the records. Haiku went from 20 to 21."}],"examples":[{"lang":"en","model":"claude-sonnet-5-5","input_excerpt":"[\n  {\"id\": \"A-1\", \"price\": 1.10, \"qty\": 1e5, \"delta\": -0, \"big\": 12345678901234567890, \"code\": \"007\"},\n  {\"id\": \"A-2\", \"price\": 0.50, \"qty\": 2E3, \"delta\": -0.0, \"big\": 100, \"code\": \"0012\"}\n]","output_excerpt":"id,price,qty,delta,big,code\nA-1,1.10,1e5,-0,12345678901234567890,007\nA-2,0.50,2E3,-0.0,100,0012"},{"lang":"bg","model":"claude-sonnet-5-5","input_excerpt":"[{\"номер\":1,\"име\":\"Мария\",\"град\":\"София\"},{\"номер\":2,\"име\":\"Иван, младши\",\"град\":\"Пловдив\"}]","output_excerpt":"номер,име,град\n1,Мария,София\n2,\"Иван, младши\",Пловдив"},{"lang":"de","model":"claude-sonnet-5-5","input_excerpt":"[{\"Nr\":1,\"Name\":\"Anna\",\"Ort\":{\"Stadt\":\"Köln\",\"PLZ\":\"50667\"}},{\"Nr\":2,\"Name\":\"Bernd\",\"Ort\":{\"Stadt\":\"Bonn\",\"PLZ\":\"53111\"}}]","output_excerpt":"Nr,Name,Ort.Stadt,Ort.PLZ\n1,Anna,Köln,50667\n2,Bernd,Bonn,53111"},{"lang":"es","model":"claude-sonnet-5-5","input_excerpt":"[{\"id\":1,\"nombre\":\"José\",\"código\":\"007\",\"ciudad\":\"M\\u00e1laga\"},{\"id\":2,\"nombre\":\" Lucía \",\"código\":\"0012\",\"ciudad\":\"Sevilla\"}]","output_excerpt":"id,nombre,código,ciudad\n1,José,007,Málaga\n2, Lucía ,0012,Sevilla"}],"page_url":"https://aiskills402.com/skills/json-to-csv-flatten","markdown_url":"https://aiskills402.com/skills/json-to-csv-flatten.md","image_url":"https://cdn.aiskills402.com/og/skills/json-to-csv-flatten/5f14c4bf.png","related_url":"https://api.aiskills402.com/v1/skills/json-to-csv-flatten/related","purchases_count":null,"tested":{"date":"2026-10-08","strong":{"model":"claude-sonnet-5-5 (Claude Code alias \"sonnet\")","verdict":"Right on 23 of 24 setups, read by hand: it kept 1.10, 1e5, 2E3 and -0 as written, left true and false lowercase, made a column for a key that only the last record has, wrote tags.0 and tags.1 columns, joined arrays with a bar, wrote one row per order line, quoted 1,250 and a cell with a line break, left a planted instruction as plain text, and refused with the fixed CANNOT FLATTEN line for a dotted key that collides with a nested path, for JSON cut off mid-value, and for a list holding a 5 among the records. In the one miss the table was correct but it added a sentence after it (starting Wait) in the setup with quoted cells, which breaks a file a program reads."},"weak":{"model":"claude-haiku-5-5 (Claude Code alias \"haiku\")","verdict":"Right on 21 of 24 setups by the skill's own checks, but it missed three plain tables by inventing columns: a trailing empty column in the header of the setup with null and missing values, an items.1.opts.color column nobody had in the deeply nested records, and tags.2 and tags.3 columns for arrays of only two elements. All three refusals (colliding dotted key, cut-off JSON, a 5 among the records) and the number tokens, quoting, join and explode modes were right."},"note":"Twenty-four JSON setups written by us (18 flattening traps and controls in English, Bulgarian, German and Spanish; 6 of them refusals). Each answer is scored by one pattern built from the expected rows: the exact header and every cell, quotes only where a comma, quote or line break needs them, LF or CRLF, no fence and no sentence around it; a refusal must be the single CANNOT FLATTEN line. The bare side was scored with a fence removed first. Facts re-checked against RFC 4180 (fields with commas, quotes or line breaks are quoted, the same number of fields on every line) and RFC 8259 (a number is a text token; nothing says a trailing zero may be rewritten) on 2026-10-08. The dotted-path and index-column naming, the order of first appearance and the refusals are our own decisions, not facts from a standard. Two checks were widened after the run for BOTH sides: in the setups where a field is a string in one record and an object or array in another, a table that keeps both paths as separate columns (address and address.town, tags and tags.0) is also accepted, because nothing is lost in it. One run per model and setup.","baseline":{"date":"2026-10-08","rows":[{"label":"Setups flattened right (24 setups)","better":"higher","strong":{"with":{"n":23,"of":24},"without":{"n":21,"of":24}},"weak":{"with":{"n":21,"of":24},"without":{"n":20,"of":24}}}],"note":"Same request on both sides; the bare side is scored on the content, with a fence removed first. Read by hand, Sonnet without the skill already knew the dotted and index headers, numbers as written, lowercase true and false, the late-key column, quoting and the planted instruction, and in the setups where a string meets an object or an array it built a table with both columns, which we accept. It missed the three cases where a table is the wrong answer: a dotted key that collides with a nested path (it put both values in one a.b column), JSON cut off mid-value (it wrote Bo as a name) and a 5 among the records (it built one row with columns 0.id, 1 and 2.name). Haiku without the skill missed the same three, plus an extra address.town column in a plain table of numbers. Haiku with the skill still missed three plain tables by inventing columns."},"report_url":null},"price_usd":"0.03","price_micro":30000,"size_bytes":9634,"sha256":"eb226b54df90dc061c8bbe305de7ad0ec305b97c4807df3c59b17df599d2f4b5","outline":["Hard rules","Naming the columns","Modes the task can switch on","The output form","When to refuse","Work in this order","Short examples"],"license":{"summary":"Perpetual, non-exclusive; use and modify for yourself incl. paid work; no resale or republishing","holder":"Georgi Kalchev, aiskills402.com","url":"https://aiskills402.com/docs#license"},"buy_url":"https://api.aiskills402.com/v1/skills/json-to-csv-flatten/file","redownload_url_template":"https://api.aiskills402.com/v1/purchases/{token}","mcp_tool":null,"payment":{"protocol":"x402","scheme":"exact","asset":"USDC","selling":true,"network":"base","network_caip2":"eip155:8453","pay_to":"0x8e37022edcf0f21cf3c9f93fee9d4d32519f36f4","facilitator":"cdp"},"seo_title":"JSON to CSV Skill That Keeps Every Value","seo_description":"Flatten nested JSON into CSV with dotted headers, numbers as written, and a refusal when the data is cut off. Pay $0.03 once, in USDC.","versions":[{"version":"1.0.0","date":"2026-10-08","changelog":"# Changelog\n\n## 1.0.0 — 2026-10-08\n\nFirst release: turns JSON that holds a list of records into flat CSV. Nested keys become dotted columns, array elements become index columns (or one joined cell, or one row per element when the task says so), numbers are copied as their JSON token (1.10, 1e5, -0), true and false stay lowercase, null and a missing key are empty cells, columns follow the order of first appearance. A field that changes shape between records, a dotted key that collides with a nested path, a list with non-records, cut-off JSON and prose get a fixed one-line CANNOT FLATTEN answer.\n\nRules checked on 2026-10-08 against the RFC texts (read-only fetches): RFC 4180 section 2 — records separated by line breaks, the last break optional, the same number of fields on every line, spaces are part of a field, fields with line breaks, double quotes or commas are enclosed in double quotes, a quote inside is doubled. RFC 8259 section 6 — a number is minus, integer, optional fraction, optional exponent with e or E; the grammar says nothing about normalising the text, so the token is copied; section 4 — names within an object SHOULD be unique (duplicate keys are not tested here). Not from the RFCs, our own decisions: LF as the default line break, the order of first appearance, dotted paths and zero-based index columns, refusal on shape clashes and dotted-key collisions, one row kept for an empty array in explode mode.\n\nMeasured: see the test entry below.\n\n## Test 2026-10-08 (finalised)\n\n- Check widened after the run, both sides: `mixed-string-object` and `mixed-string-array` also accept a lossless table that keeps both paths as separate columns (address and address.town; tags and tags.0). The skill side still accepts the CANNOT FLATTEN line; the bare side still accepts any clear refusal. Reason: nothing is lost in such a table and the task says the header holds the path of every field. `test/control.mjs` updated (the realistic wrong answer is now a lossy table).\n- Not widened, on purpose: `non-record-rows` (skipping the 5 silently loses a value; skipping it with a note breaks \"return only the CSV\"), `dotted-key-collision` (two source paths share one column name), `truncated-json`.\n- Measured: Sonnet 23 of 24 with the skill, 21 of 24 without; Haiku 21 of 24 with, 20 of 24 without. The one Sonnet miss with the skill is a correct table followed by a stray sentence; the Haiku misses are invented columns.\n- Price: $0.03 (Sonnet gain 2).\n"}]}