Skip to content

How to convert JSON to CSV across languages

Real snippets to export JSON to CSV across languages. Useful for reporting, handoff, and pipelines where rows and columns are still the expected format.

← Developer Zone
Updated: 2026-03-23

Use this article when...

This section is built for speed: one concrete task, multiple stacks, and the minimum context needed not to copy the wrong thing.

Use this after capturing the real payload

Work from the request or response body that actually failed instead of rebuilding JSON from memory.

Pick the language that matches the incident

The useful path here is fast adaptation: choose your stack, then copy only after reading the notes around parsing and formatting.

Validate the result with the tool

Once the snippet runs, inspect the live payload with JSON Formatter or the related tool to confirm the structure is really fixed.

What this article gives you

A short path from incident to implementation: context, language-specific snippets, and the warning signs that usually get skipped.

Languages

13

Related tools

3

Topics

json, csv, export, data, conversion

JSON is great for systems and APIs, but many operational reviews, manual exports, and spreadsheet-heavy processes still expect CSV. Conversion is useful when the goal is no longer preserving nesting, but producing a reviewable table.

The important decision here is not technical but semantic: which columns you export, how you serialize nested objects, and what happens when some rows have keys others do not.

Dependency snapshot by language

Check this first to see which snippets use built-in/runtime APIs and which ones need external packages before you start copying code.

Bash
Built-in

Built-in or runtime API

C#
External

CsvHelper.

Delphi
Built-in

Built-in or runtime API

Go
Built-in

Built-in or runtime API

Java
Check notes

Review the notes for setup details.

JavaScript
External

csv-stringify.

Kotlin
Check notes

Review the notes for setup details.

PHP
Built-in

Built-in or runtime API

Python
Built-in

Built-in or runtime API

Ruby
Built-in

Built-in or runtime API

Rust
Check notes

Review the notes for setup details.

TypeScript
External

csv-stringify.

VB.NET
Built-in

Built-in or runtime API

How to use these code examples well

The snippet is only the fast path. The explanation and notes are what keep you from copying the wrong assumption into production.

1. Start with the problem

Make sure the article really matches the issue you are debugging before copying code just because the title looks close.

2. Read the notes

That is where portability limits, dependency requirements, and runtime assumptions usually appear.

3. Validate the output

If the code runs but the bug remains, jump to the related tool and inspect the real payload, token, timestamp, or config.

Code examples by language

Snippets that compile/run, plus short explanations and real-world notes.

Bash

bash
Bash
python3 - <<'PY'
import csv
import io
import json

rows = json.loads('[{"id":1,"name":"Ana","active":true},{"id":2,"name":"Luis","active":false}]')
buffer = io.StringIO()
writer = csv.DictWriter(buffer, fieldnames=rows[0].keys())
writer.writeheader()
writer.writerows(rows)
print(buffer.getvalue(), end='')
PY
Explanation

If you need this quickly from the terminal, Python handles JSON parsing and CSV export with the standard library and without rewriting escaping logic.

Notes
  • Standard library only.
  • Assumes all rows share the same base columns.

C#

csharp
C#
using System.Globalization;
using System.Text.Json;
using CsvHelper;

static string ToCell(JsonElement value) => value.ValueKind switch
{
  JsonValueKind.String => value.GetString() ?? string.Empty,
  JsonValueKind.Number => value.GetRawText(),
  JsonValueKind.True => "true",
  JsonValueKind.False => "false",
  JsonValueKind.Null => string.Empty,
  _ => value.GetRawText()
};

var input = """
[
  {"id":1,"name":"Ana","active":true},
  {"id":2,"name":"Luis","active":false}
]
""";

var rows = JsonSerializer.Deserialize<List<Dictionary<string, JsonElement>>>(input)!;
var headers = rows[0].Keys.ToList();

using var writer = new StringWriter();
using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);

foreach (var header in headers)
{
  csv.WriteField(header);
}
csv.NextRecord();

