<?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: Chan Lebron</title>
    <description>The latest articles on DEV Community by Chan Lebron (@chan_lebron_61e9204b0aa43).</description>
    <link>https://dev.to/chan_lebron_61e9204b0aa43</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%2F4149614%2Fdee5ea35-5890-40e9-aace-3be644ae436d.jpg</url>
      <title>DEV Community: Chan Lebron</title>
      <link>https://dev.to/chan_lebron_61e9204b0aa43</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chan_lebron_61e9204b0aa43"/>
    <language>en</language>
    <item>
      <title>I Built a JSONL to CSV Converter. Could Developers Test It?</title>
      <dc:creator>Chan Lebron</dc:creator>
      <pubDate>Mon, 12 Oct 2026 08:13:43 +0000</pubDate>
      <link>https://dev.to/chan_lebron_61e9204b0aa43/i-built-a-jsonl-to-csv-converter-could-developers-test-it-595n</link>
      <guid>https://dev.to/chan_lebron_61e9204b0aa43/i-built-a-jsonl-to-csv-converter-could-developers-test-it-595n</guid>
      <description>&lt;p&gt;I've been building DataToolForge, a collection of browser-based data utilities.&lt;/p&gt;

&lt;p&gt;One tool I'd particularly like feedback on is a JSONL / NDJSON to CSV converter.&lt;/p&gt;

&lt;p&gt;It supports:&lt;/p&gt;

&lt;p&gt;Flattening nested objects into CSV columns&lt;/p&gt;

&lt;p&gt;Preserving arrays as JSON strings&lt;/p&gt;

&lt;p&gt;Reporting malformed JSONL records&lt;/p&gt;

&lt;p&gt;Local browser processing, without uploading files&lt;/p&gt;

&lt;p&gt;Try it: &lt;a href="https://datatoolforge.com/tools/jsonl-to-csv" rel="noopener noreferrer"&gt;https://datatoolforge.com/tools/jsonl-to-csv&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd appreciate feedback on three things:&lt;/p&gt;

&lt;p&gt;Did the conversion produce the output you expected?&lt;/p&gt;

&lt;p&gt;Was anything confusing about the interface?&lt;/p&gt;

&lt;p&gt;What would prevent you from using it for a real task?&lt;/p&gt;

&lt;p&gt;You can use the built-in example or a small, non-sensitive test dataset.&lt;/p&gt;

&lt;p&gt;I'm the developer, and I'm looking for practical feedback rather than promotional reviews. Critical feedback is welcome.&lt;/p&gt;

</description>
      <category>showdev</category>
    </item>
    <item>
      <title>Your JSON Parses. So Why Did One Value Disappear?</title>
      <dc:creator>Chan Lebron</dc:creator>
      <pubDate>Sun, 11 Oct 2026 01:07:01 +0000</pubDate>
      <link>https://dev.to/chan_lebron_61e9204b0aa43/your-json-parses-so-why-did-one-value-disappear-11gp</link>
      <guid>https://dev.to/chan_lebron_61e9204b0aa43/your-json-parses-so-why-did-one-value-disappear-11gp</guid>
      <description>&lt;p&gt;Imagine reviewing a configuration file for a nightly import job.&lt;/p&gt;

&lt;p&gt;You expect the job to stop after processing 1,000 rows. But the application keeps going until 10,000.&lt;/p&gt;

&lt;p&gt;The file looks reasonable at first glance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nightly-import"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"limits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max_rows"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max_rows"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are no missing commas or broken brackets. Your JSON parser doesn't throw an error.&lt;/p&gt;

&lt;p&gt;Look again at &lt;code&gt;max_rows&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The key appears twice, with two different values.&lt;/p&gt;

&lt;p&gt;That small detail changes what the application actually receives.&lt;/p&gt;

&lt;h2&gt;
  
  
  What JavaScript does with this input
&lt;/h2&gt;

&lt;p&gt;Save this as &lt;code&gt;check.js&lt;/code&gt; and run it with Node.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`{
  "task": "nightly-import",
  "limits": {
    "max_rows": 1000,
    "max_rows": 10000
  }
}`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max_rows&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 10000&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// [ 'max_rows' ]&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final output contains only one &lt;code&gt;max_rows&lt;/code&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nightly-import"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"limits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max_rows"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The earlier value didn't survive parsing.&lt;/p&gt;

&lt;p&gt;This is the part that makes the bug inconvenient: if you log only the parsed object, the evidence is gone.&lt;/p&gt;

