DEV Community

Cover image for CSV generation from JSON in Svelte
Nikhil karkra
Nikhil karkra

Posted on

CSV generation from JSON in Svelte

Svelte is the new big thing in the market and I decided to try one common use case i.e. CSV generation from JSON. For those who don't know svelte

"Svelte is a radical new approach to building user interfaces. Whereas traditional frameworks like React and Vue do the bulk of their work in the browser, Svelte shifts that work into a compile step that happens when you build your app."

There are several ways to setup Svelte project. You can read more about the many ways to get started here. For the purpose of this demo, we will be working with degit which is a software scaffolding tool. To start, run the following command:

npx degit sveltejs/template svelte-CSV-demo

Now go inside the project directory using following command:

cd svelte-CSV-demo

let's install the project dependencies using following command:

npm install

Now our Svelte base project is ready. Let's start writing our code.

We have four part of our project

  1. load the JSON from REST API
  2. Integrate the JSON with template
  3. Add style to project
  4. CSV generation utility
  5. End to End integration
  6. Deploying to the web With now

If you are interested only in Code you can checkout the code from below URL
https://github.com/karkranikhil/svelte-csv-demo

1. load the JSON from REST API
Go to App.svelte file and remove the existing code with the below code

<script>
  import { onMount } from "svelte";

  let tableData = [];

  onMount(async () => {
    const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
    tableData = await res.json();
    console.log(tableData);
  });

</script>

As shown above, we have imported the onMount from svelte package.
onMount is fired after the component is rendered. After that we have initialized the variable tableData with an empty array.
Now we have defined the onMount function and within that we have used the async & await .

  • async functions returns a promise.
  • async functions use an implicit Promise to return its result. Even if you don’t return a promise explicitly async function makes sure that your code is passed through a promise.
  • await blocks the code execution within the async function, of which it(await statement) is a part.

We have used Fetch API to get the JSON from the service. The Fetch API is a promise-based JavaScript API for making asynchronous HTTP requests in the browser. On successful calling of REST API we are storing the JSON in tableData and printing it in console.

Let'run the project and see the console. To start the project run the following command.

npm run dev

once Above command run successfully navigate to http://localhost:5000/.
Open your developer console and you will see the following output.
Alt Text

If you look at the above image we are able to get the data successfully. Now we will go to next step and will see how to integrate it with HTML markup

2. Integrate the JSON with template
Now we already have our API data in tableData variable. Now we will integrate the data using #each iterator. Add the following code to App.svelte below script tag

