.../articles/
Handling camelCase Requests in Rails

Handling camelCase Requests in Rails

2020.10.12

Background

In a previous article, we talked about moving development forward with OpenAPI as the foundation.

During the frontend integration phase, we received a request: "Could you make the JSON keys in requests and responses camelCase?"

To explain briefly, the example above is snake_case, which is commonly used for variable names in Ruby. The example below, on the other hand, is called camelCase, which is commonly used in JavaScript and similar languages.

{"first_name": "foo", "last_name": "bar", "note": "snake case"}
{"firstName": "foo", "lastName": "bar", "note": "camel case"}

*Naming conventions for variables aren't governed by any absolute rule, but there tend to be preferred patterns depending on the programming language.

When you build a straightforward API server in Rails that returns JSON, both requests and responses will basically use snake_case keys. However, the frontend wants to use camelCase keys, and converting between the two every time you send a request or parse a response is a hassle.

So we looked into how to handle JSON keys as camelCase in requests and responses in Rails.


What we did

With that in mind, we considered separate approaches for responses and requests, while keeping the following in mind:

  • Use snake_case as much as possible within the Rails codebase
  • Consolidate the conversion logic between camelCase and snake_case as much as possible
  • Avoid any significant impact on performance

Response

It might make more sense to start with the request side, but handling the response turned out to be simpler, so we'll introduce that first. That said, this assumes you're using ActiveModelSerializers.

If you're using ActiveModelSerializer, you can easily change the key pattern of the response by specifying key_transform. Referring to the documentation below, in our case all we need to do is specify :camel_lower.

Request

Say you send a POST request with camelCase JSON keys, as in the earlier example.

{"firstName": "foo", "lastName": "bar", "note": "camel case"}

What should we do if we want this to end up as snake_case data when Rails processes it, as shown below?

{"first_name": "foo", "last_name": "bar", "note": "snake case"}

Ideally, we'd want the conversion to already be done by the time the controller handles the request parameters, so we looked into it and found a question with the same intent on Stack Overflow, along with an answer.

Here's the code excerpted from that answer (assuming Rails version 6).

# File: config/initializers/json_param_key_transform.rb
# Transform JSON request param keys from JSON-conventional camelCase to
# Rails-conventional snake_case:
ActionDispatch::Request.parameter_parsers[:json] = lambda { |raw_post|
  # Modified from action_dispatch/http/parameters.rb
  data = ActiveSupport::JSON.decode(raw_post)

  # Transform camelCase param keys to snake_case
  if data.is_a?(Array)
    data.map { |item| item.deep_transform_keys!(&:underscore) }
  else
    data.deep_transform_keys!(&:underscore)
  end

  # Return data
  data.is_a?(Hash) ? data : { '_json': data }
}

When we tried this as written, we were able to receive request parameters where the JSON keys had been converted from camelCase to snake_case.

So what exactly is this code doing?

Modified from action_dispatch/http/parameters.rb

Using this comment as a clue, we checked the Rails source code.

In it, DEFAULT_PARSERS is defined, which appears to define the method that parses JSON.

      DEFAULT_PARSERS = {
        Mime[:json].symbol => -> (raw_post) {
          data = ActiveSupport::JSON.decode(raw_post)
          data.is_a?(Hash) ? data : { _json: data }
        }
      }

In other words, we understood that the code introduced in that article overrides the JSON-parsing method using deep_transform_keys!(&:underscore), forcing the keys of the parsed parameters into snake_case.


Summary

By configuring requests and responses as described above, we were able to have Rails work with snake_case keys internally while still behaving, from the perspective of the API client, as if the keys were in the client's preferred case.

Naturally, this can be adjusted for conventions other than camelCase too, so it should be easy to switch depending on the situation.

Parameters other than JSON

Besides request body parameters, Rails also deals with path parameters and query parameters.

Path parameters are embedded in the URL and don't have keys to speak of, so there's no issue as long as they're defined in snake_case in the Rails routes.

Query parameters, on the other hand, have keys specified by the client, so the same problem can occur there — but they aren't converted by the configuration above.

# The client uses userId, but Rails wants to treat it as user_id
GET /notes?userId=1

The example above is a bit contrived, so it might be solvable by rethinking the path design to avoid query parameters altogether.

However, if query parameters do become necessary, we'd like to avoid forcing the client to partially use snake_case, or introducing a constraint where key names can only be a single word.

We're planning to keep looking into a good way to resolve this.

Addendum

Looking into it further, it seems there's an approach using before_action to convert the entire parameter set at once. This is closer to what I originally had in mind, and it seems like a clearer approach. (Untested)

We haven't decided which approach to take yet, so we'll keep exploring and figure it out as we go.

written by

.../article/

Articles

All articles

From Firebase to Vercel, Contentful to microCMS — a migration log written with Claude Code

From Firebase to Vercel, Contentful to microCMS — a migration log written with Claude Code

We moved our corporate site's hosting and CMS, and made it bilingual along the way. The constraints we only found by running against real data were more useful than the migration itself, so this post focuses on where we got stuck.

Can't Read POST Data with Firebase Functions × Remix?

Can't Read POST Data with Firebase Functions × Remix?

How to read POST data from a Remix action when running on Firebase Functions.

Generative AI for Executives and Leaders: An Approach to Self-Driven DX

Generative AI for Executives and Leaders: An Approach to Self-Driven DX

Building a structure where executives and leaders themselves can identify issues and evaluate solutions using generative AI. We introduce how combining this with our hands-on support dramatically improves both the quality and speed of digital transformation.

Deploying a Monorepo Next.js App (App Router) to AWS Amplify

Deploying a Monorepo Next.js App (App Router) to AWS Amplify

Notes on the obstacles we hit while deploying a Next.js app managed in a monorepo to AWS Amplify.

Keeping Production Running Smoothly with Remote Work and Online Meetings [Documentation]

Keeping Production Running Smoothly with Remote Work and Online Meetings [Documentation]

Many production companies have adopted remote work as a result of the pandemic, and we are one of them.

Designing an E-Commerce Site That Sells: How to Find Great Reference Examples

Designing an E-Commerce Site That Sells: How to Find Great Reference Examples

There is no single formula for e-commerce design that sells. Driving revenue requires a solid concept, and getting to that concept requires thorough research.

Productivity Tools We Recommend as a Production Company, Including Services That Work Well Solo

Productivity Tools We Recommend as a Production Company, Including Services That Work Well Solo

With remote work becoming the norm during the COVID-19 pandemic, our team now works from home most days of the week.

We Released Thought Recorder, a Figma Plugin for Keeping a Commit History of Your Designs

We Released Thought Recorder, a Figma Plugin for Keeping a Commit History of Your Designs

We hope this helps web designers who work in Figma. Read on for how to use it.

How to Build an E-Commerce Site, and Which Platforms We Recommend

How to Build an E-Commerce Site, and Which Platforms We Recommend

Shopping online for fashion, appliances, and even groceries is now routine. With the pandemic accelerating the shift, we receive a steady stream of questions about which platform to use and how much it costs.

Generating FastAPI Schema Classes from OpenAPI

Generating FastAPI Schema Classes from OpenAPI

We chose FastAPI, a relatively modern framework, for a Python API project. FastAPI can generate an OpenAPI definition from your backend code, but here we do the opposite: generating FastAPI schema classes from an OpenAPI definition prepared in advance.

View all articles

Contact us