TextSorter

JSON to CSV and Back: The Complete Guide to Converting Data Formats Online

· 14 min read

You have an API response sitting in your clipboard. It is 400 lines of nested JSON with objects inside arrays inside more objects. Your manager wants it in a spreadsheet by lunch. You paste it into Excel, and Excel stares back at you with one giant cell containing a wall of curly braces. Sound familiar?

Converting between JSON and CSV is one of those tasks that sounds trivially simple until you actually try to do it. Flat JSON with uniform keys? Sure, that takes about 30 seconds. But the moment you hit nested objects, mixed arrays, or inconsistent schemas, the whole thing falls apart fast. This guide walks through every angle of the problem: what makes these two formats fundamentally different, how to flatten hierarchical data into rows and columns, how to handle arrays without losing information, and how to write the conversion code yourself in JavaScript. We will also cover the reverse direction (CSV to JSON) and the real world pitfalls that trip people up constantly.

What JSON Is and Why It Took Over the Internet

JSON stands for JavaScript Object Notation. Douglas Crockford popularized it in the early 2000s as a lightweight alternative to XML, and it spread like wildfire through the web development community. Today, JSON is the default data interchange format for virtually every REST API, GraphQL endpoint, NoSQL database, and configuration system on the planet.

The appeal is straightforward. JSON is human readable (at least when it is formatted properly), trivially easy to parse in every major programming language, and flexible enough to represent almost any data structure. It supports six types: strings, numbers, booleans, null, arrays, and objects. That is it. No schemas required, no namespaces, no closing tags that have to match opening tags.

Here is a typical JSON object representing a user profile:

{
  "id": 1042,
  "name": "Sarah Chen",
  "email": "sarah@example.com",
  "active": true,
  "address": {
    "street": "742 Evergreen Terrace",
    "city": "Springfield",
    "state": "IL",
    "zip": "62704"
  },
  "roles": ["admin", "editor"],
  "lastLogin": "2026-06-28T14:32:00Z"
}

Notice how the address field contains another object nested inside. The roles field is an array with multiple values. This hierarchical structure is exactly what makes JSON so powerful for representing real world entities, and exactly what makes converting it to CSV such a headache.

JSON dominates because APIs need to send structured, typed data between systems. A REST endpoint returning a list of orders with line items, shipping addresses, and payment details maps naturally to nested JSON. Trying to represent that same data in a flat format like CSV requires serious compromises. But those compromises are worth making when the end consumer is a human being staring at a spreadsheet, which brings us to CSV.

If your JSON is malformed or minified into an unreadable blob before you even start converting, run it through the JSON Formatter first. It will validate the syntax, catch missing brackets and trailing commas, and pretty print the structure so you can actually see what you are working with.

What CSV Is and Why Spreadsheet Users Still Swear By It

CSV stands for Comma Separated Values, and it has been around since the 1970s. It predates JSON by roughly 30 years. A CSV file is just plain text where each line is a row and each value in that row is separated by a comma (or sometimes a semicolon, tab, or pipe character).

Here is the same user from above, represented as CSV:

id,name,email,active,street,city,state,zip,roles,lastLogin
1042,Sarah Chen,sarah@example.com,true,742 Evergreen Terrace,Springfield,IL,62704,"admin,editor",2026-06-28T14:32:00Z

Simple. Flat. Every piece of data sits in its own column. Any spreadsheet application on earth (Excel, Google Sheets, LibreOffice Calc, Apple Numbers) can open this file instantly and display it as a clean grid. Database import tools like MySQL’s LOAD DATA INFILE or PostgreSQL’s COPY command eat CSV for breakfast.

CSV is the language of business reporting, data analysis, and bulk data operations. When a marketing team needs to review 50,000 customer records, they do not want a JSON file. They want rows and columns they can sort, filter, and pivot. When a data analyst needs to load a dataset into pandas or R, CSV is the path of least resistance. When an accountant exports transactions from QuickBooks, they get a CSV.

The format persists because it hits a sweet spot: it is simple enough for non technical users to work with, lightweight enough for massive datasets, and universal enough that every tool supports it. The tradeoff is that CSV has zero concept of hierarchy, nesting, or data types. Everything is a string. There is no way to distinguish between the number 42, the boolean true, and the string "42" just by looking at raw CSV text. That information is lost in translation, and whether that matters depends entirely on your use case.

For quick conversions of text data into properly formatted CSV, the Text to CSV tool handles delimiter detection, quoting, and escaping automatically.

