csv_plus 1.8.0 copy "csv_plus: ^1.8.0" to clipboard
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 a CsvDateOrder. iso, the default, keeps today's behaviour and leaves an ambiguous value as text. dayFirst reads 03/04/2024 as 3 April, monthFirst as 4 March.
  • /, - and . all separate, day and month may be one or two digits, and a trailing HH:mm or HH:mm:ss is 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 a null instead of an empty field. Some destinations want a sentinel: Postgres COPY reads \N, and plenty of exports use NULL. Paired with nullValues the same file reads back with its nulls intact, which the previous release could decode but not produce.
  • The placeholder is written with necessary quoting whatever quoteMode is set to. It is a sentinel rather than data, and under always or strings it would come out quoted, which nullValues deliberately 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 '' and null stay 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 as null instead 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.
  • decodeStrings is 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, so a, "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.decodeBytes decodes CSV straight from a byte list, alongside decodeBytesWithHeaders, decodeBytesToTable and decodeBytesToMaps. This is the shape Flutter hands you: PlatformFile.bytes from a file picker, rootBundle.load() for a bundled asset, and response.bodyBytes from an HTTP call, and it is the only option on web, where there is no file path to open. The byte order mark, the sep= hint and delimiter auto-detection all apply on the way in, exactly as they do for a string.
  • CsvCodec.encodeToBytes returns UTF-8 bytes, ready for File.writeAsBytes, a browser download, or a request body. With CsvConfig(addBom: true) the output starts with the byte order mark that makes Excel open the file as UTF-8.
  • CsvCharset picks the encoding when a file is not UTF-8. CsvCharset.latin1 and CsvCharset.windows1252 read 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.dart runs 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 real DateTime during a typed decode, instead of leaving it as text. It applies everywhere inference does: decode, decodeToTable, decodeToMaps, the streaming CsvDecoder, and bindBytes. Off by default, so nothing changes for existing code.
  • A value must start with YYYY-MM-DD; a time part may follow after a T or a space, with optional fractional seconds and a Z or numeric offset. A value with no offset reads as local time, one with an offset as UTC. Ambiguous locale formats such as 03/04/2024 stay text, as do quoted fields and unpunctuated runs such as 20240131.
  • Every date and time field is range-checked before parsing, so an impossible value stays text instead of silently becoming the wrong date. DateTime.parse rolls 2024-13-45 over to 14 February 2025; csv_plus does not.
  • FastDecoder.tryParseIsoDateTime exposes the same strict parser, and FastDecoder.inferType takes an optional parseDates flag.

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 documentation field.

1.2.0 #

Per-column type coercion driven by CsvSchema. Additive and backward-compatible.

New #

  • CsvSchema.coerce(headers, rows), CsvTable.coerce(schema), and CsvCodec.decodeWithSchema(input, schema) convert each column's values to the type declared on its CsvColumnDef (int, double, num, bool, String, or 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) when a value cannot be converted, or when a null appears in a column declared nullable: false; a null in a nullable column stays null. This completes the schema story: CsvSchema could 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 toward skipRows.
  • 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.decodeToMaps decodes straight to a List<Map<String, dynamic>> keyed by header name, a shortcut for decodeToTable(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. With skipEmptyLines (default), rows of a single empty field are dropped; rows of several empty fields (,,) are now always kept (previously decodeStrings dropped them).
  • Text after a closing quote is appended to the field, so "a"x reads ax (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 cell 01 stays 01 (previously typed then stringified to 1), and decoderTransform is 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 / decodeBooleans throw CsvParseException (with row and column) on invalid cells instead of coercing them; empty cells throw unless an explicit emptyAs: fill is passed (previously empty became 0 / 0.0 and any non-true value became false).
  • decodeBooleans truth table is documented and case-insensitive: true/1 and false/0.
  • decodeFlexible uses config.quoteCharacter when restoring an unmatched quote (previously hardcoded "), and reads empty fields as '' instead of null when typing is off.

Streaming you can trust #

  • CsvDecoder.bind and CsvEncoder.bind honor 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.bindBytes for UTF-8 byte streams without manual utf8 wiring.
  • CsvFile.writeStream closes 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 #

  • decodeWithHeaders parsed 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.map handed 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.parse headers are raw strings (01 stays 01).
  • 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.always writes null as "".
  • Batch and streaming encoders share one cell-writing implementation.

Changed #

  • ColumnDef is renamed CsvColumnDef (a deprecated typedef keeps old code compiling).
  • DelimiterDetector left the default csv_plus.dart namespace; import package:csv_plus/decoder.dart to use it directly.
  • CsvTable documents its mutation rule: table-returning methods copy, void methods mutate in place. New stable sortedBy() 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.dart with 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 copies
  • QuoteMode enum: necessary, always, strings
  • CsvException, CsvParseException, CsvValidationException: typed error hierarchy

Encoding #

  • FastEncoder: high-performance batch encoder with encode(), encodeStrings(), encodeGeneric<T>(), encodeMap()
  • CsvEncoder: streaming encoder as StreamTransformer with bind(), convert(), startChunkedConversion()
  • CsvEncoder.encodeField(): static helper for single-field quoting
  • codeUnit-based _needsQuoting() for multi-char delimiter support

Decoding #

  • FastDecoder: byte-level batch decoder with codeUnits parsing, labeled-loop control flow, first-byte type inference
  • Decode variants: decode(), decodeStrings(), decodeFlexible(), decodeIntegers(), decodeDoubles(), decodeBooleans()
  • CsvDecoder: chunked state-machine streaming decoder with bind(), 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-detection
  • CsvCodec.decodeToTable(), decodeMap(), encodeMap()
  • CsvCodec.decoder / encoder: streaming transformer getters
  • CsvCodecAdapter: Codec<List<List<dynamic>>, String> for dart:convert pipelines 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) and row['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 data
  • validate(): check required columns, types, nullability, patterns, custom validators
  • ColumnDef with name, type, required, nullable, pattern, validator

CsvFile (dart:io) #

  • Static methods: read(), readSync(), stream(), write(), writeSync(), writeRows(), writeStream(), append()
  • Uses utf8.decoder for stream operations
  • Isolated in io/csv_file.dart: core library stays platform-independent
40
likes
160
points
10.3k
downloads
screenshot

Documentation

Documentation
API reference

Publisher

verified publisheralmasum.dev

Weekly Downloads

Fast, complete CSV parser for Dart. Encode, decode, stream, query, and validate CSV data with automatic type inference and zero dependencies.

Repository (GitHub)
View/report issues
Contributing

Topics

#csv #csv-parser #serialization #deserialization #encoding

License

MIT (license)

More

Packages that depend on csv_plus