Converting JSON CSV XML - SmithSoft-Studios/JAZMIN GitHub Wiki
Every JAZMIN file converts to JSON, CSV and XML and back in one call, with a filter and a column list if you want only part of it. Large files convert as streams, at any size.
| Direction | JavaScript | .NET |
|---|---|---|
| JSON → JAZMIN | fromJSON(text, target, options) |
JazminConvert.FromJson(json, settings) |
| JSON file of any size → JAZMIN | importJSONFile(path, target, options) |
JazminConvert.FromJsonFile(path, output, settings) |
| JAZMIN → JSON | toJSON(reader, { filter, select, pretty, omitNulls }) |
JazminConvert.ToJson(bytes, settings, filter) |
| CSV → JAZMIN | fromCSV(text, target, { inferTypes, delimiter }) |
JazminConvert.FromCsv(csv, settings, inferTypes) |
| CSV file of any size → JAZMIN | importCSVFile(path, target, { inferTypes, delimiter, columns }) |
JazminConvert.FromCsvFile(path, output, settings, inferTypes, delimiter, columns) |
| JAZMIN → CSV | toCSV(reader, { filter, select, delimiter }) |
JazminConvert.ToCsv(bytes, settings, filter) |
| XML → JAZMIN | fromXML(text, target, { inferTypes }) |
JazminConvert.FromXml(xml, settings) |
| XML file of any size → JAZMIN | importXMLFile(path, target, { inferTypes, columns }) |
JazminConvert.FromXmlFile(path, output, settings, inferTypes, columns) |
| JAZMIN → XML | toXML(reader, options) |
JazminConvert.ToXml(bytes, settings, filter) |
| Stream to a file | exportFile(reader, 'json' | 'csv' | 'xml', path, options) |
JsonFormat.Write, CsvFormat.Write, XmlFormat.Write
|
| Rows as a JSON stream or tokens |
JazminJsonStream, JazminJsonReader
|
|
| Nested JSON and XML |
{ shape } on toJSON, toXML, exportFile
|
JazminShape (Export Shapes) |
Pass null as the target in JavaScript to get the bytes back instead of writing a file.
import { fromCSV, fromXML, open, toCSV, toJSON, toXML } from '@smithsoft-studios/jazmin';
const csv = [
'code,name,joined,balance,note',
'0042,Ann,2024-01-02T00:00:00Z,1520.75,""', // "" is an empty string
'0107,Bob,2024-03-04T00:00:00Z,99.1,', // nothing is null
].join('\n');
const inferred = open(fromCSV(csv, null));
console.log('inferred:', inferred.columns.map((c) => `${c.name} ${c.type}`).join(', '));
console.log(inferred.get(0));
// Codes with leading zeros: turn inference off (every column a string), or declare the columns
const asText = open(fromCSV(csv, null, { inferTypes: false }));
console.log('as text: ', asText.get(0).code, typeof asText.get(0).code);
// Out again: JSON keeps exact values and ISO dates; CSV keeps null and "" apart; XML leaves nulls out
console.log(toJSON(inferred, { select: ['code', 'joined', 'note'] }));
console.log(toCSV(inferred));
console.log(toXML(inferred, { filter: { name: 'Bob' }, select: ['name', 'note'] }));
// XML in: the canonical shape <jazmin><row><column>value</column></row></jazmin>
const fromXml = open(fromXML('<jazmin><row><id>1</id><city>Cape Town</city></row><row><id>2</id></row></jazmin>', null));
console.log(fromXml.columns.map((c) => `${c.name} ${c.type}${c.nullable ? '?' : ''}`).join(', '), [...fromXml.rows()]);inferred: code int, name string, joined string, balance float, note string
{
code: 42,
name: 'Ann',
joined: '2024-01-02T00:00:00Z',
balance: 1520.75,
note: ''
}
as text: 0042 string
[{"code":42,"joined":"2024-01-02T00:00:00Z","note":""},{"code":107,"joined":"2024-03-04T00:00:00Z","note":null}]
code,name,joined,balance,note
42,Ann,2024-01-02T00:00:00Z,1520.75,""
107,Bob,2024-03-04T00:00:00Z,99.1,
<?xml version="1.0" encoding="UTF-8"?>
<jazmin>
<row><name>Bob</name></row>
</jazmin>
id int, city string? [ { id: 1, city: 'Cape Town' }, { id: 2, city: null } ]
-
JSON: large integers and decimals are written exactly, even beyond JavaScript's 2^53. Dates use ISO-8601 UTC, and
binary data is base64.
omitNulls: trueleaves null properties out;pretty: trueindents. -
CSV: an empty field means null, while
""means an empty string, so round trips keep the difference. Types are inferred unless you setinferTypes: false; turn inference off (or declarecolumns) for codes with leading zeros. -
XML:
<jazmin><row><column>value</column></row></jazmin>. Null values are left out. Column names that aren't valid XML names are written as<field name="...">. A column that first appears in a later row is nullable. -
Dates in text stay strings when types are inferred, as
joineddid above. Declare the column asdatetime(withcolumns) to store real dates; for JSON,detectDates: truealso works.
importJSONFile, importCSVFile and importXMLFile (.NET FromJsonFile, FromCsvFile, FromXmlFile) read a file a
block at a time, so its size doesn't matter. They read it twice (first to work out the column types, then to write
the rows) unless you pass columns; then every header name or element must be one of those columns, and the file is
read once.
import fs from 'node:fs';
import { exportFile, importCSVFile, open } from '@smithsoft-studios/jazmin';
const lines = ['account,at,amount'];
for (let i = 0; i < 100_000; i++) lines.push(`ACC-${i % 250},2026-01-01T00:${String(i % 60).padStart(2, '0')}:00Z,${(i % 1000) / 4}`);
fs.writeFileSync('transactions.csv', lines.join('\n'));
// Declared columns: the file is read once, and 'at' is stored as a real date (inference would leave it text)
importCSVFile('transactions.csv', 'transactions.jzm', {
columns: [
{ name: 'account', type: 'string', index: 'sorted' },
{ name: 'at', type: 'datetime' },
{ name: 'amount', type: 'float' },
],
});
const reader = open('transactions.jzm');
console.log(reader.rowCount, 'rows:', reader.columns.map((c) => `${c.name} ${c.type}`).join(', '));
console.log('CSV', Math.round(fs.statSync('transactions.csv').size / 1024), 'KB -> JAZMIN', Math.round(fs.statSync('transactions.jzm').size / 1024), 'KB');
// Out again, streamed to disk: one account, as CSV
exportFile(reader, 'csv', 'acc-7.csv', { filter: { account: 'ACC-7' } });
console.log(fs.readFileSync('acc-7.csv', 'utf8').split('\n').slice(0, 3).join(' | '));
// Back in, with the columns known: read once, each value as its column's type
importCSVFile('acc-7.csv', 'acc-7.jzm', { columns: reader.columns });
console.log(open('acc-7.jzm').rowCount, 'rows re-imported');100000 rows: account string, at datetime, amount float
CSV 3332 KB -> JAZMIN 94 KB
account,at,amount
| ACC-7,2026-01-01T00:07:00.000Z,1.75
| ACC-7,2026-01-01T00:17:00.000Z,64.25
400 rows re-imported
A 1 GB file:
| Library | CSV (10 million rows) | XML (4.6 million rows) |
|---|---|---|
| .NET | 14 s, 63 MB | 11 s, 65 MB |
Node, with --max-old-space-size=64 --max-semi-space-size=2
|
44 s, 92 MB | 48 s, 86 MB |
| Node, without those flags (garbage collected late) | 32 s, 316 MB | 41 s, 250 MB |
fromCSV and fromXML hold the text and then every row as an object: a 200 MB file took 1.6 GB (CSV) and 1.3 GB (XML)
that way. Use the import...File functions for anything large.
using Jazmin;
using Jazmin.Formats;
using Jazmin.Query;
using Jazmin.Serialization;
byte[] bytes = JazminConvert.FromCsv("""
id,name,balance
1,Ann,1520.75
2,Bob,
""");
Console.WriteLine(JazminConvert.ToJson(bytes));
Console.WriteLine(JazminConvert.ToJson(bytes, new JazminSerializerSettings { NullValueHandling = NullValueHandling.Ignore }, JazminFilter.Eq("id", 2)));
Console.WriteLine(JazminConvert.ToXml(bytes, filter: JazminFilter.Eq("id", 1)));
// Tokens, one at a time, as Newtonsoft's JsonReader gives them: no JSON text is written or parsed
using var reader = JazminReader.Open(bytes);
using var json = new JazminJsonReader(reader, JazminFilter.Eq("id", 1));
while (json.Read())
if (json.TokenType == JazminJsonToken.PropertyName) Console.Write($"{json.Path} = ");
else if (json.Value is not null) Console.WriteLine($"{json.Value} ({json.TokenType})");[{"id":1,"name":"Ann","balance":1520.75},{"id":2,"name":"Bob","balance":null}]
[{"id":2,"name":"Bob"}]
<?xml version="1.0" encoding="utf-8"?>
<jazmin>
<row>
<id>1</id>
<name>Ann</name>
<balance>1520.75</balance>
</row>
</jazmin>
[0].id = 1 (Integer)
[0].name = Ann (String)
[0].balance = 1520.75 (Float)
A query's rows as a JSON stream, without building a string: for an HTTP response or any System.Text.Json pipeline.
using var stream = new JazminJsonStream(reader, JazminFilter.Eq("account", "ACC-100001"), new() { Select = ["at", "amount"] });
await foreach (var line in JsonSerializer.DeserializeAsyncEnumerable<StatementLine>(stream)) { /* ... */ }
// ASP.NET Core: return Results.Stream(new JazminJsonStream(reader), "application/json");-
The JSON is
ToJson's: an array of row objects, the same bytes.JazminJsonStreamtakesFormattingandNullValueHandlingasToJsondoes. -
Tokens:
StartArray,StartObject,PropertyName,Integer(along),Float(adouble, or a decimal as its exact text),String,Boolean,Null,Date(a UTCDateTime),Bytes,EndObjectandEndArray; ajsoncolumn's value comes as nested tokens.DepthandPath([3].amount,[3].extra.tags[0]) are as in Newtonsoft. - Memory stays flat: the stream holds about 64 KiB of text at a time. 200,000 rows × 300 columns (1.3 GB of JSON from a 135 MB file): the stream 7.1 s at 63 MB; the token reader 4.8 s (120 million tokens) at 62 MB.
npx jazmin convert transactions.csv transactions.jzm --sorted-by account,at
npx jazmin convert transactions.jzm march.csv --filter '{"at":{"gte":"2026-03-01","lt":"2026-04-01"}}'See Command-Line Tool.