Make JSON file validator in just 5 lines 👀
Tags: webdev, javascript, tutorial, beginners
Published: 2023-07-16 01:42:56

In my previous post, I reviewed the different web APIs for working with files. In this post, we will put that knowledge to use and make a simple JSON file validator web app.

A user can go to our app, upload their JSON file, and it will tell them if it is valid or not, all from the browser. No dependencies, no call to external APIs. Just pure JavaScript and HTML.

First the boilerplate HTML, CSS, and element selectors.
```html
<div class="container">
  <h1><code>JSON</code> validator</h1>
  <input name="file" accept=".json" type="file" id="file-input" />
  <div id="message" />
</div>
```
> The attribute `type="file"` tells the browser to render a file selector instead of a normal text input.  `accept=".json"` tells the browser to only allow the user to select JSON files.

```html
<style>
  .container {
    padding-top: 10px;
    max-width: 500px;
    margin: auto;
    display: flex;
    flex-direction: column;
  }
  h1 {
    font-size: 30px;
  }
  code {
    border-radius: 3px;
    background-color: gainsboro;
    padding: 4px;
    font-size: 0.9em;
  }
  #message {
    padding-top: 20px;
    font-size: 20px;
  }
</style>
```

```html
<script type="module">
  const input = document.getElementById("file-input");
  const message = document.getElementById("message");
</script>
```

To start, add an event listener to the input's `change` event. The callback will receive an event with the `<input>` as its target. The input the user selects is accessible via the [`files` attribute](https://html.spec.whatwg.org/multipage/input.html#dom-input-files), which is a list of [`Files`](https://developer.mozilla.org/en-US/docs/Web/API/File). Since our input only takes one file, we will take the file at index 0 of the list.

```js
addEventListener("change", (e) => {
  const file = e.target.files[0];
});
```

Next, we need to get the text in the file. This is where the magic of web APIs comes in. A `File` is a wrapper around another web API, [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) (funny name, I know. It actually is shorthand for *binary large object*). So anything a `Blob` can do, a `File` can do, which is handy because `Blob` has a method, [`.text()`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/text), which returns the file content as text. Perfect.

`.text()` returns a promise, so we will make our function async and await the result.

```js
addEventListener("change", async (e) => {
  const file = e.target.files[0];
  const text = await file.text();
});
```

With the text of the file in hand, we can do our JSON validation with yet another web API available in all browsers, [`JSON.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse). This method takes a string as its argument and converts it to a JavaScript object if it is valid JSON or it will throw an error if it's not valid JSON. So all the hard work of parsing and checking the string is done for us by the browser!

> Since there is the potential of an error being thrown, we need to wrap the function call in a [try...catch](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) block.

```js
input.addEventListener("change", async (e) => {
  const file = e.target.files[0];
  const text = await file.text();
  try {
    JSON.parse(text);
    // Do something if it works
  } catch (e) {
	  // Do something if it fails
  }
});
```

If the JSON file is invalid, no code below  `JSON.parse(text)` and above `catch` will run. Instead, it will jump directly to the code inside the `catch` block. 

This means we can put the code to handle valid JSON under `JSON.parse(text)` and the code to handle invalid JSON under `catch`.

To alert the user if their JSON file is valid, we will append HTML inside of the `<div id="message" />`. 

The error thrown by `.parse()` should look something like `JSON.parse: (some generic error message) at line 2 column 3 of the JSON data`. By splitting the error message string on the word `at` we can get the location of the error in the JSON file to display to the user.

```js
input.addEventListener("change", async (e) => {
  const file = e.target.files[0];
  const text = await file.text();
  try {
    JSON.parse(text);
    message.innerHTML = `<div>Valid <code>JSON</code> ✅<div/>`;
  } catch (e) {
     message.innerHTML = `<div>Invalid <code>JSON</code> ❌ at ${e.message.split(" at ")[1]}<div/>`;
  }
});
```

And there you have it. A fully functional JSON validator. Here is the full HTML file you can copy and play around with and a [live demo](https://json-validator-rho.vercel.app) to prove that it works!

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>JSON Validator</title>
    <script type="module">
      const input = document.getElementById("file-input");
      const message = document.getElementById("message");

      input.addEventListener("change", async (e) => {
        const file = e.target.files[0];
        const text = await file.text();
        try {
          JSON.parse(text);
          message.innerHTML = `<div>Valid <code>JSON</code> ✅<div/>`;
        } catch (e) {
          message.innerHTML = `<div>Invalid <code>JSON</code> ❌ at ${e.message.split(" at ")[1]}<div/>`;
        }
      });
    </script>
    <style>
      body {
        font-size: 16px;
      }
      .container {
        padding-top: 10px;
        max-width: 500px;
        margin: auto;
        display: flex;
        flex-direction: column;
      }
      h1 {
        font-size: 30px;
      }
      code {
        border-radius: 3px;
        background-color: gainsboro;
        padding: 4px;
        font-size: 0.9em;
      }
      #message {
        padding-top: 20px;
        font-size: 20px;
        font-weight: 500;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1><code>JSON</code> validator</h1>
      <input name="file" accept=".json" type="file" id="file-input" />
      <div id="message" class="message" />
    </div>
  </body>
</html>
```