&lt;p&gt;The log shows a normal object with one property. Nothing tells you that the original payload contained two competing values.&lt;/p&gt;

&lt;p&gt;If the raw document came from a generated configuration, a webhook, or a file assembled from multiple sources, the mistake may have happened several steps earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is duplicate-key JSON actually invalid?
&lt;/h2&gt;

&lt;p&gt;RFC 8259, Section 4, says that member names within a JSON object &lt;strong&gt;SHOULD be unique&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's an important distinction from &lt;strong&gt;MUST be unique&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The specification explains that implementations can handle duplicate names differently. Some keep the last occurrence; others reject the document or preserve multiple pairs.&lt;/p&gt;

&lt;p&gt;So the safer engineering assumption is not "every parser will reject this."&lt;/p&gt;

&lt;p&gt;It's "a document with duplicate object names may not be interpreted consistently."&lt;/p&gt;

&lt;p&gt;For configuration files and API contracts, I prefer a stronger rule: reject duplicate names before the application uses the parsed values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why checking the object afterward doesn't work
&lt;/h2&gt;

&lt;p&gt;You might try validating &lt;code&gt;config&lt;/code&gt; after calling &lt;code&gt;JSON.parse()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But by then, JavaScript has already reduced the duplicate entries to a single property.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;JSON.parse()&lt;/code&gt; reviver can't recover the overwritten member either. It receives values from the resulting parse, not an ordered record of every original occurrence.&lt;/p&gt;

&lt;p&gt;The same limitation applies when a JSON Schema validator is given an already-parsed object. It can check the surviving value's type, range, and required fields, but it cannot reconstruct a duplicate member that was discarded before validation.&lt;/p&gt;

&lt;p&gt;This is why duplicate-key detection belongs at the parsing boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small Python parser that rejects duplicates
&lt;/h2&gt;

&lt;p&gt;Python's standard &lt;code&gt;json&lt;/code&gt; module provides &lt;code&gt;object_pairs_hook&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead of immediately reducing every JSON object to a dictionary, this hook receives its ordered member pairs.&lt;/p&gt;

&lt;p&gt;That gives us a chance to reject duplicates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reject_duplicates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pairs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pairs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Duplicate JSON key: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_unique_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;object_pairs_hook&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;reject_duplicates&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;p&gt;Now test a duplicate in a nested object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;
{
  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nightly-import&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,
  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limits&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: {
    &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_rows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 1000,
    &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_rows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 10000
  }
}
&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;

&lt;span class="nf"&gt;load_unique_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of choosing one value, it raises:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ValueError: Duplicate JSON key: 'max_rows'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hook also runs for nested objects, including objects inside arrays.&lt;/p&gt;

&lt;p&gt;We don't need to scan the raw file with regular expressions or write an entire JSON parser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three regression cases I would keep
&lt;/h2&gt;

&lt;p&gt;A duplicate detector needs to reject repeated names &lt;strong&gt;within the same object&lt;/strong&gt;, not across unrelated objects.&lt;/p&gt;

&lt;p&gt;This should pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;
{
  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;primary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: {&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 1},
  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;backup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: {&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 2}
}
&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;load_unique_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;primary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;backup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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;p&gt;These should fail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;invalid_cases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# Duplicate in a top-level object
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;safe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fast&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;# Duplicate inside an array element
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: [{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 1, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 2}]}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;# Same decoded key, different source spelling
&lt;/span&gt;    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\u006eame&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;second&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;invalid_cases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;load_unique_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rejected:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;AssertionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Duplicate key was not rejected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third case is particularly easy to overlook.&lt;/p&gt;

&lt;p&gt;In JSON, &lt;code&gt;\u006e&lt;/code&gt; represents the letter &lt;code&gt;n&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So these two names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"name"
"\u006eame"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;decode to the same string.&lt;/p&gt;

&lt;p&gt;A simple search for repeated quoted text can miss the duplicate. The parser-based approach checks the decoded names instead.&lt;/p&gt;

&lt;p&gt;This test is about exact decoded-key equality. It does not attempt to detect visually similar Unicode characters or perform Unicode normalization.&lt;/p&gt;

&lt;h2&gt;
  
  
  One limitation worth knowing
&lt;/h2&gt;

&lt;p&gt;Our Python function detects duplicate keys, but its error only reports the member name.&lt;/p&gt;

&lt;p&gt;It does not tell you the exact path, line, or column.&lt;/p&gt;

