Skip to main content

CSV Rows With a Different Number of Fields — and What __parsed_extra Means

CSV has no rule that every row must match the header width, so a ragged row is not automatically an error. What happens next depends entirely on which parser reads the file: one preserves the surplus, one quietly changes the shape, one refuses to continue.

The short answer

A row with more or fewer fields than the header is not invalid CSV. The format has no rule requiring every record to be the same width, so each parser decides for itself what such a row means — and they disagree.

Reading the same ragged file: Papa Parse keeps the surplus values in an array it adds under __parsed_extra and reports a mismatch; Python's csv.DictReader changes the shape of the row and says nothing; Go's encoding/csv stops with an error. None of them is wrong — CSV simply does not settle the question.

One distinction is worth getting right immediately: an empty field is not a missing field. alice,a@x.com, has three fields, the last one empty, and no parser treats it as ragged.

What each kind of row produces

With the header name,email,role, measured through Papa Parse 5.5.4 with header: true:

RowDiagnosticResult
alice,a@x.com,adminExact widthnone{"name":"alice","email":"a@x.com","role":"admin"}Three fields for three columns. Nothing to report.
bob,b@x.com,user,EXTRAOne extra fieldFieldMismatch / TooManyFields{"name":"bob","email":"b@x.com","role":"user","__parsed_extra":["EXTRA"]}The surplus value is kept, in an array under a property Papa Parse adds.
dave,d@x.comOne field shortFieldMismatch / TooFewFields{"name":"dave","email":"d@x.com"}No "role" property, because that row contained no value for it.
alice,a@x.com,Empty final fieldnone{"name":"alice","email":"a@x.com","role":""}Still three fields. An empty value is a value.
alice,,adminEmpty middle fieldnone{"name":"alice","email":"","role":"admin"}Still three fields. Not a width mismatch.

What __parsed_extra means

If you have seen this property appear in converted JSON and gone looking for it, it is not part of your data and it is not an error message. It comes from Papa Parse: when header: true is set and a row carries more values than there are header columns, the surplus values are collected into an array and attached to that row under the name __parsed_extra.

name,email,role
bob,b@x.com,user,EXTRA

{"name":"bob","email":"b@x.com","role":"user","__parsed_extra":["EXTRA"]}

Its presence means values were preserved that had nowhere to go, which is why it is better read as a signal than a failure. Papa Parse reports the width problem separately, as a FieldMismatch entry in its errors array with the code TooManyFields. The property holds the data; the errors array holds the diagnosis.

Rows that are too short

A row with fewer values simply produces an object without those properties, and a matching TooFewFields diagnostic:

name,email,role
dave,d@x.com

{"name":"dave","email":"d@x.com"}     // no "role"

Nothing was discarded here. The property is absent because the source row contained no value for it — which is a different situation from a value that existed and went missing. The practical hazard is downstream: an array of objects with inconsistent shapes will break code that assumes every record has the same keys, and row.role becomes undefined rather than raising anything.

Empty is not the same as missing

This trips people up when they scan a file visually, because consecutive commas look like something is wrong.

alice,a@x.com,      -> {"name":"alice","email":"a@x.com","role":""}
alice,,admin        -> {"name":"alice","email":"","role":"admin"}
dave,d@x.com        -> {"name":"dave","email":"d@x.com"}          // this one is short

The first two rows are three fields wide and produce no diagnostic at all — an empty value is still a value. Only the third row is genuinely ragged. If a tool warns you about the first two, it is counting delimiters rather than fields.

The same file, three parsers

This is the part worth remembering, because it decides how much the mismatch actually matters to you:

BehaviourPapa Parse 5.5.4Python csv.DictReader 3.9.6Go encoding/csv 1.23
Row has extra fieldsSurplus kept in an array under __parsed_extraSurplus kept in a list under the key NoneHard error by default; parsing stops
Row is missing fieldsThe property is omitted from that objectThe key is present with the value NoneHard error by default; parsing stops
Tells you about the mismatchYes — FieldMismatch in its errors arrayNo diagnostic by defaultYes — the error is the failure
ConfigurableThe property name is fixedYes — restkey and restvalYes — FieldsPerRecord = -1 allows variable widths