The Fundamental Structural Mismatch Between JSON and CSV

Understanding why JSON to CSV conversion is hard requires understanding one core difference: JSON is a tree and CSV is a table.

JSON data is hierarchical. Objects can contain other objects. Arrays can contain objects that contain arrays. You can nest data five, ten, twenty levels deep if you want. There is no theoretical limit.

CSV data is flat. Every record is a row. Every row has the same number of columns. Every column has a header. Period. There is no way to represent “this field contains a sub object with three properties” in a CSV cell without some kind of workaround.

Consider this JSON representing an order:

{
  "orderId": "ORD-7891",
  "customer": {
    "name": "Marcus Rivera",
    "email": "marcus@example.com"
  },
  "items": [
    {"sku": "WIDGET-A", "qty": 3, "price": 12.99},
    {"sku": "GADGET-B", "qty": 1, "price": 49.95}
  ],
  "shipping": {
    "method": "express",
    "address": {
      "line1": "100 Main St",
      "city": "Denver",
      "state": "CO"
    }
  }
}

This single order has three levels of nesting: the shipping object contains an address object, and the items array contains multiple objects. To put this into a CSV, you need to answer several hard questions:

  1. What do you call the column for the customer’s name? Is it customer_name? customer.name? Just name?
  2. The order has two line items. Does that mean two rows in the CSV? Or one row with the items somehow crammed into a single cell?
  3. The shipping address is nested two levels deep. Do you flatten shipping.address.city into a column header?

There is no single “correct” answer. The right approach depends on what you plan to do with the CSV afterward. And that is the crux of the problem.

The Flattening Problem: Turning Trees into Tables

Flattening is the process of taking a nested JSON structure and converting every value into a top level key. The most common convention is to use dot notation: if the JSON has {"address": {"city": "Denver"}}, the flattened key becomes address.city.

Here is what full flattening looks like for our order example (ignoring the array problem for now):

orderId           -> "ORD-7891"
customer.name     -> "Marcus Rivera"
customer.email    -> "marcus@example.com"
shipping.method   -> "express"
shipping.address.line1 -> "100 Main St"
shipping.address.city  -> "Denver"
shipping.address.state -> "CO"

Each of those flattened keys becomes a column header in the CSV. The values fill the cells. For simple nested objects, this works beautifully.

But flattening gets messy in practice. If you are converting a batch of JSON records and they do not all have the same keys (which is extremely common with real API data), you end up with sparse columns. Record A might have a shipping.address.line2 field for an apartment number, but records B through Z might not. Your CSV now has a column that is empty for 96% of the rows.

Another issue is key collision. What if your JSON has both {"status": "active"} at the top level AND {"order": {"status": "pending"}} nested inside? After flattening, you get status and order.status, which are distinct. But some naive flatteners might not handle this gracefully.

If your source data is messy, full of inconsistent whitespace or stray characters, run it through Clean Text before you start any conversion. Garbage in, garbage out applies doubly when you are changing formats.

Handling Arrays in JSON During Conversion

Arrays are where JSON to CSV conversion goes from “slightly annoying” to “genuinely difficult.” A JSON array inside a record does not have a natural CSV representation because a single cell can only hold one value.

There are three common strategies, each with tradeoffs:

Strategy 1: Expand Into Multiple Rows

Take each array element and create a separate row for it, duplicating all the non array fields. For our order with two items:

orderId,customer.name,item.sku,item.qty,item.price
ORD-7891,Marcus Rivera,WIDGET-A,3,12.99
ORD-7891,Marcus Rivera,GADGET-B,1,49.95

This is great for SQL analysis because each row represents one fact (one item in one order). You can group by orderId to reconstruct the original record. The downside is that your row count explodes. An order with 50 line items becomes 50 rows, all with the same customer name and order ID duplicated.

Strategy 2: Join Into a Single Cell

Concatenate the array values into a single string using a delimiter like a pipe (|) or semicolon:

orderId,customer.name,items
ORD-7891,Marcus Rivera,"WIDGET-A:3:12.99|GADGET-B:1:49.95"

This keeps one row per record, which is clean and compact. But now you have a custom encoding inside a cell that you will need to parse again later. Excel cannot sort or filter by individual item SKUs because they are all mashed together in one cell.

Strategy 3: Stringify as Raw JSON

Just dump the array as a JSON string into the cell:

