12

I'd like to make Jekyll create an HTML file and a JSON file for each page and post. This is to offer a JSON API of my Jekyll blog - e.g. a post can be accessed either at /posts/2012/01/01/my-post.html or /posts/2012/01/01/my-post.json

Does anyone know if there's a Jekyll plugin, or how I would begin to write such a plugin, to generate two sets of files side-by-side?

j0k
  • 22,600
  • 28
  • 79
  • 90
Robin Winslow
  • 10,908
  • 8
  • 62
  • 91
  • Why create a JSON version? So someone has access to only the raw content? – the Tin Man Feb 07 '13 at 20:00
  • 1
    Does it matter why? Exposing your content in multiple formats is pretty useful, and JSON is the standard for APIs. What I had in mind was writing some front-end JavaScript using something like Backbone.js to pull in the content dynamically. This might help make the blog available offline using Cache Manifest. – Robin Winslow Feb 07 '13 at 22:55
  • 6
    I asked because it's very difficult to tell from people's questions what their level of competency is. Some ask for things because they don't know what they want to do. It's very conceivable someone would want to put HTML inside their JSON, thinking that'd be a useful thing. It was difficult to tell what you wanted, so I asked. – the Tin Man Feb 08 '13 at 04:21
  • @theTinMan cool. Yes it is. I know a lot of JavaScript and some Ruby, but don't really know where to start with extending Jekyll to do something like this, or if it's possible. – Robin Winslow Feb 08 '13 at 09:10
  • In any case, the challenging bit here is not outputting JSON, that's just a template formatting issue - or at worst a [markdown parsing one](https://github.com/sheremetyev/markdown-json). The bit I don't know how to do is make Jekyll output from multiple templates for the same page. By default it will use a single template from the `layout` specified in the front matter. I'd like to make it, by default, parse one page to two different locations based on two different templates. Is that possible? – Robin Winslow Feb 08 '13 at 09:55

3 Answers3

12

I was looking for something like this too, so I learned a bit of ruby and made a script that generates JSON representations of Jekyll blog posts. I’m still working on it, but most of it is there.

I put this together with Gruntjs, Sass, Backbonejs, Requirejs and Coffeescript. If you like, you can take a look at my jekyll-backbone project on Github.

# encoding: utf-8
#
# Title:
# ======
# Jekyll to JSON Generator
#
# Description:
# ============
# A plugin for generating JSON representations of your
# site content for easy use with JS MVC frameworks like Backbone.
#
# Author:
# ======
# Jezen Thomas
# jezenthomas@gmail.com
# http://jezenthomas.com

module Jekyll
  require 'json'

  class JSONGenerator < Generator
    safe true
    priority :low

    def generate(site)
      # Converter for .md > .html
      converter = site.getConverterImpl(Jekyll::Converters::Markdown)

      # Iterate over all posts
      site.posts.each do |post|

        # Encode the HTML to JSON
        hash = { "content" => converter.convert(post.content)}
        title = post.title.downcase.tr(' ', '-').delete("’!")

        # Start building the path
        path = "_site/dist/"

        # Add categories to path if they exist
        if (post.data['categories'].class == String)
          path << post.data['categories'].tr(' ', '/')
        elsif (post.data['categories'].class == Array)
          path <<  post.data['categories'].join('/')
        end

        # Add the sanitized post title to complete the path
        path << "/#{title}"

        # Create the directories from the path
        FileUtils.mkpath(path) unless File.exists?(path)

        # Create the JSON file and inject the data
        f = File.new("#{path}/raw.json", "w+")
        f.puts JSON.generate(hash)
      end

    end

  end

end
Jezen Thomas
  • 13,619
  • 6
  • 53
  • 91
6

Take a look at JekyllBot and the following code.

require 'json' 

module Jekyll

  class JSONPostGenerator < Generator
    safe true

    def generate(site)

      site.posts.each do |post|
        render_json(post,site)    
      end

      site.pages.each do |page|
        render_json(page,site)    
      end

    end

    def render_json(post, site)

      #add `json: false` to YAML to prevent JSONification
      if post.data.has_key? "json" and !post.data["json"]
        return
      end

      path = post.destination( site.source )

      #only act on post/pages index in /index.html
      return if /\/index\.html$/.match(path).nil?

      #change file path
      path['/index.html'] = '.json'

      #render post using no template(s)
      post.render( {}, site.site_payload)

      #prepare output for JSON
      post.data["related_posts"] = related_posts(post,site)
      output = post.to_liquid
      output["next"] = output["next"].id unless output["next"].nil?
      output["previous"] = output["previous"].id unless output["previous"].nil?

      #write
      #todo, figure out how to overwrite post.destination 
      #so we can just use post.write
      FileUtils.mkdir_p(File.dirname(path))
      File.open(path, 'w') do |f|
        f.write(output.to_json)
      end

    end

    def related_posts(post, site)

      related = []
      return related unless post.instance_of?(Post)

      post.related_posts(site.posts).each do |post|
        related.push :url => post.url, :id => post.id, :title => post.to_liquid["title"]
      end

      related

    end
  end
end

Both should do exactly what you want.

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
user94154
  • 16,176
  • 20
  • 77
  • 116
5

There are two ways you can accomplish this, depending on your needs. If you want to use a layout to accomplish the task, then you want to use a Generator. You would loop through each page of your site and generate a new .json version of the page. You could optionally make which pages get generated conditional upon the site.config or the presence of a variable in the YAML front matter of the pages. Jekyll uses a generator to handle slicing blog posts up into indices with a given number of posts per page.

The second way is to use a Converter (same link, scroll down). The converter will allow you to execute arbitrary code on your content to translate it to a different format. For an example of how this works, check out the markdown converter that comes with Jekyll.

I think this is a cool idea!

Jay
  • 368
  • 1
  • 3
  • 14
bwest
  • 9,182
  • 3
  • 28
  • 58
  • Wonderful. The Generator looks like the sort of thing. I'll look into this when I get a chance, and accept your answer if I get it to work. – Robin Winslow Feb 13 '13 at 13:28
  • The reason Converters don't work for this is by the time the converter plugins run, the markdown files will already be gone, replaced by html files. – Sandra Jan 20 '21 at 08:30