Browse documentationOpen

byrcsc/laravel-data-sync · 1.x

File formats and readers.

Configure the CSV, XLSX, JSON, NDJSON, and fixed-width readers, understand what streams and what does not, and know how row numbers and values are normalized.

format() returns the reader for the definition's files. Readers are built from the Reader factory:

use ByRcsc\LaravelDataSync\Contracts\Reader as ReaderContract;
use ByRcsc\LaravelDataSync\Definitions\Reader;

public function format(): ReaderContract
{
    return Reader::csv()->delimiter('|')->encoding('ISO-8859-1');
}
FactoryReads
Reader::csv() / Reader::delimited()Delimited text; the two are identical
Reader::xlsx()The first sheet of an XLSX workbook
Reader::json()One JSON document containing an array of row objects
Reader::ndjson()One JSON object per line
Reader::fixedWidth()Fixed-width text, one record per line

Every reader produces the same thing: a stream of rows, each an array keyed by column name — or by zero-based index when there is no header — with every value a string or null. Type conversion is the mapping layer's job, through transform() and model casts.

CSV and delimited text

Reader::csv()
    ->delimiter(';')
    ->enclosure('"')
    ->escape('\\')
    ->encoding('ISO-8859-1')
    ->withoutHeader();
OptionDefaultNotes
delimiter(),Exactly one byte
enclosure()"Exactly one byte
escape()\One byte, or '' to disable escaping
encoding()UTF-8Any iconv encoding name; converted to UTF-8
withHeader() / withoutHeader()headerWhether the first row names the columns

A UTF-8 byte-order mark on the first column is stripped, so a Windows-exported CSV does not silently produce a \xEF\xBB\xBFSKU column.

Non-UTF-8 input is converted with //IGNORE, so undecodable bytes are dropped rather than throwing. If a feed regularly hits that path, fix the encoding at the source.

XLSX

Reader::xlsx()->withHeader();

Only the first sheet is read; other sheets are ignored without warning. There is no cell-range or named-table selection.

Cell values are normalized to strings before mapping: dates and times become ISO-8601 (DATE_ATOM) strings, booleans become "true" and "false", numbers become their string form, and anything else is JSON-encoded. A date cell therefore arrives as 2026-07-31T00:00:00+00:00, which a datetime cast on the model handles directly.

Because the XLSX format is a zip archive, the reader copies the staged file to a local temporary file before opening it. Rows still stream from there, but a workbook needs local scratch space the size of the file.

JSON

Reader::json()->path('data.products');

path() is a dot path to the array of rows inside the document. Omit it when the document is itself an array of objects. The target must be a list of objects; anything else throws, naming the path.

JSON does not stream. Reader::json() reads the entire document into memory and then selects the row array. Multi-gigabyte single-document JSON is out of scope — use NDJSON when rows need to stream independently.

Values are normalized the same way as XLSX cells, so a nested object or array arrives as a JSON string. Map it with a transform() if you need it structured again.

NDJSON

Reader::ndjson();

One JSON object per line, no configuration. Blank lines are skipped. A line that is not valid JSON, or is valid JSON but not an object, throws with its line number.

Rows stream, but note that determining the file's columns reads every line: the header is the union of the keys seen across the whole file. That is what allows a feed whose later rows carry extra keys to validate correctly, at the cost of one extra pass.

Fixed width

Reader::fixedWidth()
    ->column('sku', start: 0, length: 12)
    ->column('name', start: 12, length: 40)
    ->column('price', start: 52, length: 10);

Positions are zero-based byte offsets and lengths. Values are trimmed. There is no header row — the columns you declare are the header, so map fields with Field::make('sku', from: 'sku') using the same names.

A line shorter than the highest start + length throws with its line number. Trailing \r\n is stripped before slicing.

Headers and column addressing

With a header row, address columns by name:

Field::make('sku', from: 'SKU');

Without one — withoutHeader() on the CSV or XLSX reader — address them by zero-based index:

Field::make('sku', from: 0);

Column names are matched exactly, including case and surrounding whitespace. A feed with " SKU" needs from: ' SKU', or a producer who trims their export.

Row numbers

Row numbers appear in failure records and in sync:status --failures, so it helps to know what they count:

ReaderRow 1 is
CSV, XLSXThe header line; data starts at 2 (at 1 with withoutHeader())
JSONThe first element of the row array
NDJSONThe first line of the file, blank lines included in the count
Fixed widthThe first line of the file

Streaming, memory, and chunk size

CSV, XLSX, NDJSON, and fixed-width files stream row by row. Memory is bounded by chunkSize() — the number of rows held for one transaction — not by file size. Reader::json() is the exception noted above.

Queued runs read the staged file more than once: once to plan the file and resolve duplicates, then once per chunk job, which skips forward to its offset. That trades repeated sequential reads of a local staging file for the ability to process one file across several workers. For a very large file where that matters, a bigger chunkSize() means fewer, longer passes.

Custom readers

format() is typed against the ByRcsc\LaravelDataSync\Contracts\Reader interface, which is two methods:

public function header(mixed $stream): array;   // list<string|int>
public function rows(mixed $stream): Generator;  // rowNumber => array<string|int, string|null>

Implementing it against a fixed-format feed the shipped readers do not cover — an EDI segment layout, say — is supported, and the two-method shape is part of the 1.x contract. format() accepts any implementation, so a custom reader needs no registration.

header() receives the open stream and returns the column names, and rows() yields rowNumber => row with every value a string or null — the same normalization the shipped readers apply, and what the mapping layer expects.