&lt;p&gt;For a large configuration containing many objects with fields named &lt;code&gt;id&lt;/code&gt; or &lt;code&gt;timeout&lt;/code&gt;, that may not be enough to locate the source.&lt;/p&gt;

&lt;p&gt;A production-grade diagnostic tool should ideally report something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Duplicate member:
  path: $.limits.max_rows
  first occurrence: line 4
  second occurrence: line 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That requires retaining more source-location information than this short example does.&lt;/p&gt;

&lt;p&gt;For a small configuration or a CI test, though, rejecting the document is already much better than silently choosing a value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put the check where data enters your application
&lt;/h2&gt;

&lt;p&gt;For a configuration-loading pipeline, my preferred order is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the original JSON text.&lt;/li&gt;
&lt;li&gt;Reject duplicate object-member names.&lt;/li&gt;
&lt;li&gt;Parse the accepted document.&lt;/li&gt;
&lt;li&gt;Validate its schema and application-specific constraints.&lt;/li&gt;
&lt;li&gt;Only then use it to configure the application.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the Python example, steps 2 and 3 happen together.&lt;/p&gt;

&lt;p&gt;The important point is to avoid discarding evidence before you have checked the rule you care about.&lt;/p&gt;

&lt;p&gt;A browser-based syntax checker is still useful for malformed JSON. For example, I maintain a &lt;a href="https://datatoolforge.com/tools/json-validator" rel="noopener noreferrer"&gt;JSON Validator on DataToolForge&lt;/a&gt; for ordinary syntax and structure checks. That is a different task from the duplicate-key rejection implemented in this article.&lt;/p&gt;

&lt;p&gt;A successful parse is useful evidence that the syntax was accepted.&lt;/p&gt;

&lt;p&gt;It is not evidence that every member of the original document survived.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8259.html#section-4" rel="noopener noreferrer"&gt;RFC 8259 — Section 4: Objects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/json.html" rel="noopener noreferrer"&gt;Python documentation: &lt;code&gt;json&lt;/code&gt; and &lt;code&gt;object_pairs_hook&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse" rel="noopener noreferrer"&gt;MDN: &lt;code&gt;JSON.parse()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The configuration example is synthetic. The JavaScript and Python examples were tested with Node.js 22.16.0 and Python 3.13.5.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: DataToolForge is my project. AI assistance was used to draft, research, and test the examples in this article.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>javascript</category>
      <category>node</category>
      <category>programming</category>
    </item>
    <item>
      <title>JSON to XML: Why an Array Needs a Root Element</title>
      <dc:creator>Chan Lebron</dc:creator>
      <pubDate>Tue, 06 Oct 2026 23:48:24 +0000</pubDate>
      <link>https://dev.to/chan_lebron_61e9204b0aa43/json-to-xml-why-an-array-needs-a-root-element-4gch</link>
      <guid>https://dev.to/chan_lebron_61e9204b0aa43/json-to-xml-why-an-array-needs-a-root-element-4gch</guid>
      <description>&lt;p&gt;A JSON object with one key can still need an extra wrapper when that key holds an array. This is the small input I use to check that case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"item"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The array contains two values. An XML document still needs one document element. The &lt;a href="https://www.w3.org/TR/xml/#sec-well-formed" rel="noopener noreferrer"&gt;XML specification&lt;/a&gt; defines that requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two items, one document
&lt;/h2&gt;

&lt;p&gt;For comparison, this deliberately broken document has two top-level elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;item&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;item&amp;gt;&lt;/span&gt;2&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those elements could be placed inside a larger document, but this text alone cannot be parsed as a well-formed XML document.&lt;/p&gt;

&lt;p&gt;The tested converter adds a wrapper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;root&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;item&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;item&amp;gt;&lt;/span&gt;2&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/root&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frp6p7wg8znoijjemjd2z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frp6p7wg8znoijjemjd2z.png" alt=" " width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The name &lt;code&gt;root&lt;/code&gt; is a mapping choice. If a receiving API expects &lt;code&gt;&amp;lt;items&amp;gt;&lt;/code&gt; or a namespace-qualified element, this output can parse successfully and still have the wrong shape for that API.&lt;/p&gt;

&lt;p&gt;Here is a small check using Python's standard &lt;a href="https://docs.python.org/3/library/xml.etree.elementtree.html" rel="noopener noreferrer"&gt;ElementTree parser&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;xml.etree.ElementTree&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ET&lt;/span&gt;