orderId,customer.name,items
ORD-7891,Marcus Rivera,"[{""sku"":""WIDGET-A"",""qty"":3,""price"":12.99},{""sku"":""GADGET-B"",""qty"":1,""price"":49.95}]"

This preserves all the data with zero information loss, but it is ugly and defeats the purpose of converting to CSV in the first place. It is useful as an intermediate format when you know the CSV will be consumed by another script that can parse JSON out of individual cells.

The right choice depends on your audience. If a data analyst needs to run pivot tables, expand into rows. If you are just archiving data for later programmatic access, stringify. If you want a quick visual summary, join.

Step by Step: Manual JSON to CSV Conversion in JavaScript

Let us write a real converter. This function takes an array of JSON objects and produces a valid CSV string. It handles nested objects with dot notation flattening and joins arrays into pipe delimited strings.

function flattenObject(obj, prefix = '') {
  const result = {};

  for (const key in obj) {
    if (!obj.hasOwnProperty(key)) continue;

    const fullKey = prefix ? prefix + '.' + key : key;
    const value = obj[key];

    if (value === null || value === undefined) {
      result[fullKey] = '';
    } else if (Array.isArray(value)) {
      // Strategy: join primitive arrays, stringify object arrays
      if (value.length === 0) {
        result[fullKey] = '';
      } else if (typeof value[0] === 'object') {
        result[fullKey] = JSON.stringify(value);
      } else {
        result[fullKey] = value.join('|');
      }
    } else if (typeof value === 'object') {
      // Recurse into nested objects
      const nested = flattenObject(value, fullKey);
      Object.assign(result, nested);
    } else {
      result[fullKey] = value;
    }
  }

  return result;
}

