A product file contains two rows with the same SKU. Removing one seems reasonable until you notice that the prices differ. Keeping the first row silently chooses one price; keeping the last silently chooses the other. Neither operation tells you which price belongs in the catalogue.
The useful distinction is between a repeated record and conflicting records about the same product. The accompanying browser CSV cleaner handles these separately: merge records that match after a few explicit cleanup rules, then flag surviving records that share a SKU.
Start with a disagreement you can see
Consider this fictional input:
SKU,Product name,Unit,Price,Stock
0001,Glass tumbler,pcs,12.50,0
0001 , Glass tumbler ,piece,12.50,0
0005,Gel pen,piece,2,20
0005,Gel pen,piece,3,20
0006,Printer paper,box,10,-1
The first two records describe the same tumbler. After trimming surrounding whitespace and mapping pcs and piece to the same unit, every cell matches. Merging them removes repetition while retaining source record numbers 1 / 2.
The two pen records disagree about price. Both remain in the result, each marked for review. The paper record also remains, with its negative stock flagged. Five input records become four output records; three output records need review.
Those record numbers count parsed data records, starting after the header. They are not physical text-line numbers: a quoted product name can contain a newline.
Define equality before grouping
In this implementation, each cell is trimmed. Only the column selected as the unit field receives unit normalization. The English wrapper handles aliases such as each, kilograms, and bottles; the shared core performs grouping and validation.
The grouping key includes the entire normalized row, including columns that have no validation role assigned. Here is the relevant part of the core, with formatting expanded:
const key = JSON.stringify(values);
if (groups.has(key)) {
groups.get(key).sources.push(i + 1);
} else {
groups.set(key, { values, sources: [i + 1] });
}
Using the array's JSON representation preserves cell boundaries. Joining cells with a comma would be ambiguous when a cell itself contains a comma.
After this pass, the cleaner counts the remaining records for each nonempty SKU. More than one means the normalized rows differ somewhere. Every member receives a conflict flag; the tool does not select a winner, add quantities, or invent a replacement value.
These equality rules have concrete consequences. SKU matching remains case-sensitive. Prices 2 and 2.00 remain different strings, even though both pass the numeric-format check. If they occur under the same SKU, the difference is surfaced for review. A reviewer can then decide whether the different formats should be treated as equivalent.
Keep identifiers and zero values intact
CSV parsing leaves cells as strings. The SKU 0001 therefore stays 0001; there is no conversion to a number followed by an attempt to reconstruct its leading zeros.
Stock 0 is valid, as is price 0. The stock check accepts digits only; the price check accepts the implemented nonnegative decimal format. A blank price and negative stock are flagged when those fields are selected. The records are retained so the missing or disputed information can be resolved.
Column mapping matters here. SKU is required; product name, unit, price, and stock checks are optional. Leaving a field unselected means its specialized check is skipped. “Cleaned” therefore means the record passed the selected checks, not that every possible catalogue requirement has been verified.
The existing checks exercise quoted commas, escaped quotes, embedded newlines, a leading byte-order mark, leading-zero SKUs, and valid zero stock. They also check duplicate provenance and preservation of conflicting rows. Together, these checks cover the distinctions used in this example.
Make the review usable outside the page
The result provides a cleaned CSV, a review-only CSV, and JSON containing both parsed source data and the cleaned result. Original source cell values remain available in JSON, alongside normalized values and source-record references.
CSV export quotes cells and prefixes formula-like values with an apostrophe. That changes their spreadsheet-facing representation; JSON keeps the source value. These are separate purposes, so the exports should not be treated as interchangeable archives.
When importing the CSV into Excel, set the SKU column to Text. Preserving zeros in the file does not control how a spreadsheet chooses to interpret that file.
The demo handles up to 200 records and ten columns per run, with cleanup performed in browser memory. The output keeps one representative of each repeated record, along with the product decisions that need review.
You can try the example in the free browser tool, or inspect and run the public source.
For a finished spreadsheet and review report, see the product-data cleanup service.