Rails 5+ API: single endpoint for both individual JSON object and array of JSON objects
In the case you send a JSON array (like [{ child_name: "Alpha" }, { child_name: "Bravo" }]
) to an endpoint, Rails stores it into _json
of the params hash. The reason for doing this is, that unlike a single JSON object, an array cannot be extracted into a hash, without introducing another key.
Let's say, that we post { child_name: "Alpha" }
to an endpoint. The params will look like:
{"child_name"=>"Alpha", "format"=>:json, "controller"=>"api/children", "action"=>"create"}
Now if we post the array [{ child_name: "Alpha" }, { child_name: "Bravo" }]
and it would be blindly extracted into the hash like a JSON object the result would look like:
{[{ child_name: "Alpha" }, { child_name: "Bravo" }], "format"=>:json, "controller"=>"api/children", "action"=>"create"}
This is not a valid hash! So Rails wraps your JSON array into _json
and it becomes:
{"_json" => [{ child_name: "Alpha" }, { child_name: "Bravo" }], "format"=>:json, "controller"=>"api/children", "action"=>"create"}
This happens here in the actionpack. Yes, it seems a bit hacky, but the only and maybe cleaner solution would be to wrap all data from the request body into one key like data
.
So to your actual question: How can I manage a JSON object and JSON array at a single endpoint?
You can use strong params like ..
def children_params model_attributes = [:child_name] if params.key? '_json' params.permit(_json: model_attributes)['_json'] else params.permit(*model_attributes) endend
.. and in the create action would look like ..
def create result = Children.create!(children_params) if children_params.kind_of? Array @children = result render :index, status: :created else @child = result render :show, status: :created endend
Of course you would need to adapt it a bit for your specific use case. From the design perspective of your API, I think it's okay doing it like that. Maybe this should be asked within a separate question.