Rails converts empty arrays into nils in params of the request

Ruby on-RailsJson

Ruby on-Rails Problem Overview


I have a Backbone model in my app which is not a typical flat object, it's a large nested object and we store the nested parts in TEXT columns in a MySQL database.

I wanted to handle the JSON encoding/decoding in Rails API so that from outside it looks like you can POST/GET this one large nested JSON object even if parts of it are stored as stringified JSON text.

However, I ran into an issue where Rails magically converts empty arrays to nil values. For example, if I POST this:

{
  name: "foo",
  surname: "bar",
  nested_json: {
    complicated: []
  }
}

My Rails controller sees this:

{
  :name => "foo",
  :surname => "bar",
  :nested_json => {
    :complicated => nil
  }
}

And so my JSON data has been altered..

Has anyone run into this issue before? Why would Rails be modifying my POST data?

UPDATE

Here is where they do it:

https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/request.rb#L288

And here is ~why they do it:

https://github.com/rails/rails/pull/8862

So now the question is, how to best deal with this in my nested JSON API situation?

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

After much searching, I discovered that you starting in Rails 4.1 you can skip the deep_munge "feature" completely using

config.action_dispatch.perform_deep_munge = false

I could not find any documentation, but you can view the introduction of this option here: https://github.com/rails/rails/commit/e8572cf2f94872d81e7145da31d55c6e1b074247

There is a possible security risk in doing so, documented here: https://groups.google.com/forum/#!topic/rubyonrails-security/t1WFuuQyavI

Solution 2 - Ruby on-Rails

Looks like this is a known, recently introduced issue: https://github.com/rails/rails/issues/8832

If you know where the empty array will be you could always params[:...][:...] ||= [] in a before filter.

Alternatively you could modify your BackBone model's to JSON method, explicitly stringifying the nested_json value using JSON.stringify() before it gets posted and manually parsing it back out using JSON.parse in a before_filter.

Ugly, but it'll work.

Solution 3 - Ruby on-Rails

You can re-parse the parameters on your own, like this:

class ApiController
  before_filter :fix_json_params    # Rails 4 or earlier
  # before_action :fix_json_params  # Rails 5

  [...]

  protected

  def fix_json_params
    if request.content_type == "application/json"
      @reparsed_params = JSON.parse(request.body.string).with_indifferent_access
    end
  end

  private

  def params
    @reparsed_params || super
  end
end

This works by looking for requests with a JSON content-type, re-parsing the request body, and then intercepting the params method to return the re-parsed parameters if they exist.

Solution 4 - Ruby on-Rails

I ran into similar issue.

Fixed it by sending empty string as part of the array.

So ideally your params should like

{
  name: "foo",
  surname: "bar",
  nested_json: {
    complicated: [""]
  }
}

So instead of sending empty array I always pass ("") in my request to bypass the deep munging process.

Solution 5 - Ruby on-Rails

Here's (I believe) a reasonable solution that does not involve re-parsing the raw request body. This might not work if your client is POSTing form data but in my case I'm POSTing JSON.

in application_controller.rb:

  # replace nil child params with empty list so updates occur correctly
  def fix_empty_child_params resource, attrs
    attrs.each do |attr|
      params[resource][attr] = [] if params[resource].include? attr and params[resource][attr].nil?
    end
  end

Then in your controller....

before_action :fix_empty_child_params, only: [:update]

def fix_empty_child_params
  super :user, [:child_ids, :foobar_ids]
end

I ran into this and in my situation, if a POSTed resource contains either child_ids: [] or child_ids: nil I want that update to mean "remove all children." If the client intends not to update the child_ids list then it should not be sent in the POST body, in which case params[:resource].include? attr will be false and the request params will be unaltered.

Solution 6 - Ruby on-Rails

I ran into a similar issue and found out that passing an array with an empty string would be processed correctly by Rails, as mentioned above. If you encounter this while submitting a form, you might want to include an empty hidden field that matches the array param :

<input type="hidden" name="model[attribute_ids][]"/>

When the actual param is empty the controller will always see an array with an empty string, thus keeping the submission stateless.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKarolisView Question on Stackoverflow
Solution 1 - Ruby on-Railsa10sView Answer on Stackoverflow
Solution 2 - Ruby on-RailslatentflipView Answer on Stackoverflow
Solution 3 - Ruby on-RailsGrandpaView Answer on Stackoverflow
Solution 4 - Ruby on-Railswaqar mirzaView Answer on Stackoverflow
Solution 5 - Ruby on-Railsthom_nicView Answer on Stackoverflow
Solution 6 - Ruby on-Railsjean-baptisteView Answer on Stackoverflow