&lt;span class="n"&gt;array_xml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;root&amp;gt;
  &amp;lt;item&amp;gt;1&amp;lt;/item&amp;gt;
  &amp;lt;item&amp;gt;2&amp;lt;/item&amp;gt;
&amp;lt;/root&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromstring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_xml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;root&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;array_root: passed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parser checks the document structure. The assertions check the wrapper, element names, values and order expected by this particular mapping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a nested object and text that needs escaping
&lt;/h2&gt;

&lt;p&gt;My second fixture is a synthetic order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"customer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lee &amp;amp; Tom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;review&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"item"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"B2"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the single non-array key &lt;code&gt;order&lt;/code&gt; supplies the document element. This converter treats the &lt;code&gt;@&lt;/code&gt; prefix as an attribute convention. Its output is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;order&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"00123"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;customer&amp;gt;&lt;/span&gt;Lee &lt;span class="ni"&gt;&amp;amp;amp;&lt;/span&gt; Tom&lt;span class="nt"&gt;&amp;lt;/customer&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;note&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;review&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/note&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;item&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sku&amp;gt;&lt;/span&gt;A1&lt;span class="nt"&gt;&amp;lt;/sku&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;item&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;sku&amp;gt;&lt;/span&gt;B2&lt;span class="nt"&gt;&amp;lt;/sku&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/order&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fno0gqtbxfielvfwlsyme.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fno0gqtbxfielvfwlsyme.png" alt=" " width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;00123&lt;/code&gt; stays a string in the source. The ampersand and angle brackets are escaped in the XML text, then decoded when a parser reads it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;order_xml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;order id=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;00123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;
  &amp;lt;customer&amp;gt;Lee &amp;amp;amp; Tom&amp;lt;/customer&amp;gt;
  &amp;lt;note&amp;gt;&amp;amp;lt;review&amp;amp;gt;&amp;lt;/note&amp;gt;
  &amp;lt;item&amp;gt;
    &amp;lt;sku&amp;gt;A1&amp;lt;/sku&amp;gt;
  &amp;lt;/item&amp;gt;
  &amp;lt;item&amp;gt;
    &amp;lt;sku&amp;gt;B2&amp;lt;/sku&amp;gt;
  &amp;lt;/item&amp;gt;
&amp;lt;/order&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromstring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_xml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attrib&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;00123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findtext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lee &amp;amp; Tom&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findtext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;note&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;review&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findtext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sku&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nested_order: passed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the two Python blocks in order. The second uses the import from the first. Keep the XML escapes in the document; the parsed values show whether the text survived.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agree on the mapping before sending the result
&lt;/h2&gt;

&lt;p&gt;For an integration, I would check the receiving contract against three choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The required document element, including any namespace.&lt;/li&gt;
&lt;li&gt;How arrays are represented: repeated elements or a collection wrapper.&lt;/li&gt;
&lt;li&gt;Which fields become attributes, child elements or text.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;@id&lt;/code&gt; is a converter convention, not a rule defined by JSON. The receiving system's schema or API documentation should determine the required structure. The assertions above verify these fixtures; they do not validate an XSD or prove that another API will accept them.&lt;/p&gt;

&lt;p&gt;Both examples were run against the live &lt;a href="https://datatoolforge.com/tools/json-to-xml" rel="noopener noreferrer"&gt;DataToolForge JSON to XML converter&lt;/a&gt; on &lt;strong&gt;2026-10-07, UTC+08:00&lt;/strong&gt;, in a remote Chrome browser. The two Python blocks were also executed with &lt;strong&gt;Python 3.12.14&lt;/strong&gt;. The screenshots show the actual tool results, and all inputs are synthetic.&lt;/p&gt;

&lt;p&gt;You can paste either fixture into the converter and compare the result with the checks above. If your XML API needs a different array shape, which element names and wrapper does it require?&lt;/p&gt;

&lt;p&gt;Disclosure: DataToolForge is my project. AI assistance was used to draft, test and edit this article.&lt;/p&gt;

</description>
      <category>coding</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Excel changed my CSV IDs. Catch the problem before import.</title>
      <dc:creator>Chan Lebron</dc:creator>
      <pubDate>Mon, 05 Oct 2026 16:49:00 +0000</pubDate>
      <link>https://dev.to/chan_lebron_61e9204b0aa43/excel-changed-my-csv-ids-catch-the-problem-before-import-4hjp</link>
      <guid>https://dev.to/chan_lebron_61e9204b0aa43/excel-changed-my-csv-ids-catch-the-problem-before-import-4hjp</guid>
      <description>&lt;p&gt;A CSV can open without an error and still lose the information you needed.&lt;/p&gt;

