csv
Read CSV into objects or rows of cells, write it back with correct quoting, and handle messy files.
import "std:csv"std:csv reads and writes comma-separated values: spreadsheet exports, bank statements, database dumps. It handles the quoting (commas, quotes and newlines inside cells), so you never split lines by hand.
import "std:csv"Parse into objects#
csv.parse treats the first row as column names and returns one object per remaining row. It returns a result, since the text may be malformed.
import "std:csv"
string text = "name,city,age\nAda,London,36\nGrace,New York,85\n"
auto people = csv.parse(text).unwrap()
log people.len
for p in people (
log p.name "lives in" p.city
)Every cell is a string, because CSV has no types. Convert what you need with tryAs, which is a result too, so a bad cell doesn't crash the program:
import "std:csv"
auto rows = csv.parse("item,qty\nbolt,12\nnut,many\nwasher,7\n").unwrap()
int total = 0
for r in rows (
switch string(r.qty).tryAs<int> (
case ok(n)
total += n
case err(e)
log "bad quantity for" r.item ":" r.qty
)
)
log "total" totalTry it: add a Cy,Rome,40 row and run again.
Quoted cells#
Cells with commas, quotes or newlines are wrapped in double quotes, and a quote inside is doubled. parse unwraps them.
import "std:csv"
string text = "name,quote\n\"Smith, J\",\"He said \"\"hi\"\"\"\nLee,\"two\nlines\"\n"
for r in csv.parse(text).unwrap() (
log r.name "|" r.quote
)Try it: put a comma in a name without quotes and see what happens to the row.
Rows of cells#
Objects don't keep column order, and a header can be missing or wrong. csv.parseRows returns plain rows of strings, header included, in file order, and never complains about uneven row lengths.
import "std:csv"
auto rows = csv.parseRows("a,b,c\n1,2\n3,4,5,6\n").unwrap()
for r in rows ( log r.len r )Other delimiters#
Tab-separated and semicolon-separated files are common. Pass delimiter (one character).
import "std:csv"
auto rows = csv.parse("id;price\n1;2,50\n2;3,10\n", {delimiter: ";"}).unwrap()
for r in rows ( log r.id r.price )
log csv.parseRows("a\tb\n1\t2", {delimiter: "\t"}).unwrap()When the input is bad#
A ParseError names the line, and for quoting problems also the column. A row with more cells than the header, a repeated column name and an unclosed quote all fail. A row with fewer cells just leaves the rest empty.
import "std:csv"
string[] inputs = ["a,b\n1,2,3", "a,a\n1,2", "a,b\n\"1,2", "a,b\n1"]
for text in inputs (
switch csv.parse(text) (
case ok(rows)
log "ok" rows
case err(e)
log e.kind e.message
)
)
log csv.isValid("a,b\n1,2") csv.isValid("a,b\n\"1")Write CSV#
csv.stringify takes an array of objects (a header row, then one row each) or an array of arrays (rows as they are). It quotes cells that need it. Columns default to every key used, sorted. Pass columns to choose and order them, and header: false to leave out the header row.
import "std:csv"
auto data = [
{name: "Ann, Jr", age: 3, note: "say \"hi\""},
{name: "Bo", age: 4}
]
log csv.stringify(data)
log csv.stringify(data, {columns: ["name", "age"]})
log csv.stringify(data, {columns: ["age"], header: false})
log csv.stringify([["a", 1], ["b", null]])Missing keys and null become empty cells. The output ends with a newline, which log shows as a blank line.
Try it: change columns to ["age", "name"].
Files#
csv.parseFile and csv.parseRowsFile read a file directly. Write the output with fs.
import "std:csv"
import "std:fs"
auto people = [{name: "Ada", year: 1815}, {name: "Alan", year: 1912}]
string text = csv.stringify(people, {columns: ["name", "year"]})
fs.writeFile("people.csv", text).unwrap()
for p in csv.parseFile("people.csv").unwrap() (
log p.name p.year
)
log csv.parseFile("missing.csv").unwrapErr().kind
fs.remove("people.csv").unwrap()Group and total#
Read a sales export, total by category, and write a report:
import "std:csv"
string text = `category,item,amount
fruit,apple,3
veg,leek,2
fruit,pear,4
veg,kale,5
fruit,plum,1
`
string[number] totals = {}
for r in csv.parse(text).unwrap() (
string cat = r.category
number amount = string(r.amount).tryAs<number>.unwrapOr(0)
if totals.contains(cat) ( totals[cat] += amount ) else ( totals[cat] = amount )
)
object[] report = []
for cat in totals.getKeys().sort() (
report.append({category: cat, total: totals[cat]})
)
log csv.stringify(report, {columns: ["category", "total"]})Clean a contact list
Parse the CSV text. Keep only people whose email contains an @, and print them as name <email>, one per line, followed by how many rows were dropped.
Ada <[email protected]> Cy <[email protected]> dropped 1
What type is every cell csv.parse gives you?
Double the scores
Parse the CSV and write a new one with the columns name and double, where double is twice the score.
name,double Ada,10 Bo,14
API reference#
39 members, generated from the std:csv source of OSL 9.3.0.
Functions
csv.addColumn(object[] data, any column, any defaultValue) object[]Deprecated. Deprecated: set the key in a for loop instead.
addColumn returns copies of the objects with column set to defaultValue.
csv.aggregate(object[] data, any key, any value, any ...fn) objectDeprecated. Deprecated: group the records with a for loop instead.
aggregate groups the objects by key and calls fn with each group's values for value.
csv.appendRow(object[] data, object row) object[]Deprecated. Deprecated: use records.append(row) instead.
appendRow returns a copy of data with row added at the end.
csv.chunk(object[] data, any size) object[][]Deprecated. Deprecated: split the records with a for loop and .trim() instead.
chunk splits the objects into arrays of size (10 when size is not positive).
csv.count(object[] data, any key) string[int]Deprecated. Deprecated: count with a for loop instead.
count counts the objects for each value of key.
csv.filter(object[] data, any fn) object[]Deprecated. Deprecated: use records.filter(fn) instead.
filter keeps the objects fn returns true for.
csv.flatten(object[] data, any separator) object[]Deprecated. Deprecated: flatten the records with a for loop instead.
flatten lifts one level of nested objects into keys joined by separator ("_" when empty).
csv.fromRows(array[] rows) object[]Deprecated. Deprecated: use csv.parse(csv.stringify(rows)) or a loop instead.
fromRows turns a header row plus rows of values into objects.
csv.getColumn(object[] data, any column) arrayDeprecated. Deprecated: use records.map(row -> row[column]) instead.
getColumn lists one key's value from every object.
csv.groupBy(object[] data, any key) string[object[]]Deprecated. Deprecated: group the records with a for loop instead.
groupBy groups the objects by their value for key.
csv.isValid(string text) booleanisValid reports whether csv.parse would accept text with the default options.
csv.join(object[] data1, object[] data2, any key) object[]Deprecated. Deprecated: join the records with a for loop instead.
join pairs objects from data1 and data2 with equal values for key, with data2's keys suffixed "_2".
csv.mapRows(object[] data, any fn) object[]Deprecated. Deprecated: use records.map(fn) instead.
mapRows replaces each object with what fn returns for it.
csv.merge(object[] data1, object[] data2) object[]Deprecated. Deprecated: use data1.concat(data2) instead.
merge concatenates two arrays of objects.
csv.parse(string text, object? options) result<object[], error>parse reads CSV text whose first row names the columns and returns one object per remaining row, mapping each column name to that row's cell as a string. A row with fewer cells than columns leaves the rest as "". Objects don't keep column order; pass columns to csv.stringify to choose it. Options: delimiter (one character, default ","). Fails with ParseError, naming the line and column, for malformed quoting, a repeated column name or a row with more cells than columns.
csv.parseFile(string path, object? options) result<object[], error>parseFile reads the CSV file at path like csv.parse, with the same options. Fails with NotFoundError, PermissionError or IOError when the file can't be read and ParseError when it isn't valid CSV.
csv.parseRaw(any data) array[]Deprecated. Deprecated: use csv.parseRows(text) instead.
parseRaw reads CSV text as rows of cells, or [] when it is malformed.
csv.parseRows(string text, object? options) result<string[][], error>parseRows reads CSV text as rows of string cells, header row included, keeping every row's length and the column order. Options: delimiter (one character, default ","). Fails with ParseError, naming the line and column, for malformed quoting.
csv.parseRowsFile(string path, object? options) result<string[][], error>parseRowsFile reads the CSV file at path like csv.parseRows, with the same options. Fails with NotFoundError, PermissionError or IOError when the file can't be read and ParseError when it isn't valid CSV.
csv.pivot(object[] data, any keyColumn, any valueColumn, any ...pivotColumn) string[object]Deprecated. Deprecated: build the object with a for loop instead.
pivot builds {key: {pivot: value}} from three columns.
csv.prependRow(object[] data, object row) object[]Deprecated. Deprecated: use records.prepend(row) instead.
prependRow returns a copy of data with row added at the start.
csv.readFile(any path) object[]Deprecated. Deprecated: use csv.parseFile(path) instead.
readFile reads and parses a CSV file, or [] when it can't be read or parsed.
csv.readFileRaw(any path) array[]Deprecated. Deprecated: use csv.parseRowsFile(path) instead.
readFileRaw reads a CSV file as rows of cells, or [] when it can't be read or parsed.
csv.reduce(object[] data, any initial, any fn) anyDeprecated. Deprecated: use a for loop instead.
reduce folds the objects into one value with fn(accumulator, row).
csv.removeColumn(object[] data, any column) object[]Deprecated. Deprecated: delete the key in a for loop, or pass columns to csv.stringify, instead.
removeColumn returns copies of the objects without column.
csv.renameColumn(object[] data, any oldColumn, any newColumn) object[]Deprecated. Deprecated: rename the key in a for loop instead.
renameColumn returns copies of the objects with oldColumn renamed.
csv.sample(object[] data, any count) object[]Deprecated. Deprecated: use random.sample(records, count) instead.
sample picks count distinct objects at random.
csv.sortBy(object[] data, any key) object[]Deprecated. Deprecated: use records.sortBy(key) instead.
sortBy sorts the objects by key as text.
csv.sortByNum(object[] data, any key) object[]Deprecated. Deprecated: use records.sortBy(row -> number(row[key])) instead.
sortByNum sorts the objects by key as numbers.
csv.stats(object[] data, any key) objectDeprecated. Deprecated: use records.map(row -> number(row[key])) with .sum(), .min(), .max() and .average() instead.
stats summarises key's values as numbers: count, min, max, average and sum. With no rows, count and sum are 0 and the others are null.
csv.stringify(any data, object? options) stringstringify writes data as CSV text. data is an array of objects, written as a header row of column names followed by one row per object, or an array of arrays, written as rows of cells as they are. Cells holding null or a missing key are empty; other values are written as OSL prints them. Options: delimiter (one character, default ","); for objects, columns (the column names to write, in order; default every key used, sorted) and header (write the header row; default true). A single object is accepted as one row for old scripts. Throws TypeError when data is not one of these shapes.
csv.stringifyArray(array[] data) stringDeprecated. Deprecated: use csv.stringify(rows) instead.
stringifyArray writes rows of cells as CSV.
csv.stringifyRows(object[] data) stringDeprecated. Deprecated: use csv.stringify(records) instead.
stringifyRows writes objects as CSV, taking the columns from the first object's keys in sorted order.
csv.toRows(object[] data) array[]Deprecated. Deprecated: write the rows with records.map(...) instead.
toRows turns objects into a header row plus rows of values.
csv.transpose(array[] data) array[]Deprecated. Deprecated: transpose with for loops instead.
transpose swaps the rows and columns of an array of arrays.
csv.unique(object[] data, any key) arrayDeprecated. Deprecated: use records.map(row -> row[key]).toSet() instead.
unique lists the distinct values of key as text, in first-seen order.
csv.writeFile(any path, object data) booleanDeprecated. Deprecated: use fs.writeFile(path, csv.stringify([data])) instead.
writeFile writes one object as a two-line CSV file and reports success.
csv.writeFileArray(any path, array[] data) booleanDeprecated. Deprecated: use fs.writeFile(path, csv.stringify(rows)) instead.
writeFileArray writes rows of cells as a CSV file and reports success.
csv.writeFileRows(any path, object[] data) booleanDeprecated. Deprecated: use fs.writeFile(path, csv.stringify(records)) instead.
writeFileRows writes objects as a CSV file and reports success.