Section 1 of 15

1 Before you start

Before you begin, take a few minutes to check what you already know and how confident you feel. You will see the same questions again at the end of the module — this helps both you and us measure what you have learned. Click an option for every question and confidence rating, then click Next to continue.

Pre-test

Which command prints every patient id as plain text, one per line?

Pre-test

What does the -r flag do in jq?

Pre-test

On the row P-001,"Tan, Alice",138 what does cut -d, -f2 return?

Pre-test

Which tool selects the CSV column called name while respecting quoted fields?

Pre-test

In jq, what does ending a filter with @csv do?

Pre-test

In jq, what does the single-dot filter '.' do to a dense one-line JSON response?

Pre-test

How do you count how many patients are in the array with jq, without writing a loop?

Pre-test

Which csvkit tool turns a CSV file into JSON?

Pre-confidence

I can use jq to pull a field out of a JSON document.

Not at all confident
Fully confident
Pre-confidence

I can turn selected JSON fields into CSV rows with jq.

Not at all confident
Fully confident
Pre-confidence

I know why cut can give wrong results on quoted CSVs, and which tool to use instead.

Not at all confident
Fully confident
Section 2 of 15

2 Introduction

Once you step outside the world of tidy spreadsheets, you will constantly encounter two data formats: JSON and CSV.

JSON is the standard for web APIs, modern configuration files, and clinical data exchange

CSV is a classic format you might already know how to slice using command-line tools like cut and awk.

However, real-world CSVs hide a trap that can quietly give you the wrong answers. This lesson introduces two powerful solutions: jq for handling JSON, and CSV-aware tools for messy comma-separated files that cut simply cannot handle safely.

In this lesson, we will cover:

  • Reading and querying JSON with jq.
  • Pulling fields, filtering arrays, and turning JSON into CSV.
  • Why cut breaks on quoted CSV fields, and what to use instead.
  • csvkit and Miller for header-aware CSV work.
Section 3 of 15

3 JSON - The Shape Of It

JSON relies on nesting two main structural elements:

  • Objects: Key-value pairs enclosed in curly braces { }.
  • Arrays: Ordered lists enclosed in square brackets [ ].

For example, a small cohort document might wrap a list of patient objects inside one top-level object. Here is what that looks like:

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
$ cat patients.json
In JSON, objects wrap key:value pairs in { } and arrays wrap an ordered list in [ ], and these structures nest, as when an array of patient objects sits inside a single top-level cohort object.
In JSON, objects wrap key:value pairs in { } and arrays wrap an ordered list in [ ], and these structures nest, as when an array of patient objects sits inside a single top-level cohort object.

Reading a file like this by eye is fine for two patients, but it becomes miserable for two thousand. That is exactly where jq comes in handy.

Section 4 of 15

4 jq - Query JSON

jq is a command-line tool that takes a filter and applies it to your JSON data. It allows you to parse, extract, and manipulate JSON right from your terminal.

The simplest filter is a single dot (.), which means "the whole thing." Piping a dense, unformatted JSON string through jq '.' reformats the document with clean indentation and color. This is the fastest way to make a one-line API response readable.

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
$ cat patients.json | jq '.'
jq applies a filter to JSON, and the simplest filter, a single dot, selects the whole document, so piping a dense one-line response through it prints the same data back with clean indentation and colour.
jq applies a filter to JSON, and the simplest filter, a single dot, selects the whole document, so piping a dense one-line response through it prints the same data back with clean indentation and colour.
Section 4.1 of 15

4.1 Pulling out a field

To pull a specific value, name a key after the dot. You can chain dots together to go deeper into nested objects.

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
$ jq '.cohort' patients.json
In jq, a dot followed by a key name selects that field's value, and chaining dots walks the same selection deeper through each layer of a nested object.
In jq, a dot followed by a key name selects that field's value, and chaining dots walks the same selection deeper through each layer of a nested object.
Section 4.2 of 15

4.2 Walking an array with .[]

