← All articles

How to Convert Between JSON, CSV, XML, and YAML

Convert JSON, CSV, XML, and YAML safely with practical rules for tabular flattening, headers, types, attributes, repeated elements, namespaces, YAML schemas, and round-trip validation.

CSV and XML both end up in JSON pipelines for the same reason: the source system is older, more spreadsheet-shaped or more enterprise-shaped than the code consuming it. A vendor sends a CSV export file. The SOAP service returns XML. RSS feed to be turned into an API response. The mechanical part is simple. Bugs arise in the data-shape choices.

The short version:

  • CSV -> JSON usually becomes an array of objects, one object per row.
  • XML -> JSON usually becomes one root object, with attributes, text nodes, and repeated elements mapped by convention.
  • CSV values start as text. Type coercion is optional and should be conservative.
  • XML has concepts JSON does not: attributes, namespaces, CDATA, comments, mixed content, and repeated element names.
  • After converting, validate the JSON and inspect the shape before handing it to another service.
  • JSON and YAML express similar trees, but YAML tags, anchors, duplicate keys, and implicit scalar typing need explicit policy.

This guide covers both ways. Decide the mapping before expecting a perfect round trip . Not all things in the source can always be expressed in the destination format .

Quick Mapping Table

Source shape Natural JSON shape Watch for
CSV header row object keys duplicate or empty headers
CSV data row one object in an array missing cells, extra cells
CSV value string, or carefully coerced scalar ZIP codes, IDs, large integers
XML root element one top-level key multiple roots are not normal XML documents
XML attribute @-prefixed key, such as @id prefix convention must be documented
XML text with attributes #text mixed content can be lossy
Repeated XML elements array one item vs many items changes shape
XML namespace prefix keep prefix in key, such as soap:Envelope stripping prefixes can create collisions

If you take only one thing away from this : * * CSV is a table ; XML is a tree . * * JSON can be either , but the mapping decisions are different .

CSV To JSON: The Row Mapping

A typical CSV has a header row followed by records:

id,name,active,zip
1,Ada,true,02139
2,Bob,false,94105

The usual JSON output is:

[
  { "id": 1, "name": "Ada", "active": true, "zip": "02139" },
  { "id": 2, "name": "Bob", "active": false, "zip": "94105" }
]

Notice the deliberate type choice. id can safely become a number here. active can become a boolean. zip should stay a string because leading zeroes matter.

That is why "convert anything that looks numeric" is dangerous. A good CSV converter will leave all values as strings or do conservative coercion.

Do Not Parse Real CSV With split(',')

This works only for toy CSV:

const [header, ...rows] = csv.trim().split('\n');
const keys = header.split(',');

It fails the moment a field contains a comma, a quote, or a newline:

id,name,note
1,Ada,"Loves compilers, math, and notes"
2,Bob,"Line one
line two"
3,Carol,"He said ""ship it"""

The field Loves compilers, math, and notes is one cell, not four. The newline inside Bob's note is part of the quoted value, not the end of the row. The doubled quote in Carol's note represents a literal quote.

Use a parser. In browser or Node projects, PapaParse is the usual production choice:

import Papa from 'papaparse';

const result = Papa.parse(csvText, {
  header: true,
  skipEmptyLines: true,
});

console.log(result.data);

For a small local tool you might still get away with a simple state machine. The thing is, it needs to keep track of whether it’s inside quotes:

function parseCsvGrid(text) {
  const rows = [];
  let row = [];
  let field = '';
  let inQuotes = false;

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

    if (inQuotes) {
      if (char === '"' && text[i + 1] === '"') {
        field += '"';
        i++;
      } else if (char === '"') {
        inQuotes = false;
      } else {
        field += char;
      }
      continue;
    }

    if (char === '"') inQuotes = true;
    else if (char === ',') { row.push(field); field = ''; }
    else if (char === '\n') { row.push(field); rows.push(row); row = []; field = ''; }
    else if (char !== '\r') field += char;
  }

  if (field !== '' || row.length) {
    row.push(field);
    rows.push(row);
  }

  return rows;
}

That is close to the dependency-free approach used by fixjson.org's CSV converter: quote-aware parsing, BOM stripping, header row to object keys, and light type coercion only when it is safe.

CSV Type Coercion: Be Conservative

CSV itself is not typed. All cells are text. JSON has strings, numbers, booleans, null, arrays and objects. The conversion step needs to decide whether to keep text as text, or coerce it.

A safe coercion policy looks like this:

