I'm using the Rails ActiveModel Serializer to output JSON to an API endpoint.
I'm getting this JSON output:
{
"data":[
{
"id":"396",
"type":"profiles",
"attributes":{
"name":"Impossibles",
"created-at":"2017-05-11T18:14:06.201-04:00",
"updated-at":"2018-04-01T13:34:15.905-04:00",
"website":"http://soundcloud.com/impossibles"
}
}
]
}
But was expecting it to be formatted like this:
{
"data":[
{
"id":"396",
"type":"profiles",
"name":"Impossibles",
"created-at":"2017-05-11T18:14:06.201-04:00",
"updated-at":"2018-04-01T13:34:15.905-04:00",
"website":"http://soundcloud.com/impossibles"
}
]
}
Trying to avoid the extra level of nesting in the returned JSON.
Is there a way to remove the "attributes" key?
This is my serializer:
class ProfileSerializer < ActiveModel::Serializer
attributes :id, :name, :created_at, :updated_at, :website
end
And my controller:
def show
profile = Profile.find(params[:id])
render json: profile, status: :ok
end
After reading through some GitHub issues, it appears the "attributes" nesting is coming from the JSON API spec and is expected behavior:
https://jsonapi.org/format/#document-resource-objects
This issue was helpful:
https://github.com/rails-api/active_model_serializers/issues/2202
Looks like a feature, not a bug.
Related
I have a Rails 5 API that renders an object with some of it's methods to JSON.
render json: { rides: #rides }.to_json( :methods => [ :is_preferred ]), status: 200
So this returns something like:
{
id: 123,
is_preferred: true
}
But I would like to change the name of the attribute that refers to the is_preferred method.
The output I would like id:
{
id: 123,
preferred: true
}
I tried
render json: { rides: #rides }.to_json( :methods => [ preferred: :is_preferred ]), status: 200
But this does not work. Easiest would be to change the method name in the model, but that's not possible in this case.
Is there any way I can manipulate the name inside the response?
You can try with an ActiveModel::Serializer, then you can define the attribute key as you want
class RideSerializer < ActiveModel::Serializer
attribute :preferred, key: :is_preferred
end
or use a method to retrieve the attribute value
class RideSerializer < ActiveModel::Serializer
attribute :is_preferred
def is_preferred
object.preferred
end
end
Using serializers has a lot of bennefits and is so powerfull if you want to create custom responses.
I am using Rails to create APIs containing basic todo information: name, list, and items. I want it to return json format, to look something like this:
{
"data": [
{
"type": "posts",
"id": "1",
"attributes": {
"title": "JSON API is awesome!",
"body": "You should be using JSON API",
"created": "2015-05-22T14:56:29.000Z",
"updated": "2015-05-22T14:56:28.000Z"
}
}
],
"links": {
"href": "http://example.com/api/posts",
"meta": {
"count": 10
}
}
}
^code from Active Serializer's Github.
When I look on my localhost http://localhost:3000/api/users/, it shows
[{"id":1,"username":"Iggy1","items":[{"id":1,"list_id":1,"name":"Wash dishes","completed":true},{"id":7,"list_id":1,"name":"Finish this assignment","completed":false}],"lists":[{"id":1,"name":"Important!","user_id":1,"permission":"private"},...
It is returning an array of hashes. I am sure I missed an important step when I was setting up my serializer. How can I reformat my array of hashes into JSON API format?
I've read getting started guide, rendering, and JSON API section but I still couldn't figure it out. I might have overlooked it.
Some of my codes:
app/serializers/user_serializer.rb
class UserSerializer < ActiveModel::Serializer
attributes :id, :username#, :email
has_many :items, through: :lists
has_many :lists
end
app/controllers/api/users_controller.rb
def index
#users = User.all
render json: #users, each_serializer: UserSerializer
end
routes
Rails.application.routes.draw do
namespace :api, defaults: { format: :json } do
resources :users do
resources :lists
end
end
end
Let me know if I can clarify it better. Thanks!!
(Answer from comments)
To use the JSON API adapter, you need to declare you want to use it.
ActiveModelSerializers.config.adapter = ActiveModelSerializers::Adapter::JsonApi
As per the AMS readme.
ActiveModelSerializers.config.adapter = :json_api # Default: `:attributes`
from 0-10-stable documentation
However, this did not solve my problem. It was due to the Hash being nested as discussed in this SO post
I am getting info from my database
#teams = Team.select(:id,:name)
Then, I am rendering this info with an assosiation
render json: #teams, include: :players
This produce me the following input:
[
{
"id": 1,
"name": "Lyon Gaming",
"players": [
{
"name": "Jirall",
"position": "Superior",
},
{
"name": "Oddie",
"position": "Jungla",
}
]
}
]
How ever I need to sort the players in an specific order (not alphabetical)
I already have the sql statement to do it; how ever I dont know how to apply this method to each association.
I am looking something like:
render json: #teams, (include: :players, method: custom_order)
But this raise me an error.
Any ideas?
This should work:
render json: #teams, include: :players, methods: :custom_order
I'd like to add that if you will always want to return the object this way, you can add this to your model instead:
def as_json(options = nil)
super(include: :players, methods: :custom_order)
end
As Adam has mentioned, active_model_serializers is good to have if your render json's start getting ugly. If the complexity doesn't grow too much, I always recommend keeping the dependencies down.
https://github.com/rails-api/active_model_serializers
You need to read more about serializers in rails(This is good practice)
Here you have a nice article about this https://www.sitepoint.com/active-model-serializers-rails-and-json-oh-my/
Then you can make something like this
app/serializers/team_serializer.rb
class TeamSerializer < ActiveModel::Serializer
attributes :id, :name, :ordered_players
end
app/controllers/teams_controller.rb
def index
respond_with(Team.all.map(&TeamSerializer.method(:new)))
end
Using Active Model Serializer, is there an easy and integrated way to return a JSON "object" (that would then be converted in a javascript object by the client framework) instead of a JSON "array" when serializing a collection? (I am quoting object and array, since the returned JSON is by essence a string).
Let's say I have the following ArticleSerializer:
class ArticleSerializer < ActiveModel::Serializer
attributes :id, :body, :posted_at, :status, :teaser, :title
end
I call it from ArticlesController:
class ArticlesController < ApplicationController
def index
#feed = Feed.new(articles: Article.all)
render json: #feed.articles, each_serializer: ArticleSerializer
end
end
Is there a way to pass an option to the serializer to make it return something like:
{"articles":
{
"1":{
...
},
"2":{
...
}
}
}
instead of
{"articles":
[
{
"id":"1",
...
},
{
"id":"2"
...
}
]
}
Edit: I guess that the approach proposed in this post (subclassing AMS ArraySerializer) might be helpful (Active Model Serializer and Custom JSON Structure)
You'd have to write a custom adapter to suit your format.
Alternatively, you could modify the hash before passing it to render.
If you do not mind iterating over the resulting hash, you could do:
ams_hash = ActiveModel::SerializableResource.new(#articles)
.serializable_hash
result_hash = ams_hash['articles'].map { |article| { article['id'] => article.except(:id) } }
.reduce({}, :merge)
Or, if you'd like this to be the default behavior, I'd suggest switching to the Attributes adapter (which is exactly the same as the Json adapter, except there is no document root), and override the serializable_hash method as follows:
def format_resource(res)
{ res['id'] => res.except(:id) }
end
def serializable_hash(*args)
hash = super(*args)
if hash.is_a?(Array)
hash.map(&:format_resource).reduce({}, :merge)
else
format_resource(hash)
end
end
No, semantically you are returning an array of Articles. Hashes are simply objects in Javascript, so you essentially want an object with a 1..n method that returns each Article, but that would not make much sense.
I'm trying to get the postgres_ext-serializers gem working, and I built a test project very similar to https://github.com/dockyard/postgres_ext-serializers/blob/master/test/test_helper.rb
class UserSerializer < ActiveModel::Serializer
attributes :id, :name, :mobile
embed :ids, include: true
has_one :address, serializer: AddressSerializer
def include_mobile?
false
end
alias_method :include_address?, :include_mobile?
end
class AddressSerializer < ActiveModel::Serializer
attributes :id, :district_name
embed :ids, include: true
end
When I try to run the serializers the output doesn't seem to have nested elements. For example my serializer to_json output is:
"{\"users\":[{\"id\":1,\"name\":\"Aaron\",\"mobile\":null}, \n {\"id\":2,\"name\":\"Bob\",\"mobile\":null}],\"addresses\":[{\"id\":1,\"district_name\":\"Rob's Address\"}]}"
Notice how users and address are two separate elements of a hash, intead of being nested. If I remove the postgres_ext-serializers gem, then the output is as expected:
"[{\"id\":1,\"name\":\"Rob\",\"mobile\":null,\"address\":{\"id\":1,\"district_name\":\"Rob's Address\"}},{\"id\":2,\"name\":\"Bob\",\"mobile\":null,\"address\":null}]"
The address is embedded in the user hash exactly how I'm expecting it.
What am I missing, do I need to change anything to make the elements nested when using postgres_ext-serializers?
Thanks!
It seems, that the JSON you receive after serialization is what postgres_ext-serializers expects. Take a look into this test. expected_json in the first test case case is:
{
"users": [
{
"id": <UserID>,
"name": "John",
"mobile": "51111111",
"offer_ids": [],
"reviewed_offer_ids": []
}
],
"offers": [],
"addresses": [
{
"id": <AddressID>,
"district_name": "mumbai"
}
]
}
It looks very similar to the JSON you have received. But, to be honest, as include_address? method in your example returns false, I expect you must have not "addresses" field included into resulting JSON at all.