The .[] filter iterates over every element of an array, handing each one to the rest of your filter. For example, .patients[].id visits each patient in the array and pulls their ID:

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
$ jq '.patients[].id' patients.json
The .[] filter streams every element of an array into the rest of the filter, so .patients[].id applies .id to each patient object and emits one id per element.
The .[] filter streams every element of an array into the rest of the filter, so .patients[].id applies .id to each patient object and emits one id per element.
Section 4.3 of 15

4.3 Raw output with -r

By default, jq prints strings with their double quotes. This is technically correct JSON, but it is awkward if you want to feed those values into a bash loop. The -r (raw) flag strips the quotes so the output is plain text:

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
$ jq -r '.patients[].id' patients.json
The jq -r flag prints JSON string values as raw, unquoted text instead of the default double-quoted form.
The jq -r flag prints JSON string values as raw, unquoted text instead of the default double-quoted form.

Now, you have a clean list of IDs that you can easily pipe into a while read loop.

Section 4.4 of 15

4.4 Filtering with select

Just like the Linux shell, jq has its own internal pipe (|). You can pipe elements through select(CONDITION) to keep only the elements that match.

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
$ jq -r '.patients[] | select(.systolic >= 140) | .id' patients.json

Read it left to right: for each patient, keep it only if systolic is at least 140, then print its id.

In jq the pipe streams each element through select(condition), which keeps only the elements where the condition holds, so a later filter like .id produces output only for the patients whose systolic is at least 140.
In jq the pipe streams each element through select(condition), which keeps only the elements where the condition holds, so a later filter like .id produces output only for the patients whose systolic is at least 140.
Section 4.5 of 15

4.5 Turning JSON into CSV

You can act as a bridge from a JSON API straight to your standard CSV tools. Collect the fields you want into an array [ ... ] and end with the @csv filter to emit a proper CSV row:

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
$ jq -r '.patients[] | [.id, .systolic, .diastolic] | @csv' patients.json
jq reads a stream of JSON records, collects chosen fields into an array, and the @csv filter turns each array into one comma-separated row.
jq reads a stream of JSON records, collects chosen fields into an array, and the @csv filter turns each array into one comma-separated row.
Section 4.6 of 15

4.6 Counting items quickly

To count how many items are in an array without writing a loop, use the length filter:

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
$ jq '.patients | length' patients.json
The jq length filter takes a collection and hands back how many items it contains, so you get an array's count directly without writing a counting loop.
The jq length filter takes a collection and hands back how many items it contains, so you get an array's count directly without writing a counting loop.
Section 5 of 15

5 CSV - The Quoting Trap

You probably already know how to slice clean CSVs with command-line tools like cut and awk. However, real-world CSVs introduce a major complication: data fields often contain commas themselves.

To prevent these internal commas from breaking the format, the data is usually wrapped in double quotes. For example, a name field might be "Tan, Alice". This is technically one value, but standard tools like cut -d, do not understand quoting. They will blindly split the field at the inner comma.

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
$ cat cohort.csv
$ cut -d, -f2 cohort.csv

What happened? cut returned "Tan instead of Tan, Alice. Even worse, every column after the name is now off by one position. The file looks fine to the naked eye, but your answer is silently wrong. This is the single most common way command-line CSV analysis goes bad.

Wrapping a field in double quotes marks the commas inside it as data, but a delimiter-blind tool like cut -d, splits on those commas anyway, emitting extra fields and silently shifting every column after the quoted one.
Wrapping a field in double quotes marks the commas inside it as data, but a delimiter-blind tool like cut -d, splits on those commas anyway, emitting extra fields and silently shifting every column after the quoted one.
Section 6 of 15

6 csvkit - CSV-Aware Tools

The robust fix is to use tools that actually understand CSV quoting. csvkit is a popular Python toolkit (installed via pip install csvkit) designed exactly for this.

Note: As this requires pip install, you will be able to run this on the bash scratchpad

Its commands feel familiar, but they allow you to select columns by their header name and safely respect quoted fields.

  • csvcut -c name cohort.csv - pick a column by header name, not position.
  • csvcut -c id,systolic cohort.csv - several columns at once.
  • csvlook cohort.csv - render the CSV as a tidy aligned table for reading.
  • csvstat cohort.csv - per-column summary: type, min, max, mean, nulls, unique values.
$ csvcut -c name cohort.csv
name
Tan, Alice
Lim, Ben

