JsonApi::Parameters

Simple JSON:API compliant parameters translator.

Gem Version Maintainability Test Coverage

The problem

JSON:API standard specifies not only responses (that can be handled nicely, using gems like fast_jsonapi from Netflix), but also the request structure.

The solution

As we couldn't find any gem that would make it easier in Rails to use these structures, we decided to create something that will work for us - a translator that transforms JSON:API compliant request parameter strucure into a Railsy structure.

Usage

Installation

Add this line to your application's Gemfile:

gem 'jsonapi_parameters'

And then execute:

$ bundle

Or install it yourself as:

$ gem install jsonapi_parameters

Rails

Usually your strong parameters in controller are invoked this way:

def create
  model = Model.new(create_params)

  if model.save
    ...
  else
    head 500
  end
end

private

def create_params
  params.require(:model).permit(:name)
end

With jsonapi_parameters, the difference is just the params:

def create_params
  params.from_jsonapi.require(:model).permit(:name)
end

Relationships

JsonApi::Parameters supports ActiveRecord relationship parameters, including nested attributes.

Relationship parameters are being read from two optional trees:

  • relationships,
  • included

If you provide any related resources in the relationships table, this gem will also look for corresponding, included resources and their attributes. Thanks to that this gem supports nested attributes, and will try to translate these included resources and pass them along.

belongs_to

Passing a resource that is a single entity in relationships tree will make JsonApi::Parameters assume that it is a belongs_to relationship.

Without included entity

Example:

class Movie < ActiveRecord::Model
    belongs_to :director
end

Request body:

{
    data: {
        type: 'movies',
        attributes: {
          title: 'The Terminator',
        },
        relationships: {
          director: {
            data: {
              id: 682, type: 'directors'
            }
          }
        }
    }
}

Will translate to:

{
  movie: {
    title: 'The Terminator',
    director_id: 682
  }
}
With included entity:

Example:

class Movie < ActiveRecord::Model
    belongs_to :director

    accepts_nested_attributes_for :director
end

Request body:

{
    data: {
        type: 'movies',
        attributes: {
          title: 'The Terminator',
        },
        relationships: {
          director: {
            data: {
              id: 682, type: 'directors'
            }
          }
        }
    },
    included: [
        {
            type: 'directors',
            id: 682,
            attributes: { 
                name: 'Some guy'
            }
        }
    ]
}

Will translate to:

{
  movie: {
    title: 'The Terminator',
    director_attributes: { id: 682, name: 'Some guy' }
  }
}
has_many

Passing a resource that is a an array of entities in relationships tree will make JsonApi::Parameters assume that it is a has_many relationship.

Without included entity

Example:

class Movie < ActiveRecord::Model
    has_many :genres
end

Request body:

{
    data: {
        type: 'movies',
        attributes: {
          title: 'The Terminator',
        },
        relationships: {
          genres: {
            data: [{
              id: 1, type: 'genres'
            },
            {
              id: 2, type: 'genres'
            }]
          }
        }
    }
}

Will translate to:

{
  movie: {
    title: 'The Terminator',
    genre_ids: [1, 2]
  }
}
With included entity:

Example:

class Movie < ActiveRecord::Model
    has_many :genres

    accepts_nested_attributes_for :genres
end

Request body:

{
  data: {
    type: 'movies',
    attributes: {
      title: 'The Terminator',
    },
    relationships: {
      genres: {
        data: [{
                 id: 1, type: 'genres'
               }]
      }
    }
  },
  included: [
    {
      type: 'genres',
      id: 1,
      attributes: {
        name: 'Genre one'
      }
    }
  ]
}

Will translate to:

{
  movie: {
    title: 'The Terminator',
    genres_attributes: [{ id: 1, name: 'Genre one' }]
  }
}

Casing

If the input is in a different convention than :snake, you should specify that.

You can do it in two ways:

  • in an initializer, simply create initializers/jsonapi_parameters.rb with contents similar to: ```ruby # config/initializers/jsonapi_parameters.rb

JsonApi::Parameters.ensure_underscore_translation = true


 * while calling `.from_jsonapi`, for instance: `.from_jsonapi(:camel)`. **The value does not really matter, as anything different than `:snake` will result in deep keys transformation provided by [ActiveSupport](https://apidock.com/rails/v4.1.8/Hash/deep_transform_keys).**

### Plain Ruby / outside Rails

```ruby

params = { # JSON:API compliant parameters here
    # ...
}

class Translator
  include JsonApi::Parameters
end
translator = Translator.new

translator.jsonapify(params)

Casing

If the input is in a different convention than :snake, you should specify that.

You can do it in two ways:

  • by a global setting: JsonApi::Parameters.ensure_underscore_translation = true
  • while calling .jsonapify, for instance: .jsonapify(params, naming_convention: :camel). The value does not really matter, as anything different than :snake will result in deep keys transformation provided by ActiveSupport.

Mime Type

As stated in the JSON:API specification correct mime type for JSON:API input should be application/vnd.api+json.

This gems intention is to make input consumption as easy as possible. Hence, it registers this mime type for you.

License

The gem is available as open source under the terms of the MIT License.