csv_plus
Fast, complete CSV parser for Dart. Encode, decode, stream, query, and validate CSV data with automatic type inference and zero dependencies.
1.3.0
5.0K downloads/mo
#3843 most downloaded on pub.dev
almasumdev/csv_plus
What this package is like to depend on
Last release today
24 Aug 2026
Release timing varies
gaps range from 2 weeks to 3 months
Nearly every release is documented
notes for 8 of 8 stable releases
Nothing withdrawn
no release was ever pulled
4 months old
8 releases · first in 2026
8 releases in the last 12 months
see the full history below
Release timeline
8 releases · Apr 2026 to Aug 2026Releases
latest 8-
1.3.024 Aug 2026Release notes
Open source →CsvConfig(parseDates: true) turns a field in ISO-8601 form into a real
DateTime during a typed decode, across decode, decodeToTable, decodeToMaps,
the streaming CsvDecoder, and bindBytes. Off by default.Every date and time field is range-checked before parsing, so an impossible
value stays text instead of becoming the wrong date: DateTime.parse rolls
2024-13-45 over to 14 February 2025, and this does not. Ambiguous locale
formats, unpunctuated runs such as 20240131, and quoted fields all stay text.Closes the only entry in the README's Limitations list. Adds a docs page at
/csv-dates and corrects the type-inference FAQ, which named a flag
(inferTypes) that does not exist.Release notes
Open source →Opt-in ISO-8601 date and date-time inference. Additive and backward-compatible.
New
CsvConfig(parseDates: true)turns a field in ISO-8601 form into a realDateTimeduring a typed decode, instead of leaving it as text. It applies everywhere inference does:decode,decodeToTable,decodeToMaps, the streamingCsvDecoder, andbindBytes. Off by default, so nothing changes for existing code.- A value must start with
YYYY-MM-DD; a time part may follow after aTor a space, with optional fractional seconds and aZor numeric offset. A value with no offset reads as local time, one with an offset as UTC. Ambiguous locale formats such as03/04/2024stay text, as do quoted fields and unpunctuated runs such as20240131. - Every date and time field is range-checked before parsing, so an impossible
value stays text instead of silently becoming the wrong date.
DateTime.parserolls2024-13-45over to 14 February 2025; csv_plus does not. FastDecoder.tryParseIsoDateTimeexposes the same strict parser, andFastDecoder.inferTypetakes an optionalparseDatesflag.
A
DateTimeencodes back to a form that decodes to the same value, so a decode/encode round trip is lossless in both local and UTC. -
1.2.123 Aug 2026Release notes
Open source →The IndexNow key file is named after the key, so building before the real
key was set left a PLACEHOLDER.txt behind. The .firebase cache is local
deploy state and should never have been tracked.Release notes
Open source →Changed
- Added a documentation website at https://csv-plus.web.app, with guides
for parsing, reading and writing CSV, headers, type inference, querying
and grouping, JSON conversion, schemas, delimiters, and large files.
Linked from the package page via the new
documentationfield.
- Added a documentation website at https://csv-plus.web.app, with guides
for parsing, reading and writing CSV, headers, type inference, querying
and grouping, JSON conversion, schemas, delimiters, and large files.
Linked from the package page via the new
-
1.2.025 Jul 2026Release notes
Open source →Add CsvSchema.coerce, CsvTable.coerce, and CsvCodec.decodeWithSchema to
convert each column's values to the type declared on its CsvColumnDef
(int, double, num, bool, String, DateTime). A column with no schema entry
or a null type is left unchanged; CsvTable.coerce returns a copy and never
mutates the source.Coercion throws CsvParseException (carrying the 0-based row and column) on
a value that cannot convert, or a null in a column declared nullable:false;
a null in a nullable column stays null. Completes the schema story: it
could already validate types, and can now coerce them.Release notes
Open source →Per-column type coercion driven by
CsvSchema. Additive and backward-compatible.New
CsvSchema.coerce(headers, rows),CsvTable.coerce(schema), andCsvCodec.decodeWithSchema(input, schema)convert each column's values to the type declared on itsCsvColumnDef(int,double,num,bool,String, orDateTime). A column with no schema entry, or anulltype, is left unchanged;CsvTable.coercereturns a copy and never mutates the source.- Coercion throws
CsvParseException(carrying the 0-basedrowandcolumn) when a value cannot be converted, or when a null appears in a column declarednullable: false; a null in a nullable column stays null. This completes the schema story:CsvSchemacould already validate types, and can now coerce them.
-
1.1.016 Jul 2026Release notes
Open source →Add three decode-only CsvConfig options plus a codec convenience:
- comment: drop comment-prefixed lines, detected only at the start of a
line so a marker inside a quoted or mid-field value stays content - skipRows: skip leading rows before the header row (a preamble)
- maxRows: cap the returned data rows; the batch decoders stop early and
the streaming decoder stops emitting - CsvCodec.decodeToMaps: decode straight to header-keyed maps
All three options apply across every decode path (batch typed, strings,
flexible, the typed decoders, table, maps, and the streaming decoder) and
are held to identical output by the conformance suite, which splits input
at every chunk boundary. Closes the comment-skipping and row-windowing
roadmap items.Release notes
Open source →Comment-line skipping, row windowing, and a header-keyed map decode. All additions are backward-compatible: existing calls behave exactly as before.
New
CsvConfig(comment: '#')drops comment lines while decoding. The marker is matched only at the very start of a line, so a#inside a quoted field or mid-field is ordinary content. The marker is a single character; comment lines never count towardskipRows.CsvConfig(skipRows: n)skips leading rows before the header row is read, for a preamble sitting above the real table.CsvConfig(maxRows: n)caps the number of data rows returned (the header is not counted); the batch decoders stop reading once the limit is reached and the streaming decoder stops emitting.CsvCodec.decodeToMapsdecodes straight to aList<Map<String, dynamic>>keyed by header name, a shortcut fordecodeToTable(input).toMaps().
All three config options apply across every decode path (
decode,decodeStrings,decodeFlexible, the typed decoders,decodeToTable,decodeToMaps, and the streamingCsvDecoder), and are held to identical output by the conformance suite that splits input at every chunk boundary. - comment: drop comment-prefixed lines, detected only at the start of a
-
1.0.110 Jul 2026Release notes
Open source →Documentation and metadata only; no library or API changes.
- Reworked the README (clearer structure, added the logo screenshot, and broader keyword coverage) and refined the pub.dev topics for discoverability.
- Switched the head-to-head benchmark to compare against serial_csv in place of fast_csv.
-
1.0.010 Jul 2026Release notes
Open source →First stable release: one documented parsing semantics across every decode path, streaming you can trust, data-loss guards on type inference, and public benchmark receipts. The API is now frozen under semantic versioning.
One parsing semantics (breaking behavior alignments)
The batch decoder, string decoder, and streaming decoder previously disagreed on edge cases. All paths now produce identical output, enforced by a conformance suite that also splits input at every chunk boundary (
test/conformance_test.dart).- An empty line reads as a row with one empty field (
[''], or[null]with typing), per RFC 4180 and matching csv 8 and fast_csv. WithskipEmptyLines(default), rows of a single empty field are dropped; rows of several empty fields (,,) are now always kept (previouslydecodeStringsdropped them). - Text after a closing quote is appended to the field, so
"a"xreadsax(Excel behavior). Previously the batch decoder produced an extra cell and the streaming decoder swallowed the following delimiter. - With
hasHeader, the header row is read as raw strings on every path: a header cell01stays01(previously typed then stringified to1), anddecoderTransformis not applied to it. - Quoted fields are never type-inferred on any path.
Type inference guards (data safety)
FastDecoder.inferTypeis now the single shared inference used by all typed paths, with guards against silent corruption:- Leading zeros (
007), leading plus (+1), and surrounding whitespace stay text (previously' 42'typed differently per path). - Digit runs longer than 15 stay text on every platform, keeping VM and web results identical (web ints lose precision past 2^53).
- Values that would parse to a non-finite double (
1e999) stay text.
Typed decoders no longer invent data (breaking)
decodeIntegers/decodeDoubles/decodeBooleansthrowCsvParseException(with row and column) on invalid cells instead of coercing them; empty cells throw unless an explicitemptyAs:fill is passed (previously empty became0/0.0and any non-truevalue becamefalse).decodeBooleanstruth table is documented and case-insensitive:true/1andfalse/0.decodeFlexibleusesconfig.quoteCharacterwhen restoring an unmatched quote (previously hardcoded"), and reads empty fields as''instead ofnullwhen typing is off.
Streaming you can trust
CsvDecoder.bindandCsvEncoder.bindhonor downstream backpressure (pause/resume/cancel propagate to the source), so a slow consumer no longer buffers the whole input in memory, and the output stream closes after an upstream error instead of hanging.- Multi-character delimiters split across chunk boundaries now parse correctly (previously they became field text).
- New
CsvDecoder.bindBytes/CsvEncoder.bindBytesfor UTF-8 byte streams without manualutf8wiring. CsvFile.writeStreamcloses the file even when the source stream errors.
New: strict mode
CsvConfig(strict: true)throwsCsvParseExceptionwith row and column on structurally malformed input (text after a closing quote, unterminated quote) instead of recovering. Lenient stays the default.Fixed
decodeWithHeadersparsed the entire input twice; it is now single pass and roughly twice as fast (fastest in the ecosystem on this workload, previously the single lost benchmark).CsvTable.maphanded live row lists to its transform, so writing through the row mutated the source table. The transform now receives a copy; the source is never modified.- Delimiter autodetect no longer misreads single-column text containing semicolons as two columns: a candidate must appear on every sampled line to qualify (csv 8 still has this failure).
CsvTable.parseheaders are raw strings (01stays01).- All table sorts are stable, and nulls sort last in both directions.
Mixed-type columns sort numbers before string look-alikes instead of
comparing
"10" < "9"lexicographically. distinct()keys are type-aware (1,1.0, and"1"are distinct) and immune to separator collisions in string content.encodeGeneric<String>quotes strings containing delimiters instead of producing corrupt CSV;QuoteMode.alwayswrites null as"".- Batch and streaming encoders share one cell-writing implementation.
Changed
ColumnDefis renamedCsvColumnDef(a deprecated typedef keeps old code compiling).DelimiterDetectorleft the defaultcsv_plus.dartnamespace; importpackage:csv_plus/decoder.dartto use it directly.CsvTabledocuments its mutation rule: table-returning methods copy, void methods mutate in place. New stablesortedBy()returns a sorted copy.- Releases are gated: the publish workflow now runs format, analyze, tests, and a publish dry-run before tagging or publishing, and CI runs a stable + minimum SDK matrix plus wasm and pana jobs.
Benchmarks
Fastest on every measured workload (decode, typed decode, autodetect, quote-heavy, encode, decodeWithHeaders) against csv 8.0.0, fast_csv 0.2.11, and serial_csv 0.5.2, on JIT and AOT. The reproducible harness and full tables live in
benchmark/compare/.
- An empty line reads as a row with one empty field (
-
0.0.215 Apr 2026Release notes
Open source →- Topics: csv, csv-parser, serialization, data-processing, file-handling
- Changelog: added 0.0.2 entry for docs, CI, and meta changes
- Version bump 0.0.1 → 0.0.2
Release notes
Open source →Documentation
- Redesigned README with hero layout, badges, feature table, and quick start examples
- Added 8 mini-library files for dartdoc sidebar navigation (core, codec, encoder, decoder, table, query, transform, io)
- Enhanced barrel export
lib/csv_plus.dartwith library modules reference
Meta
- Added MIT LICENSE file
- SEO-optimized pubspec description and topics for pub.dev
- Added CI workflow (analyze, format, test on PRs)
- Added publish workflow (auto-tag + publish to pub.dev)
-
0.0.115 Apr 2026Release notes
Open source →Core
CsvConfig: immutable configuration with presets:CsvConfig(),.excel(),.tsv(),.pipe()CsvConfig.copyWith(): create modified copiesQuoteModeenum:necessary,always,stringsCsvException,CsvParseException,CsvValidationException: typed error hierarchy
Encoding
FastEncoder: high-performance batch encoder withencode(),encodeStrings(),encodeGeneric<T>(),encodeMap()CsvEncoder: streaming encoder asStreamTransformerwithbind(),convert(),startChunkedConversion()CsvEncoder.encodeField(): static helper for single-field quoting- codeUnit-based
_needsQuoting()for multi-char delimiter support
Decoding
FastDecoder: byte-level batch decoder withcodeUnitsparsing, labeled-loop control flow, first-byte type inference- Decode variants:
decode(),decodeStrings(),decodeFlexible(),decodeIntegers(),decodeDoubles(),decodeBooleans() CsvDecoder: chunked state-machine streaming decoder withbind(),convert(),startChunkedConversion()- Handles chunk boundaries splitting mid-field, mid-escape, mid-CRLF
DelimiterDetector: frequency/consistency scoring across candidates[, ; \t |], BOM strip,sep=hint
Facade
CsvCodec: main API with all decode/encode methods, presets, auto-detectionCsvCodec.decodeToTable(),decodeMap(),encodeMap()CsvCodec.decoder/encoder: streaming transformer gettersCsvCodecAdapter:Codec<List<List<dynamic>>, String>fordart:convertpipelines and.fuse()csvPlus,csvExcel,csvTsv: global convenience instances
CsvTable (50+ methods)
- Constructors:
CsvTable(),.withHeaders(),.fromData(),.fromMaps(),.parse(),.empty() - Access:
operator [],cell(),cellByName(),setCell(),setCellByName(),column(),columnAt(),getColumn(),getColumnAt() - Row ops:
addRow(),addRowFromMap(),addRows(),insertRow(),removeRow(),removeWhere() - Column ops:
addColumn(),insertColumn(),removeColumn(),removeColumnAt(),renameColumn(),reorderColumns() - Query:
where(),firstWhere(),any(),every(),range(),take(),skip(),distinct() - Sort:
sortBy(),sortByIndex(),sortByMultiple(),sort() - Transform:
transformColumn(),map(),fold<T>() - Aggregate:
count(),sum(),avg(),min(),max(),groupBy() - Export:
toList(),toMaps(),toCsv(),toString(),toFormattedString(),copy() - Validation:
validate(),conformsTo(),inferSchema()
CsvRow
- Dual-mode access:
row[0](int) androw['name'](String) set(),headerMap,hasHeaders,headers,containsHeader(),toMap(),getHeaderName(),toString()
CsvColumn
- Column descriptor with
name,index,values,inferredType,nonNullCount,nullCount,uniqueCount
CsvSchema & ColumnDef
- Schema definition with
columns,allowExtraColumns,allowMissingColumns CsvSchema.infer(): infer types and nullability from datavalidate(): check required columns, types, nullability, patterns, custom validatorsColumnDefwithname,type,required,nullable,pattern,validator
CsvFile (dart:io)
- Static methods:
read(),readSync(),stream(),write(),writeSync(),writeRows(),writeStream(),append() - Uses
utf8.decoderfor stream operations - Isolated in
io/csv_file.dart: core library stays platform-independent