<div class="container">
  <div class="header">
    <h1>CSV generation from JSON in Svelte</h1>
  </div>
  <div class="main">
    <table>
      <thead>
        <tr>
          {#each tableHeader as header}
            <th>{header}</th>
          {/each}
        </tr>
      </thead>
      <tbody>
        {#each tableData as item}
          <tr>
            <td>{item.userId}</td>
            <td>{item.id}</td>
            <td>{item.title}</td>
            <td>{item.body}</td>
          </tr>
        {/each}
      </tbody>
    </table>

  </div>
</div>

Above we have created the div with class container that hold two child one with header class another with main class. In div with header class we are only showing the header of our app. In div with main class we are creating the table and within the table we are creating table header and table body using #each block. #each loops the data in markup.
We are using two loop one for header and another for the body. For table body we are using tableData that contains the REST API response and for header we are using the tableHeader variable that will create now under the script tag.
let's define the tableHeader below tableData and initializing it with the array of custom header keys as shown below.

let tableHeader = ["User Id", "ID", "Title", "Description"];

Let's run the project again if it's stop otherwise go to browser and you will see the following output.

Alt Text

3. Add style to project
I have define some CSS to make our page look better. you can use it by adding the style tag after the markup

<style>
  .container {
    max-width: 1140px;
    margin: auto;
  }
  .header {
    display: flex;
    justify-content: space-between;
    display: flex;
    justify-content: space-between;
    background: orange;
    padding: 10px;
  }
  table {
    font-family: arial, sans-serif;
    border-collapse: collapse;
    width: 100%;
  }

  td,
  th {
    border: 1px solid #dddddd;
    text-align: left;
    padding: 8px;
  }

  tr:nth-child(even) {
    background-color: #dddddd;
  }
  button {
    border: none; /* Remove borders */
    color: white; /* Add a text color */
    padding: 14px 28px; /* Add some padding */
    cursor: pointer; /* Add a pointer cursor on mouse-over */
    background-color: #4caf50;
    height: fit-content;
  }
  h1 {
    margin: 0px;
  }
</style>

Now if you look at the output, it will look like as shown below

Alt Text

4.CSV generation Utility

Here is the key step in which we have wrote some Utility that will generate the csv based on some parameters. It works with all browser and even on all mobile phones.

So, let's create a new file csvGenerator.js inside the src folder and paste the below code in it.

export const csvGenerator = (totalData,actualHeaderKey,headerToShow,fileName) => {
  let data = totalData || null;
  if (data == null || !data.length) {
    return null;
  }
  let columnDelimiter = ",";
  let lineDelimiter = "\n";
  let keys = headerToShow;
  let result = "";
  result += keys.join(columnDelimiter);
  result += lineDelimiter;
  data.forEach(function(item) {
    let ctr = 0;
    actualHeaderKey.forEach(function(key) {
      if (ctr > 0) result += columnDelimiter;
      if (Array.isArray(item[key])) {
        let arrayItem =
          item[key] && item[key].length > 0
            ? '"' + item[key].join(",") + '"'
            : "-";
        result += arrayItem;
      } else if (typeof item[key] == "string") {
        let strItem = item[key] ? '"' + item[key] + '"' : "-";
        result += strItem ? strItem.replace(/\s{2,}/g, " ") : strItem;
      } else {
        let strItem = item[key] + "";
        result += strItem ? strItem.replace(/,/g, "") : strItem;
      }

      ctr++;
    });
    result += lineDelimiter;
  });

  if (result == null) return;

  var blob = new Blob([result]);
  if (navigator.msSaveBlob) {
    // IE 10+
    navigator.msSaveBlob(blob, exportedFilenmae);
  } else if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
    var hiddenElement = window.document.createElement("a");
    hiddenElement.href = "data:text/csv;charset=utf-8," + encodeURI(result);
    hiddenElement.target = "_blank";
    hiddenElement.download = fileName;
    hiddenElement.click();
  } else {
    let link = document.createElement("a");
    if (link.download !== undefined) {
      // Browsers that support HTML5 download attribute
      var url = URL.createObjectURL(blob);
      link.setAttribute("href", url);
      link.setAttribute("download", fileName);
      link.style.visibility = "hidden";
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    }
  }
};

As shown above, we have created a function called csvGenerator. That takes four paramters as mentioned below

totalData - totalData is the JSON data to pass to CSV sheet
actualHeaderKey - This is the array of JSON key name that need to be used to pick up data from totalData
headerToShow - This is the array of custom name to show on header row of the csv file
fileName -Name of the file by which it get download with an extension of .csv

csvGenerator function will take the input and generate the CSV output by looping the data and adding comma to each value.

5. End to End integration

Till now we are ready with table and csvGenerator. Let's connect both together.
First we need to import the csvGenerator file to our App.svelte. Add the following line below the onMount import statement

import { csvGenerator } from "./csvGenerator";

Now we need a handler that will get called on click of button from markup and call our utility csvGenerator. Add the following code below onMount function

function downloadHandler() {
let tableKeys = Object.keys(tableData[0]); //extract key names from first Object
csvGenerator(tableData, tableKeys, tableHeader, "svelte_csv_demo.csv");
}

As shown above, we have created a function called downloadHandler that will called on click of button and generate the CSV file of table data.

Let's create now a button on our template. Add the following code below the the h1 tag

<button on:click={downloadHandler}>Download</button>

and run the project and you will see the below output on your browser.

Alt Text

On click of download button it will download the CSV in your machine.

4. Deploying to the web With now

Install now if you haven't already:

npm install -g now

Then, from within your project folder:

cd public
now deploy --name svelte-csv-demo

now will deploy your code and generate a URL.

Deployed Url - https://svelte-csv-demo.karkranikhil.now.sh

Github - https://github.com/karkranikhil/svelte-csv-demo


References

https://svelte.dev/

Top comments (2)

Collapse
 
inkones profile image
Ines Kondor

Thank you very much Nikhil. Super useful.

How would the "csv part" work with nested json?

For example:
In the "table part" I am rendering a user name like this:

{item.user.details.name}

Suspect that will require significant changes in the csvGenerator.js - or is there a smarter way?
let tableKeys = ?

Collapse
 
rwoodnz profile image
Richard Wood

Very useful. Thanks.