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 thecsvmodule 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:
- Does the JSON parse?
- Are IDs and ZIP codes still strings if they need leading zeroes?
- Did repeated XML elements become arrays where your consumer expects arrays?
- Did attributes land under the prefix your code expects?
- Did empty cells become
"",null, missing keys, or something else? - 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
- JSON to CSV Converter - convert JSON to CSV and CSV back to JSON in your browser
- JSON to XML Converter - convert JSON to XML and XML back to JSON
- YAML to JSON - convert YAML and format the JSON result locally
- JSON Validator - confirm your converted JSON is valid
- JSON Viewer - inspect the converted shape before using it
- JSON Fix - repair not-quite-valid JSON before converting again
Sources
- RFC 4180 - common CSV format rules, including quoted fields
- Python csv module - standard-library CSV parsing and writing
- PapaParse - robust CSV parser for JavaScript
- fast-xml-parser - XML-to-object parser for JavaScript
- xmltodict - XML-to-dict conversion for Python
- OWASP XML External Entity Prevention Cheat Sheet - parser hardening guidance for untrusted XML
- YAML 1.2.2 specification - YAML syntax, representation model, and JSON relationship
- yaml JavaScript library - YAML parsing and stringifying for JavaScript
- PyYAML documentation - safe loading and Python YAML APIs
Last reviewed August 2026.