JSON templating on steroids


Keywords
api, babl, babl-template, dependency-analysis, dsl, functional, immutable, json, json-schema, preloader, rails, ruby, template-engine
License
MIT
Install
gem install babl-json -v 0.6.0

Documentation

BABL Logo

Build Status Coverage Status Gem Downloads

BABL (Bannerman API Builder Language) is a functional Ruby DSL for generating JSON in APIs.

It plays a role similar to RABL, JBuilder, Grape Entity, AMS, and many others.

Example

gem install babl-json
require 'babl'
require 'date'

Author = Struct.new(:name, :birthyear)
Article = Struct.new(:author, :title, :body, :date, :comments)
Comment = Struct.new(:author, :date, :body)

# Let's define some data
data = [
    Article.new(
        Author.new("Fred", 1990),
        'Introducing BABL',
        'Blablabla',
        DateTime.now,
        [
            Comment.new(
                Author.new("Luke", 1991),
                DateTime.now,
                'Great gem'
            )
        ]
    )
]

# Define a template
template = Babl.source {

    # A template can be stored in a variable ("inline partial") and re-used later.
    # For instance, this one serializes an Author.
    author = object(
        name: _,
        birthyear: _
    )

    # Produce a JSON object
    object(

        # Visit each article of from collection and produce a JSON object for each elements
        articles: each.object(

            # nav(:iso8601) can also be seen as a method call.
            # The method #iso8601 will be called on the date during rendering.
            date: _.nav(:iso8601),

            # '_' is a shortcut for 'nav(:title)' and 'nav(:body)'
            title: _,
            body: _,

            # You can chain another template using call()
            author: _.call(author),

            # Visit each comment, and produce a JSON object for each of them.
            comments: _.each.object(

                # In Ruby, .() is just a syntax sugar for .call()
                author: _.(author),

                # Type assertions can be (optionally) specified.
                # - They add runtime type checks
                # - They are added to JSON-Schema
                body: _.string,
                date: _.nav(:iso8601).string
            )
        )
    )
}

# All the magic happens here: the template is transformed into a fast serializer.
compiled_template = template.compile

# Serialize some data into JSON
compiled_template.json(data)

# =>
# {
#     "articles":[
#       {
#         "date":"2017-09-07T08:42:42+02:00",
#         "title":"Introducing BABL",
#         "body":"Blablabla",
#         "author":{
#           "name":"Fred",
#           "birthyear":1990
#         },
#         "comments":[
#           {
#             "author":{
#               "name":"Luke",
#               "birthyear":1991
#             },
#             "body":"Great gem",
#             "date":"2017-09-07T08:42:42+02:00"
#           }
#         ]
#       }
#     ]
#   }

# render() is like json(), but produces a Hash instead of a JSON
compiled_template.render(data)

# Output a JSON-Schema description of the template
compiled_template.json_schema

Benchmark

                                     user     system      total        real
RABL                             3.180000   0.010000   3.190000 (  3.189780)
JBuilder                         0.700000   0.000000   0.700000 (  0.708928)
BABL                             0.540000   0.000000   0.540000 (  0.540724)
BABL (compiled once)             0.410000   0.010000   0.420000 (  0.412431)
Handwritten Ruby                 0.080000   0.000000   0.080000 (  0.081407)

Results using code generation [WIP]:

                                     user     system      total        real
BABL (compiled once + codegen)   0.170000   0.000000   0.170000 (  0.168479)

See source code.

Features

Template compilation

A BABL template has to be compiled before it can be used. This approach carries several advantages:

  • Many errors can be detected earlier during the development process.
  • Partials are resolved only once, during compilation: zero overhead at render time.
  • Template fragments which are provably constant are pre-rendered at compilation time.
  • Code generation [WIP] should bring performances close to handcrafted Ruby code.

Automatic documentation (JSON schema)

BABL can automatically document a template by generating a JSON-Schema. Combined with optional type-checking assertions, it becomes possible to do some exciting things.

For instance, it is possible to generate TypeScript interfaces by feeding the exported JSON-Schema to https://github.com/bcherny/json-schema-to-typescript.

See how to generate a JSON-Schema.

Dependency analysis (automatic preloading)

Due to the static nature of BABL templates, it is possible to determine in advance which methods will be called on models objects during rendering. This is called dependency analysis. In practice, the extracted dependencies can be passed to a preloader, in order to avoid all N+1 issues.

Please note that this requires a compatible preloader implementation. At Bannerman, we are using Preeloo. It natively supports ActiveRecord associations, computed columns, and custom preloadable properties. Unfortunately, it hasn't been released publicly (yet), because it still has bugs and limitations.

Resources

License

Copyright (c) 2017 Bannerman, Frederic Terrazzoni

Licensed under the MIT license.