Go treats a width mismatch as fatal unless you opt out:

// Go 1.23, encoding/csv, default settings
r := csv.NewReader(f)
r.Read() // ...
// err: record on line 3: wrong number of fields

// Opt out of the width check:
r.FieldsPerRecord = -1   // variable-length records allowed

Python keeps going and quietly changes the row's shape instead:

# Python 3.9.6, csv.DictReader, defaults
{'name': 'bob',  'email': 'b@x.com', 'role': 'dev', None: ['EXTRA']}
{'name': 'dave', 'email': 'd@x.com', 'role': None}

# Configurable:
csv.DictReader(f, restkey='__extra__', restval='<MISSING>')

Note the inversion in the middle row of that table. Papa Parse is the one that both preserves the surplus and reports the problem — but only if whatever is using it reads the errors array. Python preserves the key shape but tells you nothing. A file that imports cleanly in one pipeline can be a hard failure in the next.

When a column is genuinely named __parsed_extra

An edge case worth knowing if you ever generate CSV programmatically. Because Papa Parse reserves that property name, a header that already uses it collides:

name,__parsed_extra
alice,MINE
{"name":"alice","__parsed_extra":["MINE"]}
                                 ^^^^^^^^ scalar text, represented as an array

# Papa Parse reported no field mismatch — the row is exactly two fields wide.
# With an ordinary column name the same value is a plain string:
name,notes
alice,MINE   ->  {"name":"alice","notes":"MINE"}

The row is exactly two fields wide and Papa Parse reports no mismatch, so nothing flags it. The value is still there and converting the JSON back to CSV reproduced the original file in testing, so this is a representation collision in the JSON view rather than lost or corrupted data. Measured on Papa Parse 5.5.4; the property name is not configurable.

Finding and fixing the rows

  1. 1. Decide whether the mismatch is real or a quoting problemA value containing an unescaped comma splits into two fields and makes an otherwise correct row look too wide. Per RFC 4180 such a value must be wrapped in double quotes, with any literal quote doubled.
  2. 2. Find the rows, not just the countPapa Parse reports the offending row index in its errors array; Go names the line in the error text. Locating the specific lines is usually faster than scanning the file.
  3. 3. Fix at the producer where you canA ragged export usually means the writer emitted a field conditionally or failed to quote a value. Correcting that is more durable than repairing the file downstream.
  4. 4. Otherwise choose the reader behaviour you wantGo accepts variable widths with FieldsPerRecord = -1. Python lets you name the overflow key and the fill value with restkey and restval. Papa Parse keeps the surplus for you but does not let you rename the property.
  5. 5. Re-check after fixingConverting again and confirming the mismatch warning is gone is a quicker verification than reading the output row by row.

Inspecting a file

The CSV Converter converts CSV to JSON with Papa Parse, so it behaves exactly as the first table describes. It surfaces the FieldMismatch diagnostics rather than discarding them: when rows do not match the header width it says how many, separates extra fields from missing ones, and gives the file line numbers, while still completing the conversion so you can read the output.

What it does not do is tell you how any other tool will read the same file. It reports what one parser found, on one configuration, with a comma delimiter and a header row. It is not a CSV validator, it does not check dialects or delimiters, and a file it converts cleanly can still fail in Go or arrive shaped differently in Python. Conversion runs in your browser, so an export containing customer data is not uploaded anywhere.

Versions measured

Ragged-row handling is a library decision, not a property of CSV, so it varies by implementation and by release. Everything above was measured on:

  • Papa Parse5.5.4 (header: true)
  • Python (stdlib csv.DictReader)3.9.6
  • Go (encoding/csv)1.23
  • DataToolsHQ CSV ConverterPapa Parse 5.5.4

Other parsers behave differently again, and pandas in particular was not tested here, so check your own stack rather than assuming these results transfer.