&lt;p&gt;The awkward case is an identifier that looks like a number. An account ID of &lt;code&gt;00123&lt;/code&gt; and a long ID of &lt;code&gt;9007199254740993&lt;/code&gt; are strings of digits. If an importer turns them into numeric cells, their original spelling may not survive.&lt;/p&gt;

&lt;p&gt;For identifiers, keep the text until you have a reason to convert it. Adding a numeric format later cannot recover digits that were already lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small file that exposes the problem
&lt;/h2&gt;

&lt;p&gt;Save this as &lt;code&gt;import-check.csv&lt;/code&gt;, or use it as pasted text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;account_id,name,comment
00123,Alice,Ready
9007199254740993,Bob,=1+1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two records. Three values deserve review before a spreadsheet import:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;00123&lt;/code&gt; needs its leading zeros preserved.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;9007199254740993&lt;/code&gt; needs all of its digits preserved.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;=1+1&lt;/code&gt; may be interpreted as a spreadsheet formula rather than displayed as the original text.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The formula is deliberately harmless. These rows use synthetic data, so the example does not need real customer records.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the source before blaming the export
&lt;/h2&gt;

&lt;p&gt;Python's standard CSV reader keeps these fields as text unless your own code converts them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;account_id,name,comment
00123,Alice,Ready
9007199254740993,Bob,=1+1
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StringIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;account_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;00123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;account_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9007199254740993&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;comment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=1+1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result includes quoted JSON strings for both IDs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"comment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ready"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9007199254740993"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"comment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"=1+1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you a reference to compare with the imported data. CSV quoting handles CSV structure; it is not a reliable instruction to a spreadsheet to use Text cells.&lt;/p&gt;

&lt;h2&gt;
  
  
  Excel and JavaScript have different numeric limits
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://support.microsoft.com/en-us/excel/keeping-leading-zeros-and-large-numbers" rel="noopener noreferrer"&gt;Microsoft documents a 15-significant-digit limit for Excel numeric values&lt;/a&gt;. Long account numbers should be imported as text when every digit matters. Leading zeros can also disappear when a field is converted to a number.&lt;/p&gt;

&lt;p&gt;JavaScript has a different boundary. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER" rel="noopener noreferrer"&gt;Number.MAX_SAFE_INTEGER&lt;/a&gt; is &lt;code&gt;9007199254740991&lt;/code&gt;. Try this in a JavaScript console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{"account_id":9007199254740993}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// 9007199254740992&lt;/span&gt;

&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{"account_id":"9007199254740993"}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// "9007199254740993"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both JSON documents have valid syntax. Only the second represents this identifier as a string. Keeping an ID quoted avoids this particular Number conversion; calculations on genuinely numeric values need their own precision policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the import type before loading the column
&lt;/h2&gt;

&lt;p&gt;Use Excel's text/CSV import flow instead of relying on a double-click to choose types for you. In Power Query, set identifier columns to Text before numeric conversion loses information. Check any automatically added type-conversion step: changing a rounded value back to text does not restore the original ID. Menu labels vary between Excel versions.&lt;/p&gt;

&lt;p&gt;After loading, compare the two IDs with the source. Check the actual cell value rather than judging only its display. If you export the workbook to CSV, close it and inspect the exported file too. A workflow that preserves an ID on the first open can still change it in a later step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review formula-like values separately
&lt;/h2&gt;

&lt;p&gt;Preserving an ID is not the same task as preventing formula interpretation. The &lt;code&gt;comment&lt;/code&gt; field demonstrates why: &lt;code&gt;=1+1&lt;/code&gt; can parse as a CSV string and later act as a formula in a spreadsheet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.owasp.org/attacks/CSV_Injection" rel="noopener noreferrer"&gt;OWASP's CSV injection guidance&lt;/a&gt; covers formula prefixes and explains why escaping must be tested against the destination. There is no universal rewrite that suits every spreadsheet and downstream consumer. A warning should show the original cell and let you choose a destination-specific treatment.&lt;/p&gt;

&lt;p&gt;To try the example without uploading a file, open &lt;a href="https://datatoolforge.com/tools/excel-import-preflight" rel="noopener noreferrer"&gt;DataToolForge's Excel Import Preflight&lt;/a&gt;, paste the sample and run the check. The report is intended to surface import risks, not prove that a file satisfies every business rule. A broader &lt;a href="https://datatoolforge.com/tools/csv-import-preflight" rel="noopener noreferrer"&gt;CSV Import Preflight&lt;/a&gt; also checks file structure.&lt;/p&gt;

