Beginning rails programmer here. I'm trying to convert a hash to both xml and json, but the output is different.
Here's the hash:
{:exchangeRates=>[{:baseCurrency=>"USD", :quoteCurrency=>"EUR", :amount=>1, :nprices=>1, :conversions=>[{:date=>Tue, 20 Nov 2012 21:00:00 +0000, :ask=>"0.7813", :bid=>"0.7813"}]}, {:baseCurrency=>"CAD", :quoteCurrency=>"EUR", :amount=>1, :nprices=>1, :conversions=>[{:date=>Tue, 20 Nov 2012 21:00:00 +0000, :ask=>"0.7839", :bid=>"0.7837"}]}]}
Here's the corresponding render code
format.json { render :json => { :response => rates.to_hash() } }
and here's the JSON (which is what I want)
{"response": {"exchangeRates": [
{
"baseCurrency": "USD",
"quoteCurrency": "EUR",
"amount": 1,
"nprices": 1,
"conversions": [{
"date": "2012-11-20T21:00:00+00:00",
"ask": "0.7813",
"bid": "0.7813"
}]
},
{
"baseCurrency": "CAD",
"quoteCurrency": "EUR",
"amount": 1,
"nprices": 1,
"conversions": [{
"date": "2012-11-20T21:00:00+00:00",
"ask": "0.7839",
"bid": "0.7837"
}]
}
]}}
Here's my xml render code:
format.xml { render :xml => rates.to_hash(), :root => 'response' }
Here's the xml output (there are extra tags where I put in arrays):
<response>
<exchangeRates type="array">
<exchangeRate>
<baseCurrency>USD</baseCurrency>
<quoteCurrency>EUR</quoteCurrency>
<amount type="integer">1</amount>
<nprices type="integer">1</nprices>
<conversions type="array">
<conversion>
<date type="datetime">2012-11-20T21:00:00+00:00</date>
<ask>0.7813</ask>
<bid>0.7813</bid>
</conversion>
</conversions>
</exchangeRate>
<exchangeRate>
<baseCurrency>CAD</baseCurrency>
<quoteCurrency>EUR</quoteCurrency>
<amount type="integer">1</amount>
<nprices type="integer">1</nprices>
<conversions type="array">
<conversion>
<date type="datetime">2012-11-20T21:00:00+00:00</date>
<ask>0.7839</ask>
<bid>0.7837</bid>
</conversion>
</conversions>
</exchangeRate>
</exchangeRates>
</response>
As you can see, it is adding the extra "array" attribute tags, i.e. exchangeRates and conversions. How do I get this to format the same as the json? I also don't want the attributes on any of the tags either. I know you can pass in attributes, such as :root => 'response', but after looking for quite some time, I can't seem to find a listing of these attributes on the web.
Any help would be greatly appreciated, thanks!
This is a case where it's best just to go directly to the source code. The to_xml method is in the ActiveModel::Serializer module, here are the inline docs, which don't mention anything about the type="array" attribute tags. Dig a bit deeper though and you'll see they appear in the same file on line 130 of a method called add_associations.
rails/activemodel/lib/active_model/serializers/xml.rb:130
type = options[:skip_types] ? { } : {:type => "array"}
That tells us that there's an option called skip_types, which appears to be documented nowhere. Try passing that to to_xml, and you get the desired behaviour:
a = [1, 2, 3]
a.to_xml
#=> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<fixnums type=\"array\">\n <fixnum type=\"integer\">1</fixnum>\n <fixnum type=\"integer\">2</fixnum>\n <fixnum type=\"integer\">3</fixnum>\n</fixnums>\n"
a.to_xml(:skip_types => true)
#=> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<fixnums>\n <fixnum>1</fixnum>\n <fixnum>2</fixnum>\n <fixnum>3</fixnum>\n</fixnums>\n"
You'll notice all the added type attributes are gone.
So just pass the same option to render and you'll get the desired result:
format.xml { render :xml => rates.to_hash(), :root => 'response', :skip_types => true }
Related
I am having trouble trying to render specific fields in my rails app my structure is like so:
Home_controller.rb
def index
taobao_hash = OpenTaobao.get(
:method => "taobao.tbk.item.get",
:fields => "num_iid,title,nick,pict_url,cid,price,type,delist_time,post_fee,score,volume",
:q => "snus",
:page_size => 1,
:sort => "_des",
)
h = JSON.parse(taobao_hash.to_json)
render json: h["tbk_item_get_response"]
end
How this renders my whole request as json like so
{
"results": {
"n_tbk_item": [
{
"nick": "恶毒对于",
"num_iid": 17707525948,
"pict_url": "http://img4.tbcdn.cn/tfscom/i2/TB1umhOHVXXXXa0aXXXXXXXXXXX_!!0-item_pic.jpg",
"title": "瑞典戒烟产品唇烟嚼烟经典款General Classic White Portion Snus",
"volume": 127
}
]
},
"total_results": 99,
"request_id": "1476rjvn5sc2g"
}
How would I go about only rendering title in my view? I can only seem to render everything.
Json response which I send is like that
"ad": {
"id": 3,
"title": "dgdfg",
"description": "kjlj",
"video_file_name": "SampleVideo_1080x720_1mb.mp4",
"thumbnail_file_name": "images.jpeg",
"campaign_id": null,
"duration": null
},
"video_url": "/system/ads/videos/000/000/003/original/SampleVideo_1080x720_1mb.mp4?1448019186"
I want that video_url also merge with in ad object.
The way I send response now is
render json: {:success=>true, :message=>"Ad detail",:ad=>#ad, :video_url => #ad.video.url}, :status=>200
How I merge it with ad object?
I want to send it like
"ad": {
"id": 3,
"title": "dgdfg",
"description": "kjlj",
"video_file_name": "SampleVideo_1080x720_1mb.mp4",
"thumbnail_file_name": "images.jpeg",
"campaign_id": null,
"duration": null,
"video_url": "/system/ads/videos/000/000/003/original/SampleVideo_1080x720_1mb.mp4?1448019186"
}
My #ad object is
#<Ad:0x007efc20495f98
id: 3,
title: "dgdfg",
description: "kjlj",
video_file_name: "SampleVideo_1080x720_1mb.mp4",
video_content_type: "video/mp4",
video_file_size: 1055736,
video_updated_at: Fri, 20 Nov 2015 11:33:06 UTC +00:00,
thumbnail_file_name: "images.jpeg",
thumbnail_content_type: "image/jpeg",
thumbnail_file_size: 9962,
thumbnail_updated_at: Fri, 20 Nov 2015 11:33:22 UTC +00:00,
created_at: Fri, 20 Nov 2015 11:33:22 UTC +00:00,
updated_at: Fri, 20 Nov 2015 11:33:22 UTC +00:00,
campaign_id: nil,
duration: nil>
First merge {:video_url => #ad.video.url } with #ad then do following:
{:ad => #ad.attributes.merge( :video_url => #ad.video.url )}
so your render call would look like following:
render json: {:success=>true, :message=>"Ad detail", ad: #ad.attributes.merge( :video_url => #ad.video.url )}, :status=>200
You may need to use #ad.attributes.except("created_at",....) at following code if you don't need some of the attributes of your active record object #ad.
Just before render define the object to send (note that if #ad is not a hash, probably it should be converted to hash before):
# ⇓⇓⇓⇓⇓⇓⇓ this depends on what #ad currently is
object_to_send = #ad.to_hash.merge(video_url: #ad.video.url)
and then:
render json: { success: true,
message: "Ad detail",
ad: object_to_send },
status: 200
You could use the as_json method, but you need a method that returns the URL directly
class Ad
def video_url
video.url
end
end
Then in the render
render json: {
success: true,
message: "Ad detail",
ad: ad.as_json(
only: {
:id, :title, :description, :video_file_name, :thumbnail_file_name, :campaign_id, :duration
},
methods: :video_url
),
status: 200
of course if you want you could wrap this into some method,
class Ad
def my_video_json
as_json(
only: {
:id, :title, :description, :video_file_name, :thumbnail_file_name, :campaign_id, :duration
},
methods: :video_url
)
end
end
Then the render would look like this
render json: { success: true, message: "Ad detail", ad: ad.my_video_json }, status: 200
You can add new key and value in hash by adding this:
#ad.attributes[:video_url] = #ad.video.url
I hope this help you.
Say I have a model User and a serializer UserSerializer < ActiveModel::Serializer, and a controller that looks like this:
class UsersController < ApplicationController
respond_to :json
def index
respond_with User.all
end
end
Now if I visit /users I'll get a JSON response that looks like this:
{
"users": [
{
"id": 7,
"name": "George"
},
{
"id": 8,
"name": "Dave"
}
.
.
.
]
}
But what if I want to include some extra information in the JSON response that isn't relevant to any one particular User? E.g.:
{
"time": "2014-01-06 16:52 GMT",
"url": "http://www.example.com",
"noOfUsers": 2,
"users": [
{
"id": 7,
"name": "George"
},
{
"id": 8,
"name": "Dave"
}
.
.
.
]
}
This example is contrived but it's a good approximation of what I want to achieve. Is this possible with active model serializers? (Perhaps by subclassing ActiveModel::ArraySerializer? I couldn't figure it out). How do I add extra root elements?
You can pass them as the second arguement to respond_with
def index
respond_with User.all, meta: {time: "2014-01-06 16:52 GMT",url: "http://www.example.com", noOfUsers: 2}
end
In version 0.9.3 in an initializer set ActiveModel::Serializer.root = true:
ActiveSupport.on_load(:active_model_serializers) do
# Disable for all serializers (except ArraySerializer)
ActiveModel::Serializer.root = true
end
In controller
render json: #user, meta: { total: 10 }
Got it working using render:
render json: {
"time": "2014-01-06 16:52 GMT",
"url": "http://www.example.com",
"noOfUsers": 2,
"users": #users
}
The problem is, this doesn't call UserSerializer, it just calls .as_json on each individual user object and skips the Serializer. So I had to do it explicitly:
def index
.
.
.
render json: {
"time": "2014-01-06 16:52 GMT",
"url": "http://www.example.com",
"noOfUsers": 2,
"users": serialized_users
}
end
def serialized_users
ActiveModel::ArraySerializer.new(#users).as_json
end
Not the most elegant solution but it works.
Just a simple hack if you don't want to modify either the serializer or render:
data = serializer.new(object, root: false)
# cannot modify data here since it is a serializer class
data = data.as_json
# do whatever to data as a Hash and pass the result for render
data[:extra] = 'extra stuff'
render json: data
I was able to get this working for my use case by just adding the following in my controller. Nothing else needed with AMS 0.10.
render
json: #user,
meta: {
time: "2014-01-06 16:52 GMT",
url: "http://www.example.com",
noOfUsers: 2
}
I'm trying to use the Active Model Serializer gem with my API, although I am struggling with something I thought would be pretty simple.
All my JSON responses are in a wrapped format, with every response having a top level message and status property, the data is within the content property. Every JSON response follows this format.
Example
{
'status': statuscode,
'message': message,
'content': { 'object':obj }
}
The contents of the "content" property is where I would like to place the output of the Serializer. My lists of articles, etc.
I cannot figure out how to do this though?
Any help would be greatly appreciated.
IF You dont mind your status and messages hashes being inside a hash you can use a meta key.
(from https://github.com/rails-api/active_model_serializers/tree/0-8-stable)
render :json => #posts, :serializer => CustomArraySerializer, :meta => {:total => 10}
=>
{
"meta": { "total": 10 },
"posts": [
{ "title": "Post 1", "body": "Hello!" },
{ "title": "Post 2", "body": "Goodbye!" }
]
}
Or if you need them to be top level keys you can SubClass ArraySerializer and overwrite as_json to allow it to merge in your keys.
def as_json(*args)
#options[:hash] = hash = {}
#options[:unique_values] = {}
hash.merge!(#options[:top_level_keys]) if #options.key?(:top_level_keys)
root = #options[:root]
if root.present?
hash.merge!(root => serializable_array)
include_meta(hash)
hash
else
serializable_array
end
end
then just
render :json #object, :serializer => YourCustomArraySerializer
I have a model for languages and i want to get all the languages as json but the json output looks as follows
[{"language":{"created_at":null,"id":1,"language":"English","updated_at":null}},{"language":{"created_at":null,"id":2,"language":"Swedish","updated_at":null}},{"language":{"created_at":null,"id":3,"language":"German","updated_at":null}},{"language":{"created_at":null,"id":4,"language":"French","updated_at":null}},{"language":{"created_at":null,"id":5,"language":"spanish","updated_at":null}},{"language":{"created_at":null,"id":6,"language":"dutch","updated_at":null}},{"language":{"created_at":"2012-12-03T05:01:18Z","id":7,"language":"Tamil","updated_at":"2012-12-03T05:01:18Z"}}]
but i want to make this as
{"language":[{"created_at":null,"id":1,"language":"English","updated_at":null},{"created_at":null,"id":2,"language":"Swedish","updated_at":null},{"created_at":null,"id":3,"language":"German","updated_at":null},{"created_at":null,"id":4,"language":"French","updated_at":null},{"created_at":null,"id":5,"language":"spanish","updated_at":null},{"created_at":null,"id":6,"language":"dutch","updated_at":null},{"created_at":null,"id":7,"language":"Tamil","updated_at":null} ] }
Update
def index
#languages = Language.all
respond_to do |format|
format.json { render json: #languages}
end
end
update 2
class Language < ActiveRecord::Base
ActiveRecord::Base.include_root_in_json = false
has_and_belongs_to_many :users
end
I believe this should work:
format.json { render json: { "language" => #languages.as_json(:root => false) }.to_json }
What this does it to convert the #languages array into an array of JSON-formatted hash models with no root keys (using as_json), then wraps the result in a hash with a root key "language", and convert that hash into a JSON-formatted string with to_json. (See the docs for details on including or not including a root node using as_json.)
For example, with a model Post:
posts = Post.all
#=> [#<Post id: 1, name: "foo", title: "jkl", content: "some content", created_at: "2012-11-22 01:05:46", updated_at: "2012-11-22 01:05:46">]
# convert to array of hashes with no root keys
posts.as_json(root: false)
#=> [{"content"=>"some content", "created_at"=>Thu, 22 Nov 2012 01:05:46 UTC +00:00, "id"=>1, "name"=>"foo", "title"=>"jkl", "updated_at"=>Thu, 22 Nov 2012 01:05:46 UTC +00:00}]
# add root back to collection:
{ "post" => posts.as_json(root: false) }
#=> {"post"=>[{"content"=>"some content", "created_at"=>Thu, 22 Nov 2012 01:05:46 UTC +00:00, "id"=>1, "name"=>"foo", "title"=>"jkl", "updated_at"=>Mon, 03 Dec 2012 09:41:42 UTC +00:00}]}
# convert to JSON-formatted string
{ "post" => posts.as_json(root: false) }.to_json
#=> "{\"post\":[{\"content\":\"some content\",\"created_at\":\"2012-11-22T01:05:46Z\",\"id\":1,\"name\":\"foo\",\"title\":\"jkl\",\"updated_at\":\"2012-12-03T09:43:37Z\"}]}"
override the as_json on the Model you want to customize
def as_json options={}
{
id: id,
login: login,
name: custom.value, #for custom name
...
}
end
==> or
def as_json(options={})
super(:only => [:id, :login, :name ....])
end
from : here
Other link: here
I suggest you to use rabl gem (https://github.com/nesquena/rabl) to format your data.
Override as_json method in your model, to include associations, hide columns and why not? calling custom methods as they were attributes
def as_json(options={})
super(:except => [:created_at,:updated_at],
:include => {
:members => {
:only => [:role, :account],
:include => {
:account => {
:only => [:name, :subdomain]
}
}
}
},
:methods => [:jwt_token]
)
end
This will output something like this:
{
"id": 2,
"name": "Test Teacher",
"email": "teacher#testing.io",
"jwt_token":"eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MiwiZXhwIjoxNTY2NzQ0OTQzfQ.HDGu7JiJEQEEpGo7inuXtOZBVQOfTaFquy8dr-QH5jY",
"members": [{
"role": "instructor",
"account": {
"name": "Testing",
"subdomain": "test"
}
}],
}
The easiest way of adding custom json output when you render json is by using gem that provide many json templates-
https://github.com/fabrik42/acts_as_api