yq 16,045 picks the input format from the file extension (or -p) and the output format from -o, so conversion is an ordinary expression with different flags. For CSV, give it an array of rows; reading CSV back turns each row into an object keyed by the header.
yq -o yaml '.books[0] | {"id": .id, "title": .title, "price": .price}' data/books.json
yq -o csv '[["id", "title", "price"]] + [.books[] | [.id, .title, .price]]' data/books.json \
> books.csv
head -n 3 books.csv
yq -p csv -o json -I 0 '.[1]' books.csv
jq -r '.books[] | [.id, .title, .price] | @csv' data/books.json | sed -n 3p
yq -o json -I 0 '.source' pipeline.yamlOutput
id: 1
title: The Quiet Harbor
price: 14.99
id,title,price
1,The Quiet Harbor,14.99
2,Patterns of the Deep Web,39.5
{"id":2,"title":"Patterns of the Deep Web","price":39.5}
3,"Salt and Saffron",24.0
{"path":"data/orders.jsonl","format":"jsonl"}The two CSV writers disagree in small ways that matter downstream: yq quotes only when needed and wrote 24.0 as 24, while jq 133,477 's @csv quotes every string and keeps the literal. Converting CSV back to JSON infers types, so a ZIP code such as 02134 can lose its leading zero. CSV Done Right deals with CSV's dialects properly.