foreach (var row in rows)
{
  foreach (var header in headers)
  {
    csv.WriteField(row.TryGetValue(header, out var value) ? ToCell(value) : string.Empty);
  }
  csv.NextRecord();
}

Console.WriteLine(writer.ToString());
Explanation

JsonElement lets you decide how each type is exported without losing control, and CsvHelper takes care of correct CSV escaping.

Notes
  • Dependency: CsvHelper.
  • For optional columns, build the union of keys before writing the header.

Delphi

delphi
Delphi
program JsonToCsv;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  System.Classes,
  System.JSON,
  System.StrUtils;

function EscapeCsv(const S: string): string;
begin
  if ContainsText(S, ',') or ContainsText(S, '"') or ContainsText(S, sLineBreak) then
    Result := '"' + StringReplace(S, '"', '""', [rfReplaceAll]) + '"'
  else
    Result := S;
end;

function JsonValueToCell(const Obj: TJSONObject; const Name: string): string;
var
  V: TJSONValue;
begin
  V := Obj.Values[Name];
  if V = nil then
    Exit('');
  if V is TJSONString then
    Exit(TJSONString(V).Value);
  Result := V.Value;
end;

var
  JsonArray: TJSONArray;
  Headers: TStringList;
  Pair: TJSONPair;
  I: Integer;
  Row: TJSONObject;

begin
  JsonArray := TJSONObject.ParseJSONValue('[{"id":1,"name":"Ana","active":true},{"id":2,"name":"Luis","active":false}]') as TJSONArray;
  Headers := TStringList.Create;
  try
    for Pair in JsonArray.Items[0] as TJSONObject do
      Headers.Add(Pair.JsonString.Value);

    Writeln(String.Join(',', Headers.ToStringArray));
    for I := 0 to JsonArray.Count - 1 do
    begin
      Row := JsonArray.Items[I] as TJSONObject;
      Writeln(
        EscapeCsv(JsonValueToCell(Row, Headers[0])) + ',' +
        EscapeCsv(JsonValueToCell(Row, Headers[1])) + ',' +
        EscapeCsv(JsonValueToCell(Row, Headers[2]))
      );
    end;
  finally
    Headers.Free;
    JsonArray.Free;
  end;
end.
Explanation

With System.JSON you can read the array, take keys from the first row, and write a simple CSV with explicit escaping.

Notes
  • Standard library only.
  • For more complex exports, move headers and escaping into reusable helpers.

Go

go
Go
package main

import (
  "encoding/csv"
  "encoding/json"
  "fmt"
  "os"
)

func main() {
  input := "[{"id":1,"name":"Ana","active":true},{"id":2,"name":"Luis","active":false}]"

  var rows []map[string]any
  if err := json.Unmarshal([]byte(input), &rows); err != nil {
    panic(err)
  }

  headers := []string{"id", "name", "active"}
  writer := csv.NewWriter(os.Stdout)
  defer writer.Flush()

  if err := writer.Write(headers); err != nil {
    panic(err)
  }

  for _, row := range rows {
    record := []string{
      fmt.Sprint(row["id"]),
      fmt.Sprint(row["name"]),
      fmt.Sprint(row["active"]),
    }
    if err := writer.Write(record); err != nil {
      panic(err)
    }
  }
}
Explanation

In Go it is usually better to define the columns you want to export explicitly. That avoids depending on map order and forces you to think about the CSV contract.

Notes
  • Standard library only.
  • If nested objects exist, convert them to JSON strings first or flatten them deliberately.

Java

java
Java
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class Main {
  public static void main(String[] args) throws Exception {
    String input = """
      [
        {"id":1,"name":"Ana","active":true},
        {"id":2,"name":"Luis","active":false}
      ]
      """;

    ObjectMapper mapper = new ObjectMapper();
    List<Map<String, Object>> rows = mapper.readValue(input, new TypeReference<List<Map<String, Object>>>() {});
    List<String> headers = new ArrayList<>(rows.get(0).keySet());

    StringWriter out = new StringWriter();
    try (CSVPrinter printer = new CSVPrinter(out, CSVFormat.DEFAULT.builder().setHeader(headers.toArray(String[]::new)).build())) {
      for (Map<String, Object> row : rows) {
        List<String> record = new ArrayList<>();
        for (String header : headers) {
          record.add(Objects.toString(row.get(header), ""));
        }
        printer.printRecord(record);
      }
    }

    System.out.println(out);
  }
}
Explanation

