This project is archived and is in readonly mode.
as_json should return a hash for ActiveModel/ActiveRecord objects
Reported by Jakub Suder | August 13th, 2010 @ 01:31 PM | in 3.0.2
Maybe I misunderstood what the as_json method should do, but according to this post http://jonathanjulian.com/2010/04/rails-to_json-or-as_json/, "as_json is used to create the structure of the JSON as a Hash". Currently, as_json simply returns the object as is (def as_json(options = nil); self; end), which kind of defeats the whole purpose of this method.
Here's the Rails 2.3.5 behavior of as_json:
>> User.first.as_json
=> {"created_at"=>Fri Jun 27 05:04:47 -0400 2008, "remember_token_expires_at"=>Sat Feb 20 15:23:11 -0500 2010,
"last_login"=>Thu Jan 21 13:04:06 -0500 2010, "updated_at"=>Thu Jan 21 13:04:16 -0500 2010, "role"=>"admin",
"send_emails"=>false, "id"=>1, "forward_messages"=>true, "password_code"=>nil, "login"=>"admin"}
>> User.first.as_json(:only => :login)
=> {"login"=>"admin"}
And here's the Rails 3 behavior:
> User.last.as_json
=> #<User id: 124, name: "warekus", admin: false, github_username: "", twitter_username: "", ...>
> User.last.as_json(:only => :name)
=> #<User id: 124, name: "warekus", admin: false, github_username: "", twitter_username: "", ...>
Comments and changes to this ticket
-
Jakub Suder August 13th, 2010 @ 02:28 PM
I think this should fix it (lib/active_model/serializers/json.rb):
@@ -91,5 +91,5 @@ def as_json(options = nil) - self + serializable_hash(options) end
Also, as_json may be overwritten in subclasses and that overwritten version should always be used when generating JSON, so I think this should also be included:
@@ -81,5 +81,5 @@ # "title": "So I was thinking"}]} def encode_json(encoder) - hash = serializable_hash(encoder.options) + hash = as_json(encoder.options) if include_root_in_json custom_root = encoder.options && encoder.options[:root]
-
Rasmus Rønn Nielsen August 17th, 2010 @ 08:56 PM
I'm having this problem as well. Jakub's fix works great for me. Thanks Jakub!
-
Jakub Suder August 29th, 2010 @ 08:05 PM
I'm updating the ticket with a new, extended version of the patch. This got much more complicated than I thought at first, every time I fixed something I kept finding another issue... I probably also fixed this ticket in the process https://rails.lighthouseapp.com/projects/8994/tickets/3087-activesu... (about as_json called on arrays).
Here's an explanation of what happens in the commit:
-
active_model/serialization.rb
- theserializable_hash
method was modified so that it doesn't overwrite the values in the options hash that you pass in the argument; in such situation the caller normally doesn't expect the called method to modify the options hash, which could be stored as the caller's instance variable (as is the case with ActiveSupport JSON encoder). Also, I've updated comments because they were misleading (as_json
doesn't return a string,to_json
does;as_json
returns (or should return) a hash). -
active_model/serializers/json.rb
-as_json
was changed to return a hash instead of self; theencode_json
method was removed because it will never be called (as_json
should only return a string, number, array or hash) -
active_support/json/encoding.rb
- lots of changes here:Array#as_json
callsas_json
on all its elements; but I've noticed that just callingas_json
directly breaks a test that checks a case with a circular reference… so I've added added a methodjsonify
in the encoder, which callsas_json
while preventing circular references in the same way this is done inencode
Array#encode_json
: there's no point in callingas_json
on elements again, because the encoder must have called it beforeencode_json
, so we just callencode_json
on the elements directlyHash#as_json
: I've modified it to also callas_json
with options on all its values; I think this is more consistent, because ifas_json
is supposed to work just liketo_json
, except it returns a hash instead of string, then it's confusing ifto_json
passes the options down to its elements andas_json
doesn't. I also had to handle the circular reference case here.Hash#encode_json
: there were two problems here: one is that we can't make it callas_json
on all the values with the original options, because this breaks if ActiveModel returns a representation with a root, like{'user' => { …fields… }}
. If we callas_json(:only => :name)
on it again, it will return{'user' => {}}
, because:name
is now called'name'
. On the other hand, I think it is allowed to make a customas_json
method in a model that returns fields like symbols or dates in their original form (the problem is that the contract foras_json
isn't defined anywhere, so it's hard to say what exactly it can or should return). So the way I did this is I've added a parameteruse_options
toencode
in encoder, andHash#encode_json
calls encode with use_options = false. That way, the elements are processed withas_json
, but without the:only
/:except
options which could remove some fields.
-
active_record/relation.rb
- since I assume inArray#encode_json
thatas_json
has already been called on the elements, I modifiedas_json
in Relation to callas_json
on the result fromto_a
.
As you can see, there's a lot of stuff happening here, so I'd really appreciate if someone looked through this very carefully. There's also a possibility that I've missed some edge case, because a few times I only found an issue because a test failed, so if something wasn't tested well enough, I might have missed it altogether.
I'm aware that some of this code might look a bit hacky, but I couldn't think of any better way of writing this and making the tests still pass. Feel free to improve this if you have a better idea…
-
-
Neeraj Singh August 30th, 2010 @ 01:41 AM
- Importance changed from to Low
@Jakub. That's a lot of change. It will take me a while to digest all of that.
It seems the basic assumption of your work is that as_json should return a hash. I disagree.
I believe as_json should return a ruby object that could be converted into json. The reason why it returns self is because it lets subclasses modify the default representation if the user wants. For example I could write like this
class User < AR def as_json [self.id, self.name, self.created_at] end end
That is a valid representation of as_json. As per your thoughts I must return a hash in as_json method.
The method that should return a hash is an internal method called serializable_hash.
I spent some time looking at Ticket #3087 . The fix of that ticket requires change in terms of where in lifecycle serializable_hash is called. Now that rails 3 is out I will work on that ticket again.
-
Jeremy Kemper September 6th, 2010 @ 08:17 PM
- Milestone cleared.
- State changed from new to open
-
Repository September 7th, 2010 @ 07:36 PM
- State changed from open to committed
(from [2524cf404ce943eca8a5f2d173188fd0cf2ac8b9]) fixed some issues with JSON encoding
- as_json in ActiveModel should return a hash and handle :only/:except/:methods options
- Array and Hash should call as_json on their elements
- json methods should not modify options argument
[#5374 state:committed]
Signed-off-by: Jeremy Kemper jeremy@bitsweat.net
http://github.com/rails/rails/commit/2524cf404ce943eca8a5f2d173188f... -
Repository September 7th, 2010 @ 07:36 PM
(from [33b954005cd71f1bfba1beca296804ce6c66b0a8]) fixed some issues with JSON encoding
- as_json in ActiveModel should return a hash and handle :only/:except/:methods options
- Array and Hash should call as_json on their elements
- json methods should not modify options argument
[#5374 state:committed]
Signed-off-by: Jeremy Kemper jeremy@bitsweat.net
Conflicts:
activemodel/lib/active_model/serialization.rb
http://github.com/rails/rails/commit/33b954005cd71f1bfba1beca296804...
-
Jakub Suder September 7th, 2010 @ 09:07 PM
Thanks!
Don't forget about the other ticket (https://rails.lighthouseapp.com/projects/8994/tickets/3087) - I think it can be closed too...
It might be worth mentioning the as_json method in Rails Guides, what do you think? We could add something like this:
If you need to return an object converted to JSON as a part of a more complex structure, you can use the
as_json
method which returns a hash representation of the object:render :json => { :status => 'ok', :product => @product.as_json(:except => :id) }
to the "2.2.9 Rendering JSON" part of "Layouts and Rendering" guide (http://guides.rubyonrails.org/layouts_and_rendering.html) - it's the only guide I found that has any mention of to_json, so it's probably the best place right now (until someone adds a JSON section in a ActiveModel or ActiveRecord guide).
Create your profile
Help contribute to this project by taking a few moments to create your personal profile. Create your profile »
<h2 style="font-size: 14px">Tickets have moved to Github</h2>
The new ticket tracker is available at <a href="https://github.com/rails/rails/issues">https://github.com/rails/rails/issues</a>
People watching this ticket
Attachments
Referenced by
- 5374 as_json should return a hash for ActiveModel/ActiveRecord objects [#5374 state:committed]
- 5374 as_json should return a hash for ActiveModel/ActiveRecord objects [#5374 state:committed]
- 3087 ActiveSupport::JSON.encode is Inconsistent for #as_json and #to_json #5374