&lt;p&gt;The useful habit is simple: compare the source text, the imported values and the next exported file. Check exact IDs and formula-like cells before using the data in another system.&lt;/p&gt;

&lt;p&gt;Disclosure: DataToolForge is my project. AI assistance was used to draft and edit this article.&lt;/p&gt;

</description>
      <category>data</category>
      <category>tools</category>
    </item>
    <item>
      <title>CSV Formula Injection: Why =, +, -, and @ Can Be Dangerous</title>
      <dc:creator>Chan Lebron</dc:creator>
      <pubDate>Tue, 29 Sep 2026 12:26:23 +0000</pubDate>
      <link>https://dev.to/chan_lebron_61e9204b0aa43/01-csv-formula-injectionmd-4npd</link>
      <guid>https://dev.to/chan_lebron_61e9204b0aa43/01-csv-formula-injectionmd-4npd</guid>
      <description>&lt;p&gt;CSV looks simple, but a file can be structurally valid and still contain values that behave unexpectedly when opened in spreadsheet software.&lt;/p&gt;

&lt;h2&gt;
  
  
  CSV syntax is not the whole story
&lt;/h2&gt;

&lt;p&gt;CSV itself does not define formulas. A parser may see &lt;code&gt;=1+1&lt;/code&gt; as an ordinary string, while spreadsheet software may interpret the same value as a formula after import.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;id,value
1,=1+1
2,+10+20
3,@SUM(A1:A2)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing in that example is necessarily malformed CSV. The risk appears later, when another application decides how to interpret the cell value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Formula-like values matter most when CSV data contains user-controlled or externally supplied content. Typical sources include form submissions, CRM exports, support tickets, marketplace data, and administrative reports.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;username,comment
alice,Looks good
bob,"=HYPERLINK(""https://example.com"",""Click"")"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A parser can accept this file while a spreadsheet application may treat the second row differently from plain text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Characters worth checking
&lt;/h2&gt;

&lt;p&gt;Common spreadsheet formula-triggering prefixes include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=
+
-
@
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A match is not automatically malicious. &lt;code&gt;-12&lt;/code&gt; may be a negative number, and &lt;code&gt;+44&lt;/code&gt; may be part of a phone number.&lt;/p&gt;

&lt;p&gt;Spreadsheet behavior also varies by application and locale. Security guidance such as OWASP's CSV Injection material additionally calls out tab, carriage return, line feed, and some full-width variants as cases worth considering.&lt;/p&gt;

&lt;p&gt;That is why reporting the risk and testing against the target spreadsheet application is usually better than assuming one universal rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why automatic sanitization can be risky
&lt;/h2&gt;

&lt;p&gt;One defensive technique is to prefix a value with an apostrophe. That may force some spreadsheet applications to treat the value as text, but it also changes the original dataset.&lt;/p&gt;

&lt;p&gt;For exports, migrations, and audit data, silent rewriting may be undesirable. Some spreadsheet applications may also transform quoting or escaping when a CSV file is saved and reopened.&lt;/p&gt;

&lt;p&gt;A safer workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;parse the CSV;&lt;/li&gt;
&lt;li&gt;identify formula-like cells;&lt;/li&gt;
&lt;li&gt;show the row, column, and original value;&lt;/li&gt;
&lt;li&gt;let the user decide whether the value is intentional;&lt;/li&gt;
&lt;li&gt;apply a mitigation appropriate to the target spreadsheet application.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Formula risk is different from malformed CSV
&lt;/h2&gt;

&lt;p&gt;An unclosed quote is a structural CSV error. A quoted value such as &lt;code&gt;"=1+1"&lt;/code&gt; can still be structurally valid while remaining interesting from a spreadsheet-risk perspective.&lt;/p&gt;

&lt;p&gt;Successful parsing does not guarantee safe downstream interpretation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;A CSV file can be valid and still contain values that downstream software interprets in surprising ways.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reference
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://owasp.org/www-community/attacks/CSV_Injection" rel="noopener noreferrer"&gt;OWASP: CSV Injection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Originally published on &lt;a href="https://datatoolforge.com/guides/csv-formula-injection" rel="noopener noreferrer"&gt;DataToolForge&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI-assisted disclosure: I used AI as a drafting/editing assistant and reviewed the technical content before publication.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>csv</category>
      <category>security</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