CSV cell JSON value Why
true true exact boolean token
false false exact boolean token
null null exact null token
42 42 number round-trips safely
3.14 3.14 number round-trips safely
007 "007" leading zero may be meaningful
9007199254740993 "9007199254740993" too large for safe JavaScript integer precision
empty cell "" empty string is safer than guessing null

In JavaScript:

function coerceCsvCell(value) {
  if (value === '') return '';
  if (value === 'true') return true;
  if (value === 'false') return false;
  if (value === 'null') return null;

  if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(value)) {
    const number = Number(value);
    if (Number.isFinite(number) && String(number) === value) {
      return number;
    }
  }

  return value;
}

That String(number) === value check is small but important. It keeps 007 as a string and avoids pretending unsafe large integers survived as exact numbers.

CSV To JSON In Python

Python's standard library handles CSV quoting correctly:

import csv
import json

with open("customers.csv", newline="", encoding="utf-8-sig") as file:
    rows = list(csv.DictReader(file))

print(json.dumps(rows, indent=2, ensure_ascii=False))

Two details are doing useful work:

  • newline="" lets the csv module handle line endings correctly.
  • encoding="utf-8-sig" strips a UTF-8 BOM if Excel or another Windows tool added one.

csv.DictReader returns strings. If you want numbers and booleans, add a deliberate coercion pass field by field.

CSV Edge Cases That Break Imports

Duplicate Headers

This CSV is ambiguous:

id,name,name
1,Ada,Lovelace

An object cannot keep both name values under the same key. Decide whether to reject duplicate headers, rename them (name, name_2), or collect duplicates into arrays. Silent overwrite is the worst option.

Missing And Extra Cells

Rows do not always match the header:

id,name,active
1,Ada,true
2,Bob
3,Carol,false,extra

Common policies:

  • Missing cells become "".
  • Extra cells are rejected.
  • Extra cells are stored under a reserved key such as _extra.

Pick one and document it. Imports fail later when every row has a slightly different shape.

Delimiters That Are Not Commas

"CSV" often means "spreadsheet text export." It may be comma-separated, tab-separated, or semicolon-separated:

  • US-style CSV: id,name,active
  • TSV: id\tname\tactive
  • European Excel export: id;name;active

You have the wrong delimiter if it ends up being one giant column. The user can choose the delimiter, or use a parser that detects the delimiter.

JSON To CSV: Flatten a Tree Deliberately

CSV expects a single header row and a rectangular set of records. So the clean input is an array of similarly-shaped objects:

[
  { "id": "u_1", "name": "Ada", "active": true },
  { "id": "u_2", "name": "Grace", "active": false }
]
id,name,active
u_1,Ada,true
u_2,Grace,false

Choose your column set before converting. “Keys from the first row” is easy, but it misses a field that only shows up later. A union of keys keeps fields but may leave many blank cells. The explicit column list is best for a stable export contract.

Nested values need another policy:

JSON value Possible CSV representation Tradeoff
Nested object Flatten to profile.email columns Dots can collide with literal key names
Array of strings Join with a documented delimiter Delimiter may appear inside a value
Arbitrary object or array Put compact JSON in one quoted cell Preserves structure but is awkward in spreadsheets
Missing property Empty cell or documented sentinel Empty, missing, and null otherwise collapse

A robust CSV writer must quote a field that contains a comma, quote, CR, or LF and double internal quotes. Do not create rows with values.join(',').

