If you ever have a case where you want to convert JSON to CSV, you may have come across the json2csv ibrary by Zemirco. The library offers a simple way to stringify your JSON data to a CSV-compatible string quickly.

The only issue with this library though, well in my case anyway is that the documentation can be a bit confusing. As a result, I felt forced to dig through the code and find out what json2csv was doing behind the scenes.

Creating Custom Transforms and Defaults

Transforms are a preprocessing step for json2csv, that uses built-in default transformers to manipulate your data as needed. The issue is that there are technically only two built-in transforms:

  • unwind – deconstruct an array field to create a row for each element
  • flatten – takes nested documents and merges them into a single object. A separator is used to create the column names.

While these are generally great for most use cases, you may have a standardized representation of your JSON data that doesn’t fit the provided transforms.

That’s where custom transforms come in.

The Custom Transform

I’ll be using a sample of json2csv’s doc example to create a transformer that will format

import { Parser } from '@json2csv/plainjs';
import { unwind } from '@json2csv/transforms';

const data = [
  { "carModel": "Audi", "price": 0, "colors": ["blue","green","yellow"] },
  { "carModel": "BMW", "price": 15000, "colors": ["red","blue"] },
  { "carModel": "Tesla", "price": 50000, "colors": []}
];

try {
  const opts = {
    transforms: [
      unwind({ paths: ['colors'] })
    ]
  };
  const parser = new Parser(opts);
  const csv = parser.parse(data);
  console.log(csv);
} catch (err) {
  console.error(err);
}

Transforms are run sequentially and use the previous transform function’s results. To create a custom transform we can do the following:

const generateID = () => Math.floor(Math.random() * max);

const serializeItems = () => {

return (item) => {
id: generateID(),
...item,
}
}

This function will serialize and add IDs to each row in the table. This works great. But what if you want to change the header because you want “id” to exist but not the word ‘id’ to show up in the CSV header?

You might assume that’s done via a transform, but unfortunately, that isn’t the case. If there are fields that you want to run special transformations on, you need to format them. Not transform them. That’s not apparent from the docs. But I’ll share an example.

Creating Custom Header Formatters in json2csv

Now we’ll talk about json2csv’s fields. This will involve a two-part process, one we need to get the list of fields. Two we need to create a formatting function to hide and show fields in the header.

If you are interested in showing or hiding certain fields in json2csv, you’ll need to do that using the “fields” option.

We need to build our own function and in that function, we need to do the following steps:

  1. Filter all the fields we would like to show on the CSV header.
  2. Loop through each field and look for the ‘id’ field.
  3. Replace the id field value with an empty string.

The algorithm is simple but the implementation can be a bit tricky, I had to play around a bit with the code to make it work. In the end, luckily I was able to!

Parser

Here’s the setup for the Parser, I rewrote the functions for only Typescript, but if you use Vue you can read how to set that up with Typescript:

const keyToFormat = 'id';

const parser = new Parser({
          fields: getFields(columns as string[], 'some_col'),
          delimiter: ',',
          header: true,
          formatters: {
            undefined: defaultFormatter,
            header: formatHeader({keyToFormat}),
          },
        });

In the constructor, we have getFields() which will first get the fields for us. Then we add into the formatters option we add ‘header’. header takes a custom formatting function called formatHeader with any key we want to hide.

We can extend the function to take more keys, but let’s keep it simple.

The Formatter

Here’s the getFields() function:

const getFields = (column: string[], excludedColumn: string) => {
    return column.filter((col) => col !== excludedColumn);
};

The real magic happens in the formatHeader()

const formatHeader = (options) => {
  const {primaryKey} = options;
  return (value: string) => {
    if (primaryKey === '') return `"${value}"`;
    const clearColumnTitle = new RegExp(`${primaryKey}`, 'g');
    if (value.includes(primaryKey)) {
      return value.replace(clearColumnTitle, `""`);
    }
  };
};

Let’s explain what happens here.

First, we get the primary key we are going to use. In our case ‘id.’

Second, we wrap a function with the current field being passed and do a check. We check if the primaryKey is empty and return the value as a formatted CSV string. We need to wrap the value around quotes to make it CSV friendly.

Third, we create a regular expression with the primary key, this way we can do a check to see if the value has the primary key text within. Then we replaced the column text with an empty string.

That’s it. Now if you try to parse your JSON data. You will get your header formatted just as we expected. I hope you like this article, and if it was helpful to you in your learning feel free to share and leave a comment below!