TIDBIT: formatting JSON in a templating language
Published: 2023-09-15 14:15:41

Formatting JSON in a templating language can be difficult if there are optional fields. In particular, trailing commas can cause invalid JSON, which happens often if there are optionally defined fields.

This article shows how to make it safer to template out JSON.

The examples will be using Liquid to template out a LD+JSON snippet of a schema.org [Book](https://schema.org/Book).

TLDR: use a leading comma instead of a typical trailing comma

```liquid
{% if book.numberOfPages %}
  , "numberOfPages": {{ book.numberOfPages }}
{% endif %}
```


## Basic example

This example is pretty simple:

```liquid
{
  "@context": "http://schema.org",
  "@type": "Book",
  "name": "{{ book.name }}",
  "description": "{{ book.description }}"
}
```
The name and description are from our Liquid code. Even if there is no name and description, the JSON will render correctly.

## More fields

Because our product is a `Book`, we can add fields specific to a book, like `bookFormat`, `isbn`, and `numberOfPages`.
```liquid
{
  "@context": "http://schema.org",
  "@type": "Book",
  "name": "{{ book.name }}",
  "description": "{{ book.description }}",
  "bookFormat": "Hardback",
  "isbn": "{{ book.isbn }}",
  "numberOfPages": {{ book.numberOfPages }}
}
```

## Optional fields

What if the `bookFormat` is an `EBook` instead of a `Hardback`, what do we do with the `numberOfPages`? 

### Solution 1: default

We could use a default value:

```liquid
{
  "@context": "http://schema.org",
  "@type": "Book",
  "name": "{{ book.name }}",
  "description": "{{ book.description }}",
  "bookFormat": {{ book.bookFormat }},
  "isbn": "{{ book.isbn }}",
  "numberOfPages": {% if book.bookFormat == "EBook" %} 0 {% else %} {{ book.numberOfPages }} {% endif %}
}
```

That works, but it is semantically awkward to have a `numberOfPages` property on a electronic item.

### Solution 2: conditionally add the field

Instead of a default value, we can conditionally add the field:

```liquid
{
  "@context": "http://schema.org",
  "@type": "Book",
  "name": "{{ book.name }}",
  "description": "{{ book.description }}",
  "bookFormat": {{ book.bookFormat }},
  "isbn": "{{ book.isbn }}",
  {% if book.numberOfPages != blank %}
    "numberOfPages": {{ book.numberOfPages }}
  {% endif %}
}
```

That makes a lot more sense.

But wait! There's one issue. Do you see it?

If there is no `numberOfPages`, then we have invalid JSON because of the trailing comma!

### Solution 3: a leading comma

In order to avoid issues with a trailing comma, we can use leading commas:

```liquid
{
  "@context": "http://schema.org",
  "@type": "Book",
  "name": "{{ book.name }}",
  "description": "{{ book.description }}",
  "bookFormat": {{ book.bookFormat }},
  "isbn": "{{ book.isbn }}"
  {% if book.numberOfPages != blank %}
    ,"numberOfPages": {{ book.numberOfPages }}
  {% endif %}
}
```

Regardless of whether or not the `numberOfPages` field exists, the JSON will always be valid!

Even adding as many fields as we need, it will always be valid:

```liquid
{
  "@context": "http://schema.org",
  "@type": "Book",
  "name": "{{ book.name }}",
  "description": "{{ book.description }}",
  "bookFormat": {{ book.bookFormat }},
  "isbn": "{{ book.isbn }}"
  {% if book.numberOfPages != blank %}
    ,"numberOfPages": {{ book.numberOfPages }}
  {% endif %}
  {% if book.illustrator != blank %}
    ,"illustrator": {{ book.illustrator }}
  {% endif %}
  {% if book.abridged != blank %}
    ,"abridged": {{ book.abridged }}
  {% endif %}
}
```

## Conclusion

It's a pretty simple fix that can save you quite a lot of time debugging invalid JSON.

It helped me, I hope it helps you!