<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nishant Shaligram</title>
    <description>The latest articles on DEV Community by Nishant Shaligram (@nishant_shaligram).</description>
    <link>https://dev.to/nishant_shaligram</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4119772%2F39b5e562-9625-4a11-b009-b836efad21b4.png</url>
      <title>DEV Community: Nishant Shaligram</title>
      <link>https://dev.to/nishant_shaligram</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nishant_shaligram"/>
    <language>en</language>
    <item>
      <title>Engineering a Resilient CSV Parsing &amp; Normalization Layer for Accounting Integrations</title>
      <dc:creator>Nishant Shaligram</dc:creator>
      <pubDate>Fri, 11 Sep 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/nishant_shaligram/engineering-a-resilient-csv-parsing-normalization-layer-for-accounting-integrations-3nj6</link>
      <guid>https://dev.to/nishant_shaligram/engineering-a-resilient-csv-parsing-normalization-layer-for-accounting-integrations-3nj6</guid>
      <description>&lt;h1&gt;
  
  
  Engineering a Resilient CSV Parsing &amp;amp; Normalization Layer for Accounting Integrations
&lt;/h1&gt;

&lt;p&gt;Building apps that connect to accounting software often seems simple at first.. Once you get to the data ingestion layer things get complicated. Financial platforms like Xero have unforgiving rules for CSV imports. If your data doesn’t match their expected schema— down to the smallest detail—your ingestion pipeline throws errors.. These errors are usually hard to understand.&lt;/p&gt;

&lt;p&gt;In this post I want to share how we built the data normalization and validation system at Sync2Zero. We made it to handle user inputs, date format differences across regions and the way Microsoft Excel changes data when you open and save files.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Why Standard CSV Parsers Fail in Finance
&lt;/h2&gt;

&lt;p&gt;When you build tools that take in spreadsheet data or export files for accounting systems standard CSV parsers like csv-parse or papaparse do the job of reading the file structure.. They don’t handle human errors or spreadsheet issues.&lt;/p&gt;

&lt;p&gt;The common problems that break systems in production include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UTF-8 BOM (Byte Order Mark):&lt;/strong&gt; Some desktop apps add a hidden EF BB BF sequence at the start of the file. This breaks the parser when it tries to match the header field like &lt;code&gt;*ContactName&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leading Zero Stripping:&lt;/strong&gt; Spreadsheet software treats numbers like account codes (example: &lt;code&gt;0400&lt;/code&gt; or &lt;code&gt;0021&lt;/code&gt;) as numbers. It removes the leading zeros. That breaks the account references in the ledger.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regional Date Collisions:&lt;/strong&gt; Even if you use ISO format (&lt;code&gt;YYYY-MM-DD&lt;/code&gt;) files can still have issues. Regional formats like &lt;code&gt;DD/MM/YYYY&lt;/code&gt; get flipped when opened on operating systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Designing a Bulletproof Normalization Pipeline
&lt;/h2&gt;

&lt;p&gt;To make our ingestion engine work reliably we built a -stage preprocessing system. This runs before any mapping or exporting happens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Conceptual schema validation pass for Xero CSV imports&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;XeroBillRow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;ContactName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;InvoiceNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;InvoiceDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Must strictly match regional settings&lt;/span&gt;
  &lt;span class="nl"&gt;DueDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;AccountCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Must retain leading zeros as strings&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sanitizeAndNormalizeRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawRow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;XeroBillRow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ContactName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sanitizeString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawRow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ContactName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;InvoiceNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sanitizeString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawRow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InvoiceNumber&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;InvoiceDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;normalizeDateFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawRow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InvoiceDate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;DueDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;normalizeDateFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawRow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DueDate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawRow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;AccountCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawRow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AccountCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Prevent leading zero stripping&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Stripping Byte Order Marks (BOM)
&lt;/h3&gt;

&lt;p&gt;Before parsing our system checks for a UTF-8 BOM at the start of the file. If it finds one it removes it immediately. This ensures that column names like &lt;code&gt;*ContactName&lt;/code&gt; are matched correctly and don’t fail.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Enforcing String Types on Numeric Codes
&lt;/h3&gt;

&lt;p&gt;Account codes and tracking categories are stored as strings. We enforce zero-padding to make sure the leading zeros never get removed. That keeps the account codes valid and readable.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Automated Date Normalization
&lt;/h3&gt;

&lt;p&gt;Of letting the system fail because of regional date differences we check the target Xero organization’s date settings. Then we convert the date strings into the format they need. This way the data stays accurate across regions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Building tools in fintech or accounting shows you something developer experience isn’t just about clean APIs. It’s about making software that can handle real-world messiness.&lt;/p&gt;

&lt;p&gt;If you’re curious about how we extract data from PDFs using AI and generate CSV files take a look, at &lt;a href="https://sync2zero.com" rel="noopener noreferrer"&gt;Sync2Zero&lt;/a&gt;. I’d love to hear how other developers deal with data ingestion challenges. Leave a comment below.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>sass</category>
    </item>
  </channel>
</rss>
