5

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?

Morozzzko
  • 580
  • 5
  • 12
  • 1
    Just to mention that jbuilder now has `Jbuilder.deep_format_keys true` which does exactly what you want. I know this is an old post, but I was able to find it and I'm probably not the only one :) – Entilore Jul 14 '21 at 12:14
  • Hey, Entilore. Could you please turn your comment into an answer? I think it should be higher up & visible to everyone! – Morozzzko Aug 11 '22 at 15:46

3 Answers3

6

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.

Morozzzko
  • 580
  • 5
  • 12
3

Try setting this globally in your application (like config/environment.rb for example)

Jbuilder.key_format camelize: :lower
beaorn
  • 454
  • 3
  • 9
  • I get the same result. It won't camelize hash keys – Morozzzko Jun 01 '16 at 13:54
  • Hmm, i'm sure you did, but you restarted your rails server after adding that right? You are also sure that jbuilder is rendering the JSON template and rails isn't just falling back to to_json ? You may need to look into building array of hashes loaded into json.array http://www.rubydoc.info/github/rails/jbuilder/Jbuilder:array! – beaorn Jun 01 '16 at 14:04
  • Yes, I did restart it. Even did it a few times, just to be sure. I am not entirely sure about to_json. I'd be happy if you could explain this one. Currently I have a workaround using loops, which I will post if nobody finds a better solution. – Morozzzko Jun 01 '16 at 14:15
  • It still sounds to me like its defaulting to rendering using to_json and bypassing Jbuilder so the camelize transformation is not getting applied. https://github.com/rails/jbuilder/issues/236 – beaorn Jun 01 '16 at 14:19
1

I'm not sure if this is still relevant. But for the ones that are still looking for an elegant solution, I recommend taking a look on:

https://github.com/vigetlabs/olive_branch

This gem lets your API users pass in and receive camelCased or dash-cased keys, while your Rails app receives and produces snake_cased ones.

Here is a post where the author explains the idea behind this gem. https://www.viget.com/articles/introducing-olivebranch/

Hope it helps