Search code examples
ruby-on-railsrubyjbuilder

Automatically convert hash keys to camelCase in JBuilder


I am using JBuilder version 2.4.1 and Rails 4.2.6. I am trying to serialize a complex object to JSON. The code looks as follows:

json.key_format! camelize: :lower

json.data_object @foo

@foo looks like this:

{
  key: 'value',
  long_key: 'value'
}

I expect it to be rendered as

{
  "dataObject": {
    "key": "value",
    "longKey": "value"
  }
}

But instead it keeps the original hash keys, only converting data_object into camelCase

{
  "dataObject": {
    "key": "value",
    "long_key": "value"
  }
}

So the question is: what is the proper way to camelize hash keys using JBuilder?


Solution

  • As Bryce has mentioned, Jbuilder uses to_json instead of processing the hash.

    A simple solution is to use json.set! to manually serialize the hash.

    json.key_format! camelize: :lower
    
    json.data_object do
      @foo.each do |key, value|
        json.set! key, value
      end
    end
    

    Although, there is an issue: if @foo is empty, it won't create an object at all. These are the solutions I found:

    1. Define an empty hash before the serialization

      json.key_format! camelize: :lower
      
      json.data_object({}) # don't forget parentheses or Ruby will handle {} as a block 
      
      json.data_object do
        @foo.each do |key, value|
          json.set! key, value
        end
      end
      
    2. Serialize an empty hash if the source variable is empty

      json.key_format! camelize: :lower
      
      if (@foo.empty?) do 
        json.data_object({})
      else 
        json.data_object do
          @foo.each do |key, value|
            json.set! key, value
          end
        end
      end
      

      Or if you prefer your code flat

      json.key_format! camelize: :lower
      
      json.data_object({}) if @foo.empty?
      
      json.data_object do
        @foo.each do |key, value|
          json.set! key, value
        end
      end unless @foo.empty?
      

    However, those solutions will not work if you have to serialize nested objects. You can achieve deep serialization by monkeypatching the json object inside Jbuilder

    def json.hash!(name, hash)
      if hash.empty?
        set! name, {}
      else
        set! name do
          hash.each do |key, value|
            if value.is_a?(Hash)
              hash! key, value
            else
              set! key, value
            end
          end
        end
      end
    end
    

    Then you can simply use json.hash! :data_object, @foo and get the desired result.