Jackson parses the JSON and Apache Commons CSV writes properly escaped CSV so you do not have to compose rows by hand.

Notes
  • Dependencies: jackson-databind and commons-csv.
  • Declare the column order if you want the export to stay stable across runs.

JavaScript

javascript
JavaScript
import { stringify } from 'csv-stringify/sync';

const rows = [
  { id: 1, name: 'Ana', active: true },
  { id: 2, name: 'Luis', active: false }
];

const csv = stringify(rows, {
  header: true,
  columns: ['id', 'name', 'active']
});

console.log(csv);
Explanation

csv-stringify avoids manual mistakes with quotes, commas, and line breaks. If you already export from Node, it is a very reasonable dependency.

Notes
  • Dependency: csv-stringify.
  • Define columns explicitly so the output stays stable.

Kotlin

kotlin
Kotlin
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.apache.commons.csv.CSVFormat
import org.apache.commons.csv.CSVPrinter
import java.io.StringWriter

fun main() {
  val input = """
    [
      {"id":1,"name":"Ana","active":true},
      {"id":2,"name":"Luis","active":false}
    ]
  """.trimIndent()

  val mapper = jacksonObjectMapper()
  val rows: List<Map<String, Any?>> = mapper.readValue(input, object : TypeReference<List<Map<String, Any?>>>() {})
  val headers = rows.first().keys.toList()

  val out = StringWriter()
  CSVPrinter(out, CSVFormat.DEFAULT.builder().setHeader(*headers.toTypedArray()).build()).use { printer ->
    rows.forEach { row ->
      printer.printRecord(headers.map { header -> row[header]?.toString() ?: "" })
    }
  }

  println(out.toString())
}
Explanation

Kotlin/JVM can rely on Jackson for parsing and Commons CSV to produce a stable, properly escaped export.

Notes
  • Dependencies: jackson-module-kotlin and commons-csv.
  • If nested values exist, decide up front whether to flatten or serialize them.

PHP

php
PHP
<?php
$json = '[{"id":1,"name":"Ana","active":true},{"id":2,"name":"Luis","active":false}]';
$rows = json_decode($json, true, 512, JSON_THROW_ON_ERROR);

$handle = fopen('php://temp', 'r+');
fputcsv($handle, array_keys($rows[0]));

foreach ($rows as $row) {
    fputcsv($handle, $row);
}

rewind($handle);
echo stream_get_contents($handle);
fclose($handle);
Explanation

fputcsv handles proper escaping for you. It is the simple path for backoffice exports and operational tasks in PHP.

Notes
  • Standard functions only.
  • If some rows have different keys, build the full list of columns before iterating.

Python

python
Python
import csv
import io
import json

rows = json.loads('[{"id":1,"name":"Ana","active":true},{"id":2,"name":"Luis","active":false}]')

buffer = io.StringIO()
writer = csv.DictWriter(buffer, fieldnames=rows[0].keys())
writer.writeheader()
writer.writerows(rows)

print(buffer.getvalue(), end='')
Explanation

csv.DictWriter is enough for the common homogeneous array-of-objects case and saves you from reimplementing CSV escaping.

Notes
  • Standard library only.
  • For optional columns use extrasaction or define fieldnames manually.

Ruby

ruby
Ruby
require 'json'
require 'csv'

rows = JSON.parse('[{"id":1,"name":"Ana","active":true},{"id":2,"name":"Luis","active":false}]')
headers = rows.first.keys

puts CSV.generate do |csv|
  csv << headers
  rows.each do |row|
    csv << headers.map { |header| row[header] }
  end