function csvCell(value) {
  const text = value == null ? '' : String(value);
  return /[",\r\n]/.test(text) ? `"${text.replaceAll('"', '""')}"` : text;
}

Spreadsheet formula injection is a separate output risk. If untrusted cells start with =, +, -, or @, a spreadsheet may interpret them as formulas. Follow the import target's escaping guidance and do not assume normal CSV quoting disables formulas.

XML To JSON: The Tree Mapping

XML is not a table. It is an element tree with attributes and text:

<user id="1" active="true">
  <name>Ada</name>
  <role>admin</role>
  <role>editor</role>
  <note priority="high">Review access</note>
</user>

A practical JSON mapping is:

{
  "user": {
    "@id": "1",
    "@active": "true",
    "name": "Ada",
    "role": ["admin", "editor"],
    "note": {
      "@priority": "high",
      "#text": "Review access"
    }
  }
}

This is the same convention used by many XML-to-object tools:

  • attributes -> @ keys
  • element text with attributes or children -> #text
  • repeated child elements -> arrays
  • root element -> top-level JSON key

There is no universal XML-to-JSON standard. The convention matters because downstream code will depend on it.

XML Attributes vs Child Elements

XML gives you two ways to say "id":

<user id="1">
  <name>Ada</name>
</user>

and:

<user>
  <id>1</id>
  <name>Ada</name>
</user>

Those are not identical XML shapes. In JSON, keep them distinct:

{
  "user": {
    "@id": "1",
    "name": "Ada"
  }
}

versus:

{
  "user": {
    "id": "1",
    "name": "Ada"
  }
}

The @ prefix prevents an attribute named id from colliding with a child element named <id>.

The Single-Item Array Problem

This is the XML-to-JSON bug that shows up in production:

<roles>
  <role>admin</role>
</roles>

often becomes:

{ "roles": { "role": "admin" } }

but:

<roles>
  <role>admin</role>
  <role>editor</role>
</roles>

becomes:

{ "roles": { "role": ["admin", "editor"] } }

The field changes type depending on the data. If consumers expect role to always be an array, normalize after parsing:

const roles = [].concat(doc.roles?.role ?? []);

If you have a schema-backed feed, always set your XML parser to array-ify known repeatable paths. The safest default for a generic browser converter is to mirror the document shape and explain the convention.

XML Text, CDATA, And Mixed Content

For data-style XML, text is usually simple:

<title>Effective TypeScript</title>

becomes:

{ "title": "Effective TypeScript" }

If the element also has attributes, the text needs a key:

<price currency="USD">9.99</price>

becomes:

{ "price": { "@currency": "USD", "#text": "9.99" } }

CDATA is text too:

<body><![CDATA[Use <strong>care</strong> here]]></body>

should become a string containing Use <strong>care</strong> here.

Mixed content is harder:

<p>Hello <strong>Ada</strong>, welcome back.</p>

Most data converters will either concatenate text, drop stray whitespace, or return a more verbose node list. If your XML is document-style markup, not data-style XML, you should expect to review the output by hand.

XML Namespaces

Namespaces have no direct JSON equivalent:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>...</soap:Body>
</soap:Envelope>

The safest generic mapping keeps the prefix:

{
  "soap:Envelope": {
    "@xmlns:soap": "http://schemas.xmlsoap.org/soap/envelope/",
    "soap:Body": "..."
  }
}

This looks a little awkward, but it is lossless. Stripping soap: may make keys prettier, but it can merge two different elements that share a local name.

XML Parser Safety

If you parse XML from untrusted sources, beware of DTDs and external entities. Server-side XML parsers have had their share of XXE-style bugs when they resolve external entities or process unexpected DTDs.

For browser-side conversion, DOMParser does not fetch arbitrary external entities like an old server parser might, but you still need to treat the output as untrusted data. For backend code:

  • disable DTDs and external entity resolution unless you explicitly need them
  • set size limits before parsing huge documents
  • reject document-style XML if your converter only supports data XML
  • use a maintained parser with safe defaults

The fixjson.org XML converter is intentionally dependency-free and best-effort for typical data XML. It skips processing instructions and DTD-like declarations, maps attributes with @, maps mixed text to #text, and decodes the common XML entities.

JSON To XML: Choose Names, Attributes, and Repeated Elements

JSON has no required root element, no required attribute names, and no required element names for array items. Conventions for all need a converter.

Using the @ and #text convention in reverse:

{
  "user": {
    "@id": "u_1",
    "name": "Ada",
    "role": ["admin", "editor"],
    "note": { "@priority": "high", "#text": "Review access" }
  }
}

becomes:

<user id="u_1">
  <name>Ada</name>
  <role>admin</role>
  <role>editor</role>
  <note priority="high">Review access</note>
</user>

The serializer must escape &, <, and > in text, plus quotes in attributes. It must also reject or encode object keys that are not valid XML names. A JSON array at the root needs a caller-supplied wrapper and item name, such as <users><user>...</user></users>.

null has no universal XML representation. Choices include an empty element, omission, literal text, or an XML Schema instance nil attribute. Pick the convention required by the receiving system. The same is true for booleans and numbers: XML text does not preserve their JSON type unless a schema or agreed mapping does.

Round-tripping may not produce the same bytes. Like data may differ in order of attributes, insignificant whitespace, namespace prefixes, CDATA vs. escaped text, and number formatting. Compare parsed data model not just raw XML string

JSON And YAML: Similar Data Model, Different Safety Rules

JSON is a subset of the YAML 1.2 data model for ordinary objects, arrays, scalars, and null, so valid JSON can often be parsed by a YAML 1.2 parser. YAML adds comments, block strings, anchors, aliases, tags, multiple documents, and more concise syntax.

user:
  name: Ada
  active: true
  roles:
    - admin
    - editor

converts naturally to:

{
  "user": {
    "name": "Ada",
    "active": true,
    "roles": ["admin", "editor"]
  }
}

Use a maintained safe loader rather than writing a YAML parser or enabling arbitrary object construction:

import YAML from 'yaml';

const value = YAML.parse(yamlText);
const jsonText = JSON.stringify(value, null, 2);
import json
import yaml

value = yaml.safe_load(yaml_text)
json_text = json.dumps(value, indent=2)

Review these cases before conversion:

  • Duplicate mapping keys may be rejected or one value may silently win, depending on the library and options.
  • Anchors and aliases can create shared references; JSON serialization expands a tree and cannot represent cycles.
  • Custom YAML tags do not have a standard JSON equivalent.
  • Comments disappear because JSON has no comments.
  • Multiple YAML documents need separate output values, an array, or an NDJSON policy.
  • YAML 1.1 and 1.2 libraries can infer scalars differently; quote ambiguous strings such as identifiers and date-like values.

If the data is JSON compatible, converting JSON to YAML is mostly a presentation choice. Do not add anchors or implicit typing, or custom tags if the file has to convert back predictably. A formatter can fix indentation, but it can not tell if a scalar should have been a string or a number.

Validate The JSON Output

After conversion, run a quick review before the result enters another system:

  1. Does the JSON parse?
  2. Are IDs and ZIP codes still strings if they need leading zeroes?
  3. Did repeated XML elements become arrays where your consumer expects arrays?
  4. Did attributes land under the prefix your code expects?
  5. Did empty cells become "", null, missing keys, or something else?
  6. Are namespaces preserved if you need to round-trip back to XML?

For one-off browser work, paste the converted output into JSON Validator or inspect it in JSON Viewer. If the source is sensitive, use local browser tools rather than uploading customer exports to a random formatter.

Convert In The Browser

fixjson.org has two relevant local tools:

  • JSON to CSV Converter - converts JSON to CSV and CSV back to JSON; CSV parsing respects quoted commas, quoted newlines, doubled quotes, BOM stripping, and conservative type coercion.
  • JSON to XML Converter - converts JSON to XML and XML back to JSON using the @ / #text / repeated-element conventions described above.

Both run in your browser. Your spreadsheet exports, API payloads, and XML feeds are not uploaded to a server.

Frequently Asked Questions

How do I convert CSV to JSON?

Use a quote-aware CSV parser to treat the first row as headers, then map each subsequent row to an object keyed by those headers. Coerce types when safe. IDs, ZIP codes and big integers often need to stay strings.

Why are all my CSV-to-JSON values strings?

CSV has no native types. Every cell starts as text. A converter may choose to coerce exact true, false, null, and lossless numbers, but ambiguous values such as 007 should remain strings.

How do I handle XML attributes when converting to JSON?

Use a convention that keeps attributes separate from child elements. The common approach is to prefix attributes with @, so <user id="1"> becomes { "user": { "@id": "1" } }.

Why does my XML-to-JSON output sometimes give an object and sometimes an array?

Because many converters use the data itself to decide shape: one <role> becomes a string or object, while two <role> elements become an array. Normalize known repeatable fields after parsing, or configure your parser to always array-ify those paths.

Should I convert XML values to numbers and booleans?

Only if your schema says those fields are numbers or booleans. XML text and attributes are strings. Blind force can corrupt IDs, codes and high precision values.

Is it safe to convert XML from untrusted users?

Use a parser that has DTDs and external entities disabled, set size limits, and treat the JSON you converted as untrusted input. XML conversion changes the format and not the business rules and does not validate business rules.

How do I convert nested JSON to CSV?

Choose an explicit flattening policy: dotted columns for known nested objects, joined values for simple arrays, or compact JSON in a quoted cell for arbitrary nested data. Without such a convention CSV cannot hold a generic JSON tree.

How should JSON null be represented in XML or CSV?

There is no universal equivalent for either format. Use blank cells or a documented sentinel in CSV . For XML, choose either omission, empty element, literal text, or nil attribute (specific to the schema). The receiving contract should decide that.

Can every YAML file convert cleanly to JSON?

No. Comments, custom tags, anchors with cycles, non-string mapping keys and multiple documents have no direct JSON equivalents. Safe loader, disallow duplicate keys, infer scalar types on the fly and review them

Convert, Validate, And Format

Sources

Last reviewed August 2026.