JSON Feed is a specification for representing an RSS-style feed in JSON. I wanted to add one as an alternative alongside the Atom feed on a new website I’m building. The website is built with Zola, which unfortunately doesn’t support the format, so this is how I went about adding one.
My first idea was to add a Tera template json.feed
and try to generate JSON
in the template. This was foiled on multiple fronts:
- Zola only loads files matching
*.{*ml,md}
androbots.txt
as templates, so it didn’t render myjson.feed
template. - Tera has a
json_encode
filter but does not support object literals, which makes building well formed JSON tricky.
My next thought was to write a tool to convert the Atom feed to JSON. Rust is
my scripting language of choice these days (only a tiny bit joking) but I
didn’t feel like adding a Rust project and build step for this. I then wondered
if it could be done with jq, or in my case jaq—a jq
implementation in
Rust. Short answer it can. Here’s what I came up with:
I created templates/dump.xml
with these contents:
{{ __tera_context }}
This dumps the whole Tera context as JSON when rendered into public/dump.xml
.
It’s .xml
so that Zola will render it as a template. I added it to
feed_filenames
in the Zola config.toml
:
feed_filenames = [
"atom.xml",
"dump.xml", # HACK: This just dumps the Tera context as JSON
]
Then I wrote this jaq
filter (json-feed.jaq
) to generate a JSON Feed
feed.json
from the rendered version of the template.
# vim: ft=jq
# Generate a JSON feed from context of a Zola feed template
{
"version": "https://jsonfeed.org/version/1.1",
"title": .config.title,
"home_page_url": .config.base_url,
"feed_url": .feed_url | sub("dump\\.xml$"; "feed.json"),
"authors": [ { "name": .config.author } ],
"language": "en-AU",
"items": .pages | map({
"id": .permalink,
"url": .permalink,
"title": .title,
"content_html": .content,
"date_published": .date,
"tags": .taxonomies.tags,
} + if .updated then {"date_modified": .updated} else {} end
+ if .extra.link then {"external_url": .extra.link} else {} end)
}
It turned out quite nice. The main challenge with the jaq
filter was working
out how to conditionally include a key only if the value was non-null. I ended
up with the trailing if-then-else-end expressions. If there’s a better way
I’d be keen to hear about it.
Finally, to coordinate all this I added a Makefile
that deletes dump.xml
afterwards:
help:
@echo "Available tasks:"
@echo
@echo "- build"
@echo "- deploy"
build:
zola build
jaq --from-file json-feed.jaq public/dump.xml > public/feed.json
rm public/dump.xml
deploy:
@echo todo
.PHONY: build depoly