csv_plus 1.8.0
csv_plus: ^1.8.0 copied to clipboard
Fast, complete CSV parser for Dart. Encode, decode, stream, query, and validate CSV data with automatic type inference and zero dependencies.
1.8.0 #
Numeric dates like 03/04/2024 can now be read, once you say which order they
use. That closes the one limitation the README listed.
New #
CsvConfig.dateOrder, taking aCsvDateOrder.iso, the default, keeps today's behaviour and leaves an ambiguous value as text.dayFirstreads03/04/2024as 3 April,monthFirstas 4 March./,-and.all separate, day and month may be one or two digits, and a trailingHH:mmorHH:mm:ssis kept. A two-digit year follows the spreadsheet convention: up to 68 is this century, 69 and above the last one.
Notes #
The order is something you tell csv_plus, never something it works out. A CSV
carries no locale, so a file of 03/04/2024 values is genuinely ambiguous and
sniffing it would be wrong for half of all files. The default stays iso, so
nothing changes for anyone who does not set it.
Range checking is unchanged and still applies to the new forms: under
monthFirst a value like 25/12/2024 has no 25th month, so it stays text
rather than rolling over into another year, and 29 February is accepted only in
a leap year.
All three decode paths, the batch decoder, decodeStrings and the streaming
CsvDecoder, resolve dates through one shared function, and the suite checks
every case through all of them with the stream split at every offset, so they
cannot drift apart.
Named months (3 April 2024) are still out of scope; a decoderTransform
handles those.
1.7.0 #
Write a sentinel for a null, so a file can round-trip its nulls.
New #
CsvConfig(nullPlaceholder: 'NULL')writes that text for anullinstead of an empty field. Some destinations want a sentinel: PostgresCOPYreads\N, and plenty of exports useNULL. Paired withnullValuesthe same file reads back with its nulls intact, which the previous release could decode but not produce.- The placeholder is written with
necessaryquoting whateverquoteModeis set to. It is a sentinel rather than data, and underalwaysorstringsit would come out quoted, whichnullValuesdeliberately never matches, so the round trip would have quietly failed. It still gets quotes when the text itself needs them. - An empty string is untouched by all of this. It still encodes as a quoted
empty field, so
''andnullstay distinguishable on the way back.
1.6.0 #
Turn the spellings an exporter uses for a missing value into real nulls.
New #
CsvConfig(nullValues: {'NULL', 'NA', 'N/A'})decodes those fields asnullinstead of as their own text. Exports frequently write a missing value that way rather than leaving the field empty, and it otherwise arrives as a string that every downstream check has to special-case.
The rule is narrow on purpose, because it is what keeps the decoders in step:
- Matching is exact and case-sensitive, so list every spelling the file uses.
- A quoted field is never matched, so a genuine
"NULL"in the data survives. - Only a value that would otherwise read as text is eligible. An entry that
type inference turns into a number or a bool (
0,false) never reaches the check, so putting one in the set has no effect. decodeStringsis unaffected, since its return type cannot hold a null.
This was deferred twice over the one-parsing-semantics invariant. The suite now checks batch and streaming agree on every case, with the streaming input split at every possible offset, so a value straddling a chunk boundary is exercised rather than assumed.
1.5.0 #
Read the padding some exporters leave after a delimiter.
New #
CsvConfig(skipInitialSpace: true)drops spaces sitting between a delimiter and the start of a field. RFC 4180 treats a space before a quote as content, soa, "b, c"is three fields by the letter of the spec; spreadsheets and several exporters read it as two. Off by default, so the strict reading stays the default. Only unquoted leading spaces are dropped: a space inside a quoted field, after the first character, or at the end of a field is still content.- A comment marker reached only after skipped spaces is content rather than a comment, matching what the batch path already did with an indented marker.
All three decode paths honour it identically, and the new suite checks that on every case by splitting the streaming input at every possible offset, so a run of spaces crossing a chunk boundary is exercised rather than assumed.
1.4.0 #
Read the files other tools actually hand you: bytes in, bytes out, and the encodings Excel on Windows writes. Additive and backward-compatible.
New #
CsvCodec.decodeBytesdecodes CSV straight from a byte list, alongsidedecodeBytesWithHeaders,decodeBytesToTableanddecodeBytesToMaps. This is the shape Flutter hands you:PlatformFile.bytesfrom a file picker,rootBundle.load()for a bundled asset, andresponse.bodyBytesfrom an HTTP call, and it is the only option on web, where there is no file path to open. The byte order mark, thesep=hint and delimiter auto-detection all apply on the way in, exactly as they do for a string.CsvCodec.encodeToBytesreturns UTF-8 bytes, ready forFile.writeAsBytes, a browser download, or a request body. WithCsvConfig(addBom: true)the output starts with the byte order mark that makes Excel open the file as UTF-8.CsvCharsetpicks the encoding when a file is not UTF-8.CsvCharset.latin1andCsvCharset.windows1252read the accented names and currency symbols that a UTF-8 decoder replaces, and Windows-1252 is what the Excel CSV export writes on Western European Windows. Pass it to any of the byte decoders. No new dependency: Windows-1252 differs from Latin-1 only in 32 slots, so it is a small constant table rather than a charset library.- A leading UTF-8 byte order mark is now stripped for the single byte encodings too, so it can never be glued onto the first column name.
Docs #
- Two new guides: reading bytes, covering file pickers, assets, HTTP bodies and web, and encodings for non UTF-8 files.
example/bytes_example.dartruns the whole path end to end, including the same bytes read as UTF-8 and as Windows-1252 so the difference is visible.
1.3.0 #
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 DateTime encodes 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.1 #
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.
1.2.0 #
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.0 #
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 streaming CsvDecoder), and are held to identical
output by the conformance suite that splits input at every chunk boundary.
1.0.1 #
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.0 #
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.inferType is 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) throws CsvParseException with 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/.
0.0.2 #
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.1 #
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
