csv
Uma biblioteca CSV de alta qualidade. Codifique e decodifique strings CSV, com suporte a fluxos (uma linha por evento), detecção automática e compatibilidade com Excel. A string deve ser valores separados por vírgula (configurável).
Um codificador/conversor de CSV para lista em Dart
Texto original em inglês. Visite o GitHub para a versão atual.
A high-quality, best-practice CSV library for Dart, inspired by PapaParse but built with Dart idioms in mind.
Version 8 fixes an issue where using csv.decoder as a stream transformer
produced an extra layer of nesting (#77).
CsvCodec has been renamed to Csv. A deprecated CsvCodec typedef is
available for migration.Csv no longer extends dart:convert's Codec class. If you need a Codec
(e.g., for .fuse()), use asCodec().If you rely on the version 6 API, please consult doc/README-v6.md.
decoder and encoder are proper StreamTransformers — one row per stream event.; separator, \r\n line endings).The Csv class and CsvDecoder support:
,, ::, |).".\r, \n, and \r\n. The encoder allows specifying a custom lineDelimiter (defaults to \r\n).The default line delimiter is \r\n (CRLF) as specified by RFC 4180.
Be careful on Windows!
If you write the CSV string directly to a file using certain methods (like File.writeAsString without specifying strict options or redirecting stdout), Windows file handling might automatically convert \n to \r\n. Since the CSV already contains \r\n, this can result in double carriage returns: \r\r\n.
To avoid this, either:
\n as the line delimiter:final codec = Csv(lineDelimiter: '\n');
final csvString = codec.encode(data);
File.openWrite()) which writes bytes exactly as they are.import 'package:csv/csv.dart';
void main() {
final data = [
['Name', 'Age', 'City'],
['Alice', 30, 'New York'],
['Bob', 25, 'London'],
];
// Encode
final String csvString = csv.encode(data);
print(csvString);
// Decode
final List<List<dynamic>> decodedData = csv.decode(csvString);
print(decodedData);
}
The decoder and encoder are StreamTransformers that emit one row per event,
so stream.transform(csv.decoder).toList() gives you a flat List<List<dynamic>>.
import 'dart:convert';
import 'dart:io';
import 'package:csv/csv.dart';
void main() async {
final file = File('data.csv');
// Each event in the stream is a single row (List<dynamic>).
final List<List<dynamic>> rows = await file
.openRead()
.transform(utf8.decoder)
.transform(csv.decoder)
.toList();
print(rows.first); // e.g. ['Name', 'Age', 'City']
}
import 'dart:convert';
import 'dart:io';
import 'package:csv/csv.dart';
void main() async {
final input = File('input.csv');
final output = File('output.csv');
await input.openRead()
.transform(utf8.decoder)
.transform(csv.decoder)
.map((row) {
row.add('Processed');
return row;
})
.transform(csv.encoder)
.transform(utf8.encoder)
.pipe(output.openWrite());
}
Excel often requires a UTF-8 BOM and ; as a separator to open files correctly in certain locales.
import 'package:csv/csv.dart';
void main() {
final data = [
['Header1', 'Header2'],
['Value 1', 'Value 2'],
];
// Use the built-in excel codec
final String excelCsv = excel.encode(data);
// This automatically adds the BOM and uses ';' as delimiter
}
import 'package:csv/csv.dart';
void main() {
final myCodec = Csv(
fieldDelimiter: '\t',
lineDelimiter: '\n',
quoteMode: QuoteMode.strings, // Only quote strings, not numbers
escapeCharacter: '\\', // Use backslash for escaping
);
final encoded = myCodec.encode([['a', 1, true], ['b', 2.5, false]]);
// Output: "a"\t1\ttrue\n"b"\t2.5\tfalse
}
Automatically convert values that look like numbers or booleans into their respective Dart types.
[!NOTE] Enabling
dynamicTypingadds a small performance overhead (approx. 15% in benchmarks). For maximum performance on large files where you know the schema, using a manualdecoderTransformor processing rows after decoding is faster.
import 'package:csv/csv.dart';
void main() {
final input = 'Name,Age,Active\nAlice,30,true';
// With dynamic typing enabled
final codec = Csv(dynamicTyping: true);
final rows = codec.decode(input);
print(rows[0][1].runtimeType); // int (30)
print(rows[0][2].runtimeType); // bool (true)
}
You can use the encoderTransform and decoderTransform hooks to process fields based on their value, column index, or header name to for example trim text, change decimal separators or format dates.
import 'package:csv/csv.dart';
void main() {
final customCodec = Csv(
fieldDelimiter: ';',
parseHeaders: true, // Required if you want 'header' name in the transform
decoderTransform: (value, index, header) {
// Change column 3 (index 2) to uppercase
if (index == 2) return value.toUpperCase();
// Convert 'Age' column to int
if (header == 'Age') return int.tryParse(value) ?? value;
return value;
},
);
final input = 'Name;City;Age\nAlice;London;30';
final decoded = customCodec.decode(input);
print(decoded[0]['Age']); // 30 (as int)
print(decoded[0]['City']); // LONDON
}
The library automatically detects common delimiters (,, ;, \t, |). It also respects the sep=; header common in some Excel-exported CSV files.
final decoded = csv.decode('sep=;\r\nA;B;C');
// Result: [['A', 'B', 'C']]
If you want to access values by their header names, you can use either decodeWithHeaders() or the parseHeaders option on the codec. Both return CsvRow objects which behave like both a List and a Map.
import 'package:csv/csv.dart';
void main() {
final fileContents = 'id,name\n1,Alice\n2,Bob';
// Method 1: Using decodeWithHeaders (Recommended)
// This automatically sets the parseHeaders flag and returns a properly typed List<CsvRow>.
final rowsWithHeaders = csv.decodeWithHeaders(fileContents);
// Access by header name directly
print(rowsWithHeaders[0]['name']); // Alice
// Still accessible by index
print(rowsWithHeaders[0][1]); // Alice
// Method 2: Using Csv(parseHeaders: true)
// This requires casting the returned rows to CsvRow manually.
final codec = Csv(parseHeaders: true);
final dynamicRows = codec.decode(fileContents);
final row = dynamicRows[0] as CsvRow;
// Access by header name
print(row['name']); // Alice
// The first row of the file was used for headers and is not in the list.
}
asCodec() for FusingIf you need a dart:convert Codec — for example, to use .fuse() — call asCodec():
import 'dart:convert';
import 'package:csv/csv.dart';
// A simple converter that adds a column to every row.
class AddColumnConverter extends Converter<List<List<dynamic>>, List<List<dynamic>>> {
@override
List<List<dynamic>> convert(List<List<dynamic>> input) {
return input.map((row) => [...row, 'Processed']).toList();
}
}
void main() {
final processor = AddColumnConverter();
// Use asCodec() to get a dart:convert Codec, then fuse.
final codec = csv.asCodec();
final sanitizingCodec = codec.decoder.fuse(processor).fuse(codec.encoder);
final inputCsv = 'Name,Age\nAlice,30';
final outputCsv = sanitizingCodec.convert(inputCsv);
print(outputCsv);
// Output:
// Name,Age,Processed
// Alice,30,Processed
}
Controlling batch size: When the codec's decoder is used as a stream
transformer, rows from the same input chunk are emitted together in a single
batch (a List<List<dynamic>>). You can limit the maximum number of rows per
batch with maxRowsPerBatch:
// Each stream event contains at most 100 rows.
final codec = csv.asCodec(maxRowsPerBatch: 100);
// For single-row events (one row per stream event), use 1:
final singleRowCodec = csv.asCodec(maxRowsPerBatch: 1);
This library incorporates many good ideas from PapaParse, such as:
sep= headers.header: true in PapaParse).Add this to your pubspec.yaml:
dependencies:
csv: ^8.0.0
asCodec()This section explains why Csv does not extend dart:convert's Codec,
and what asCodec() provides as an escape hatch.
Dart's Codec<S, T> provides two converters:
| Component | Type |
|---|---|
decoder |
Converter<T, S> |
encoder |
Converter<S, T> |
A Converter<S, T> has two roles:
T convert(S input) — convert an entire input at once.Stream<T> bind(Stream<S>) — transform a stream, where each event is of type T.The type parameter T must serve both roles. For most codecs this is fine.
For example, utf8 is a Codec<String, List<int>>: batch conversion produces a
List<int>, and each stream event is also a List<int> (a chunk of bytes).
Concatenating chunks of bytes is natural.
For a CSV decoder, the two roles need different types:
| Role | Needs |
|---|---|
Batch (convert()) |
Returns List<List<dynamic>> — all rows at once |
| Stream (each event) | Should be List<dynamic> — one row at a time |
There is no single type T that works for both. If we use
Converter<String, List<List<dynamic>>> (as version 7 did), then:
convert("a,b\nc,d") → [['a','b'], ['c','d']] ✓ correctstream.transform(decoder).toList() → List<List<List<dynamic>>> ✗ extra nesting!Each stream event is a batch of rows, and .toList() collects those batches
into yet another list — the "extra []" reported in #77.
This is a fundamental limitation of dart:convert's type system, not a bug in
this library. Versions 3.2 through 6 worked around it by not being a Codec at
all (see commit 235d898).
Version 7 reintroduced Codec and the problem came back.
Version 8 takes a hybrid approach:
CsvDecoder and CsvEncoder are StreamTransformerBase
subclasses. Stream usage works correctly — one row per event.asCodec() returns a real Codec<List<List<dynamic>>, String> adapter for
when you need .fuse() or other Codec-specific APIs. The returned codec
wraps the same parsing/encoding logic, but follows Dart's Converter type
contract (with the inherent stream nesting trade-off).// Default: stream works correctly
final rows = await file.openRead()
.transform(utf8.decoder)
.transform(csv.decoder)
.toList();
// rows is List<List<dynamic>> ✓
// asCodec(): for fuse() and other Codec APIs
final fused = csv.asCodec().decoder.fuse(someConverter);
// asCodec() with batch size limit
final codec = csv.asCodec(maxRowsPerBatch: 100);
If you use asCodec().decoder as a stream transformer, you will get the extra
nesting — that is inherent to Dart's Converter type contract and cannot be
avoided. Rows from the same input chunk are grouped together into a single batch.
Use maxRowsPerBatch to limit the batch size, or use csv.decoder directly for
streams.