The full name survives because csvcut parses the quotes properly.

A comma inside a quoted CSV field is part of the data, so quote-aware tools like csvcut return the whole field where naive comma-splitting tears it apart.
A comma inside a quoted CSV field is part of the data, so quote-aware tools like csvcut return the whole field where naive comma-splitting tears it apart.

Meanwhile, csvstat tells you the type and range of your data before you write any analysis:

$ csvcut -c systolic cohort.csv | csvstat
  1. "systolic"
    Type of data: Number
    Smallest value: 138
    Largest value:  152
    Mean: 145
csvstat profiles a single column's type, range, and mean so you understand the shape of your data before committing to any analysis.
csvstat profiles a single column's type, range, and mean so you understand the shape of your data before committing to any analysis.
Section 6.1 of 15

6.1 Converting in and out

csvkit is not just for reading; it is also excellent for format conversion, helping you move data between different systems.

  • in2csv: Turns an Excel sheet directly into a CSV.
  • csvjson: Turns a CSV into JSON.
$ in2csv cohort.xlsx > cohort.csv
$ csvjson cohort.csv | jq '.[0]'
{
  "id": "P-001",
  "name": "Tan, Alice",
  "systolic": 138
}
csvkit moves the same record between formats (in2csv turns an Excel sheet into CSV, csvjson turns CSV into JSON) without altering the underlying data, and each format marks field boundaries and value types in its own way, so CSV must quote a value containing a comma while JSON keeps strings quoted but leaves the number 138 bare.
csvkit moves the same record between formats (in2csv turns an Excel sheet into CSV, csvjson turns CSV into JSON) without altering the underlying data, and each format marks field boundaries and value types in its own way, so CSV must quote a value containing a comma while JSON keeps strings quoted but leaves the number 138 bare.

An alternative worth knowing is Miller (mlr). It is a single, extremely fast program that handles CSV, TSV, and JSON natively. However, Miller is beyond the scope of this tutorial.

Whether you choose csvkit or Miller, stop using cut on any CSV that might contain quoted fields.

A comma inside quotes is part of a field's value, not a column boundary, so byte/character splitters like cut corrupt quoted CSV while quote-aware tools (mlr, csvkit) parse it correctly.
A comma inside quotes is part of a field's value, not a column boundary, so byte/character splitters like cut corrupt quoted CSV while quote-aware tools (mlr, csvkit) parse it correctly.

While jq, csvkit, and Miller are perfect for fast, command-line answers. But when your work grows past basic slicing and summarizing—like complex joins, data reshaping, or real statistics—it is time to step out of the terminal and move your data into Python (pandas) or R.

Section 7 of 15

7 Check your understanding

You have reached the end of the module. Try the same questions again — your answers here, paired with your pre-test answers, are how we measure what the module taught you. Answer every question and confidence rating, then click Submit and see results to view your score.

Post-test

Which command prints every patient id as plain text, one per line?

Post-test

What does the -r flag do in jq?

Post-test

On the row P-001,"Tan, Alice",138 what does cut -d, -f2 return?

Post-test

Which tool selects the CSV column called name while respecting quoted fields?

Post-test

In jq, what does ending a filter with @csv do?

Post-test

In jq, what does the single-dot filter '.' do to a dense one-line JSON response?

Post-test

How do you count how many patients are in the array with jq, without writing a loop?

Post-test

Which csvkit tool turns a CSV file into JSON?

Post-confidence

I can use jq to pull a field out of a JSON document.

Not at all confident
Fully confident
Post-confidence

I can turn selected JSON fields into CSV rows with jq.

Not at all confident
Fully confident
Post-confidence

I know why cut can give wrong results on quoted CSVs, and which tool to use instead.

Not at all confident
Fully confident
Section 8 of 15

8 Your results

Here is how your post-test answers compare with your pre-test answers. The pre/post pairing is the most reliable way to see what this module actually taught you.

Your score

Submit the post-test to see your results.

Muddiest point

What is the one thing from this module that is still unclear to you?

Rate this module

Overall, how would you rate this module?

How likely are you to recommend this module to a peer? (0 = not at all, 10 = extremely likely)