end
Explanation

Ruby lets you combine JSON.parse and CSV.generate with very little code. It is a clean option for small export scripts.

Notes
  • Standard library only.
  • Keeping headers separate helps preserve stable column order.

Rust

rust
Rust
use csv::Writer;
use serde_json::{Map, Value};

fn main() {
    let input = r#"[{"id":1,"name":"Ana","active":true},{"id":2,"name":"Luis","active":false}]"#;
    let rows: Vec<Map<String, Value>> = serde_json::from_str(input).unwrap();
    let headers = vec!["id", "name", "active"];

    let mut writer = Writer::from_writer(vec![]);
    writer.write_record(&headers).unwrap();

    for row in rows {
        let record: Vec<String> = headers
            .iter()
            .map(|header| row.get(*header).map(value_to_cell).unwrap_or_default())
            .collect();
        writer.write_record(record).unwrap();
    }

    print!("{}", String::from_utf8(writer.into_inner().unwrap()).unwrap());
}

fn value_to_cell(value: &Value) -> String {
    match value {
        Value::String(v) => v.clone(),
        Value::Null => String::new(),
        _ => value.to_string(),
    }
}
Explanation

The csv crate writes properly escaped output and serde_json lets you decide how each value should be serialized before export.

Notes
  • Dependencies: csv = "1", serde_json = "1".
  • Fixing headers explicitly avoids surprises with ordering.

TypeScript

ts
TypeScript
import { stringify } from 'csv-stringify/sync';

type Row = {
  id: number;
  name: string;
  active: boolean;
};

const rows: Row[] = [
  { id: 1, name: 'Ana', active: true },
  { id: 2, name: 'Luis', active: false }
];

const csv = stringify(rows, {
  header: true,
  columns: ['id', 'name', 'active']
});

console.log(csv);
Explanation

In TypeScript the added value is making the object shape explicit before exporting. That reduces mistakes when CSV becomes a contract with other teams.

Notes
  • Dependency: csv-stringify.
  • If the real object contains nested values, decide first whether to flatten them or serialize them as JSON.

VB.NET

vbnet
VB.NET
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Text.Json

Module Program
  Private Function EscapeCsv(value As String) As String
    If value.Contains(",") OrElse value.Contains(""") OrElse value.Contains(Environment.NewLine) Then
      Return """" & value.Replace("""", """""") & """"
    End If
    Return value
  End Function

  Private Function JsonToCell(value As JsonElement) As String
    Select Case value.ValueKind
      Case JsonValueKind.String
        Return value.GetString()
      Case JsonValueKind.Null
        Return String.Empty
      Case Else
        Return value.GetRawText()
    End Select
  End Function

  Sub Main()
    Dim input = "[{""id"":1,""name"":""Ana"",""active"":true},{""id"":2,""name"":""Luis"",""active"":false}]"
    Dim rows = JsonSerializer.Deserialize(Of List(Of Dictionary(Of String, JsonElement)))(input)
    Dim headers = rows(0).Keys.ToArray()

    Console.WriteLine(String.Join(",", headers))
    For Each row In rows
      Dim record = headers.Select(Function(header) EscapeCsv(If(row.ContainsKey(header), JsonToCell(row(header)), String.Empty)))
      Console.WriteLine(String.Join(",", record))
    Next
  End Sub
End Module
Explanation

In VB.NET you can parse JSON with System.Text.Json and write CSV while controlling cell escaping with a small helper function.

Notes
  • Only the .NET/VB runtime.
  • For optional or nested columns, normalize the model before exporting.

Related tools

Use the tool when you need to generate or validate output fast.

Trust and privacy

Copy/paste-friendly by design. Still, treat real production secrets carefully.

  • Prefer synthetic examples when sharing screens or recording.
  • Do not paste real API keys, tokens, or personal data unless you fully trust your environment.
  • If you need ordering, do not use UUID v4 as a sortable id: add createdAt or use sortable ids.