Groovy has an optional groovy-csv module which provides support for reading and writing
CSV (RFC 4180) data. The classes are found
in the groovy.csv package.
1. CsvSlurper
CsvSlurper parses CSV text into a list of maps, where each row becomes a map keyed
by the column headers from the first row. Values are returned as strings.
def csv = new CsvSlurper().parseText('name,age\nAlice,30\nBob,25')
assert csv.size() == 2
assert csv[0].name == 'Alice'
assert csv[0].age == '30'
assert csv[1].name == 'Bob'
Rows support dynamic property access using the header names:
def csv = new CsvSlurper().parseText('''\
name,city,country
Alice,London,UK
Bob,Paris,France'''.stripIndent())
assert csv[0].city == 'London'
assert csv[1].country == 'France'
1.1. Configuration
The separator character and quote character can be customised:
def csv = new CsvSlurper().setSeparator((char) '\t').parseText('name\tage\nAlice\t30')
assert csv[0].name == 'Alice'
assert csv[0].age == '30'
Quoted fields follow RFC 4180 — fields containing the separator, newlines, or the quote character are enclosed in quotes, with embedded quotes doubled:
def csv = new CsvSlurper().parseText('name,note\nAlice,"hello, world"\nBob,"say ""hi"""')
assert csv[0].note == 'hello, world'
assert csv[1].note == 'say "hi"'
1.2. Typed parsing
CsvSlurper can parse CSV directly into typed objects using Jackson databinding.
Standard Jackson annotations such as @JsonProperty and @JsonFormat are supported
for column name mapping and type conversion.
This is particularly useful for CSV since all values are strings — Jackson handles
the conversion to numeric, date, and other types automatically:
static class Sale {
String customer
BigDecimal amount
}
def sales = new CsvSlurper().parseAs(Sale, 'customer,amount\nAcme,1500.00\nGlobex,250.50')
assert sales.size() == 2
assert sales[0].customer == 'Acme'
assert sales[0].amount == 1500.00
assert sales[1].customer == 'Globex'
2. CsvBuilder
CsvBuilder converts collections of maps or typed objects to CSV. The keys of the first
map are used as column headers.
def data = [
[name: 'Alice', age: 30],
[name: 'Bob', age: 25]
]
def csv = CsvBuilder.toCsv(data)
assert csv.contains('name,age')
assert csv.contains('Alice,30')
assert csv.contains('Bob,25')
2.1. Typed writing
CsvBuilder can also write typed objects. Jackson annotations are supported for
column naming and formatting:
static class Product {
String name
BigDecimal price
}
def products = [new Product(name: 'Widget', price: 9.99),
new Product(name: 'Gadget', price: 24.50)]
def csv = CsvBuilder.toCsv(products, Product)
assert csv.contains('name,price')
assert csv.contains('Widget,9.99')
assert csv.contains('Gadget,24.5')
2.2. Round-trip
CSV written by CsvBuilder can be read back with CsvSlurper:
def original = [[name: 'Alice', age: '30'], [name: 'Bob', age: '25']]
def csv = CsvBuilder.toCsv(original)
def parsed = new CsvSlurper().parseText(csv)
assert parsed[0].name == 'Alice'
assert parsed[1].age == '25'