function escapeCSVField(field) {
  const str = String(field);
  // If the field contains commas, quotes, or newlines, wrap it
  if (str.includes(',') || str.includes('"') || str.includes('\n')) {
    return '"' + str.replace(/"/g, '""') + '"';
  }
  return str;
}

function jsonArrayToCSV(jsonArray) {
  // Step 1: Flatten every record
  const flatRecords = jsonArray.map(record => flattenObject(record));

  // Step 2: Collect all unique headers across every record
  const headerSet = new Set();
  flatRecords.forEach(record => {
    Object.keys(record).forEach(key => headerSet.add(key));
  });
  const headers = Array.from(headerSet);

  // Step 3: Build the CSV string
  const headerRow = headers.map(escapeCSVField).join(',');
  const dataRows = flatRecords.map(record => {
    return headers.map(header => {
      const value = record[header] !== undefined ? record[header] : '';
      return escapeCSVField(value);
    }).join(',');
  });

  return headerRow + '\r\n' + dataRows.join('\r\n');
}

// Example usage
const users = [
  {
    id: 1,
    name: "Alice Park",
    contact: { email: "alice@example.com", phone: "555-0101" },
    tags: ["developer", "team-lead"]
  },
  {
    id: 2,
    name: "Bob Singh",
    contact: { email: "bob@example.com" },
    tags: ["designer"]
  }
];

console.log(jsonArrayToCSV(users));

Running this produces:

id,name,contact.email,contact.phone,tags
1,Alice Park,alice@example.com,555-0101,developer|team-lead
2,Bob Singh,bob@example.com,,designer

A few things to notice here. Bob does not have a phone field in the original JSON, so the cell is empty. The tags arrays are joined with pipes. The header collection step (Set) ensures that every unique key across all records becomes a column, even if only one record has that key. This is critical for real world data where schemas are inconsistent.

The escapeCSVField function handles RFC 4180 compliance: any field containing a comma, double quote, or newline gets wrapped in double quotes, and any internal double quotes get doubled.

Step by Step: CSV to JSON Conversion

Going the other direction is generally easier, but there are still gotchas. Here is a JavaScript function that parses a CSV string into an array of JSON objects:

function parseCSVLine(line) {
  const fields = [];
  let current = '';
  let inQuotes = false;

  for (let i = 0; i < line.length; i++) {
    const char = line[i];

    if (inQuotes) {
      if (char === '"') {
        // Check if next char is also a quote (escaped quote)
        if (i + 1 < line.length && line[i + 1] === '"') {
          current += '"';
          i++; // skip the next quote
        } else {
          inQuotes = false;
        }
      } else {
        current += char;
      }
    } else {
      if (char === '"') {
        inQuotes = true;
      } else if (char === ',') {
        fields.push(current);
        current = '';
      } else {
        current += char;
      }
    }
  }
  fields.push(current); // push the last field
  return fields;
}

function csvToJSON(csvString) {
  const lines = csvString.split(/\r?\n/).filter(line => line.trim() !== '');
  if (lines.length === 0) return [];

  const headers = parseCSVLine(lines[0]);
  const records = [];

  for (let i = 1; i < lines.length; i++) {
    const values = parseCSVLine(lines[i]);
    const record = {};

    headers.forEach((header, index) => {
      const value = index < values.length ? values[index] : '';

      // Optional: unflatten dot notation keys
      const keys = header.split('.');
      let target = record;
      for (let k = 0; k < keys.length - 1; k++) {
        if (!target[keys[k]]) target[keys[k]] = {};
        target = target[keys[k]];
      }
      target[keys[keys.length - 1]] = value;
    });

    records.push(record);
  }

  return records;
}

// Example usage
const csv = `name,contact.email,contact.phone,active
Alice Park,alice@example.com,555-0101,true
Bob Singh,bob@example.com,,false`;

console.log(JSON.stringify(csvToJSON(csv), null, 2));

This produces:

[
  {
    "name": "Alice Park",
    "contact": {
      "email": "alice@example.com",
      "phone": "555-0101"
    },
    "active": "true"
  },
  {
    "name": "Bob Singh",
    "contact": {
      "email": "bob@example.com",
      "phone": ""
    },
    "active": "false"
  }
]

Notice that the dot notation in the CSV headers (contact.email, contact.phone) gets unflattened back into nested objects. This is a nice feature but it requires the CSV headers to follow the dot convention. Also notice that active comes through as the string "true", not the boolean true. CSV has no type information, so your code needs to handle type coercion explicitly if it matters.

The parseCSVLine function is a proper state machine parser that handles quoted fields correctly. Never try to parse CSV by splitting on commas. That approach fails the moment any field contains a comma, which in real data happens constantly.

When to Use JSON vs CSV

This is not a “one is better” situation. They solve different problems.

Use JSON when:

  • The data is hierarchical or nested (products with variants, users with addresses and order histories)
  • You need to preserve data types (booleans, numbers, nulls vs empty strings)
  • The data will be consumed by an API, a JavaScript frontend, or a mobile app
  • The schema varies between records (one user has a middle name, another does not)
  • You are storing configuration or state that a program will read back

Use CSV when:

  • The data is flat and tabular (sales reports, log entries, contact lists)
  • The consumer is a human using Excel or Google Sheets
  • You are importing data into a SQL database
  • File size matters and you want the most compact text representation
  • You need to quickly share data with non technical people who know spreadsheets

Use both when:

  • You pull data from an API (JSON), transform it, and deliver a report (CSV)
  • You receive a spreadsheet upload from a user (CSV), parse it, and store it in your database (JSON via API)
  • You export database records for business intelligence (CSV) but keep the raw data in document stores (JSON)

In most real workflows, you will bounce between both formats constantly. The conversion skill is not optional; it is something you will use every week.

Common Pitfalls: Encoding, Special Characters, and Edge Cases

Even experienced developers get bitten by CSV edge cases. Here are the ones that cause the most pain:

Character Encoding Mismatches

CSV files do not have a built in way to declare their encoding. If you write a file in UTF-8 and someone opens it on a system expecting Latin-1, accented characters turn into garbage. The letter “ü” becomes “ü” and “é” becomes “é”. Excel on Windows is especially notorious for this: when you double click a .csv file, it often defaults to the system’s local encoding rather than UTF-8.

The fix is to prepend a UTF-8 BOM (Byte Order Mark, the three bytes EF BB BF) to the beginning of the file. Excel sees this and switches to UTF-8 mode. In JavaScript:

const bom = '\uFEFF';
const csvWithBom = bom + csvString;

Newlines Inside Cell Values

A cell value can legally contain a newline character. For example, a “description” field might span multiple lines. RFC 4180 says these fields must be wrapped in double quotes:

id,description
1,"This product is excellent.
It comes in three colors."

Most parsers handle this correctly, but simple line by line splitting (csvString.split('\n')) will break because it does not know the difference between a newline that ends a row and a newline that is inside a quoted field. That is why the state machine parser shown earlier is so important.

Double Quotes Inside Quoted Fields

If a field value contains a literal double quote and the field is already wrapped in quotes, you must escape it by doubling:

title,quote
1,"He said ""hello"" to everyone"

Forgetting this escaping step is one of the most common sources of broken CSV files. A single unescaped quote throws off the parser for every subsequent row.

Inconsistent JSON Schemas

When you convert a batch of JSON records and not all records have the same keys, some columns will be empty for certain rows. This is fine, but it can confuse people who expect every cell to have a value. It also inflates the file with empty commas. If 90% of your records are missing a field, consider dropping that column entirely or splitting the data into separate files.

Large Numbers and Scientific Notation

Excel has a nasty habit of converting large numbers into scientific notation. The value 12345678901234 becomes 1.23457E+13 when Excel opens the CSV. Even worse, it silently truncates the precision. If your JSON contains large IDs (like Snowflake IDs or credit card numbers), you need to prefix them with an equals sign and wrap them in quotes: ="12345678901234". Or better yet, tell your users to import the CSV through Excel’s Data tab with explicit column types set to Text.

Dates and Locale Traps

JSON typically stores dates as ISO 8601 strings: "2026-06-30T14:00:00Z". When Excel sees this in a CSV, it may or may not convert it to a date, depending on the system locale. Some locales expect DD/MM/YYYY and will misinterpret 06/07/2026 as July 6th instead of June 7th. If date accuracy matters, stick to the unambiguous ISO format and document the timezone.

Preprocessing Your Data Before Conversion

Real world data is messy. Before you convert anything, you should clean it up. JSON pulled from APIs sometimes contains HTML entities, escaped Unicode sequences, trailing whitespace, or invisible control characters. CSV files from old databases might have inconsistent line endings (a mix of \r\n and \n), tabs mixed with spaces, or fields with leading/trailing whitespace that throws off matching.

Use Clean Text to strip out invisible characters, normalize whitespace, and remove formatting artifacts before feeding your data into a converter. Five minutes of preprocessing can save you hours of debugging broken output.

If you are going the other direction (CSV to JSON) and your CSV was hand edited in a text editor, validate the structure first. Are all rows the same length? Are quotes properly closed? Is the delimiter consistent? These are the questions that catch problems before they cascade.

Putting It All Together

The JSON to CSV conversion pipeline, when done properly, looks like this:

  1. Validate your JSON using the JSON Formatter to catch syntax errors
  2. Clean the data with Clean Text to remove invisible characters and normalize whitespace
  3. Flatten nested objects using dot notation for column headers
  4. Decide on an array strategy: expand rows, join values, or stringify
  5. Escape all CSV fields properly (quote fields with commas, double up internal quotes)
  6. Encode the output as UTF-8 with a BOM if the file will be opened in Excel
  7. Export the result using the Text to CSV tool or download it directly

Going from CSV to JSON is the reverse: parse with a proper state machine (not string splitting), reconstruct nested objects from dot notation headers, coerce types where needed, and validate the output JSON.

Neither format is going away anytime soon. JSON will keep dominating APIs and application data, and CSV will keep dominating spreadsheets and bulk data operations. Knowing how to move data cleanly between the two is one of those skills that pays off every single week. Stop fighting your data. Format it properly, convert it cleanly, and move on to the work that actually matters.

Start by validating your JSON: Open the JSON Formatter

Frequently Asked Questions

Can I convert nested JSON to a flat CSV file?

Yes, but nested objects and arrays must be flattened first. Each nested key is typically joined with a dot separator (for example, address.city becomes a column header). Arrays can be expanded into separate rows, joined into a single cell with a delimiter like a pipe, or stringified as raw JSON. The right approach depends on whether you need one row per record or one row per array element.

Is there a free online tool to convert JSON to CSV without uploading my data to a server?

Yes. TextSorter offers browser based text and data tools that run entirely in your browser using client side JavaScript. Your data never leaves your machine. You can use the JSON Formatter to validate your JSON first, then use the Text to CSV tool to structure your output.

Why does my CSV file look broken when I open it in Excel?

The most common causes are encoding mismatches (missing UTF-8 BOM), unescaped commas or quotes inside field values, and newline characters embedded within cells. Make sure every field containing special characters is wrapped in double quotes, and that any literal double quotes inside a field are escaped by doubling them (e.g., "" instead of ").

When should I use JSON instead of CSV for storing data?

Use JSON when your data is hierarchical or deeply nested, when you need to preserve data types like booleans and nulls, or when the data will be consumed by APIs and web applications. Use CSV when you need flat, tabular data for spreadsheets, SQL database imports, or business reporting where every record has the same columns.