3

The standard mechanism I return JSON in a Rails controller is with:

respond_to do |format|
  format.html
  format.json { render json: @cars }
end

Is there a way to modify the @cars JSON? In particular I simply want to add 4 extra fields in there.

UPDATE: Sorry, I should've explained a little more. @cars contains a list of Car objects. I want to add 4 fields to each Car object in the JSON. This is unique to a particular controller, so I don't want to make an as_json method as that would affect other JSONs of this class.

Muntasim
  • 6,689
  • 3
  • 46
  • 69
at.
  • 50,922
  • 104
  • 292
  • 461
  • is @cars a result set from the Car Model? also are these 4 fields unique to each record, or a superset and adjacent to a collection of `cars`? – blotto Feb 12 '14 at 00:46
  • You mean, you want to add 4 extra fields outside of the car object? like: `{total_records: 4, current_page: 1, per_page: 2, cars: [{...},{...}]}` – Muntasim Feb 12 '14 at 01:07
  • Sorry, I should've clarified. Updating the question. – at. Feb 12 '14 at 01:08
  • @at.: As I wrote in another comment, you can easily use to_json(:exclude => [], :methods => []). See my answer for more details on it – Francesco Belladonna Feb 12 '14 at 03:27
  • @at. You can pass all of the options that you would normally pass to the #as_json method to #to_json in your controller. OR, you could create a custom ActiveModelSerializer and only use it once in the controller: `render json: @cars, serializer: MyCustomCarSerializer` – deadwards Feb 13 '14 at 20:17
  • I love that Serializer option! didn't know about that. – at. Feb 13 '14 at 22:45

5 Answers5

3

You can also add custom or nested fields with something like:

class MyModel < ActiveRecord::Base

  def as_json(options)
    super(options).merge({
      "custom" => "myvalue",
      :name => self.name.titleize,
      "result" => self.my_method(self.value1)
    })
  end
end
nakwa
  • 1,157
  • 1
  • 13
  • 25
2

There are two ways that you can modify the structure of the json returned.

  1. Override the #as_json method on the model

    model Car < ActiveRecord::Base
    
      def as_json(options={})
        opts = {
          :only => [:id, :name],
          :methods => [:custom_method]
        }
    
        super(options.merge(opts))
      end
    
      def custom_method
        # some extra info (possibly calculated values)
      end
    
    end
    
  2. You can use the active_model_serializer gem. This is my preferred method because it comes with a few built-in conveniences.

    class CarSerializer < ActiveModel::Serializer
      attributes :id, :name, :custom_method
    
      def custom_method
        # method only available within serializer
      end
    end
    
deadwards
  • 1,531
  • 11
  • 18
2

If you have to do it once, you should use to_json ( http://apidock.com/rails/ActiveRecord/Serialization/to_json ) in this way, directly in your controller:

@object.to_json(:except => ['created_at', 'updated_at'], :methods => ['method1', 'method2'])

In :methods you can specify additional methods you want include.

activemodel_serializer should be used only if you do it very often (for example on a JSON API), if you do it only once in your project, you shouldn't do it.

You can also use the default Rails 4 builder if you are actually using Rails 4.
Notice that the most famous gem for building JSON is RABL, but I do prefer activemodel_serializer. There is also a very nice railscasts available for free about it.

Francesco Belladonna
  • 11,361
  • 12
  • 77
  • 147
  • Love the `to_json` method, but how do I use it with `render json: @cars`? Instead of `@cars` I can pass `@cars.to_json(...)`? – at. Feb 12 '14 at 08:18
  • I don't know about this, I mean, I did it but I don't know the "official" way, I suppose you can just render @obj.to_json(blabla) and nothing else, otherwise use one like :inline or :text, or even better, I suggest you to make a different question for this. Anyway, here is the doc: http://guides.rubyonrails.org/layouts_and_rendering.html#using-render – Francesco Belladonna Feb 12 '14 at 08:41
  • You can check this post: http://jonathanjulian.com/2010/04/rails-to_json-or-as_json/ there are some suggestions about it. Even better, check this stackoverflow question that explains this clearly: http://stackoverflow.com/questions/2572284/how-to-override-to-json-in-rails/2574900#2574900 – Francesco Belladonna Feb 12 '14 at 08:42
  • 1
    Looks like this is correct, though `as_json` seems more appropriate instead of `to_json`. `as_json` just creates the data structure, `to_json` also renders it. – at. Feb 12 '14 at 08:56
1

If you want to edit the json then you can use ActiveSupport::JSON.decode to decode the json. It will give you an array of hash which is easily modifiable and then you can convert that to json using to_sosn

Example: I have used rails console to demonstrate. You need the last portion only.

>> cars = [{id: 1,name: 'foo'}, {id:2, name: 'bar'}]
[{:id=>1, :name=>"foo"}, {:id=>2, :name=>"bar"}]

>> json = cars.to_json
"[{\"id\":1,\"name\":\"foo\"},{\"id\":2,\"name\":\"bar\"}]"

>> parsed_json = ActiveSupport::JSON.decode json
[{"id"=>1, "name"=>"foo"}, {"id"=>2, "name"=>"bar"}]

Then you can iterate that array and modify where necessary and convert back to json

Muntasim
  • 6,689
  • 3
  • 46
  • 69
  • Sorry I don't like to downvote but I find crazy get a memory object, convert it to a string, reconverting it to a memory object (when you already have it) and then send it out as a json, you triple memory usage, waste cpu cycles for something that can be done much better like the as_json method or https://github.com/rails-api/active_model_serializers – Francesco Belladonna Feb 12 '14 at 02:48
  • I just showed the process in console. He only needs the last steps – Muntasim Feb 12 '14 at 02:49
  • @Fire-Dragon-DoL Please look at the Update section of the question, `as_json` does not seem to be useful – Muntasim Feb 12 '14 at 02:53
  • @object.as_json(:except => ['created_at', 'updated_at'], :methods => ['dis', 'val']) and can be called directly in controller. I'm not sure if you can even use it directly on the to_json method. – Francesco Belladonna Feb 12 '14 at 03:12
0

I think it could use a method to do the adding fields process and render the return result. Could the following work for you:

In a helper

def some_method(in_param, from_which_controller)
  return_array = Array.new
  obj_array = JSON.parse(in_param);
  obj_array.each do |obj|
    case from_which_controller
      when from_controller_a
         # add the field to obj or some other thing
      when ...........
         # add the field to obj or some other thing
    end
    return_array << obj # or new obj instead of obj
  end
  return return_array.to_json
end

In your controller:

respond_to do |format|
  format.html
  format.json { render json: some_method(@cars, this_controller) }
end
lalameat
  • 754
  • 3
  • 10