32

I have no trouble making typical AJAX calls to and from Rails(3) with JSON objects and jQuery-rails (jQuery library plus a special rails.js file).

In one controller, though, I want to RETURN some JSON in an erb template (create.js.erb) after an AJAX call.

I've tried every combination of things in the controller (@object.to_json, '[{"content":"hello world"}]', etc.) and in the template itself (JSON.parse(), single quotes, double quotes, etc.), but the object keeps on rendering like this:

'[{"groups":{},"created_at":"2010-09-21T03:49:34Z" ...

and as a result, my jQuery code cannot parse it and I get errors.

How do I need to prep my object in the controller, and what erb syntax do I need in the view for it to render as a valid JSON object?

Thanks so much!

Michael Waxman
  • 1,815
  • 3
  • 18
  • 32

5 Answers5

57

I'm not sure this is the cause, but you can also try playing around with html_safe method. ERB might be escaping your JSON because it thinks it's not html safe. Try calling that method when using the string:

@object.to_json.html_safe
alex.zherdev
  • 23,914
  • 8
  • 62
  • 56
  • 20
    Watch out: calling html_safe without escaping can lead to an XSS vulnerability if you're embedding the result in a script tag. – John Apr 30 '12 at 20:46
  • Check this [railscast](http://railscasts.com/episodes/204-xss-protection-in-rails-3) to see how to use `html_safe` safely – Subtletree May 06 '14 at 00:57
  • Note, calling html_safe on this is ONLY dangerous if it can be called on user supplied data like something submitted via a form. If you're doing it to a dropdown list you supplied on the backend then you're fine. – bkunzi01 Jun 09 '17 at 15:42
32

Using html_escape or raw alone will leave you vulnerable to XSS.

Instead, define a sensible version of the json_escape (a.k.a. j) helper:

module ActionView::Base
  def json_escape(s)
    result = s.to_s.gsub('/', '\/')
    s.html_safe? ? result.html_safe : result
  end

  alias j json_escape
end

Use it like this:

<script>
  var Accounts = new Backbone.Collection;
  Accounts.reset(<%=j @accounts.to_json.html_safe %>);
  var Projects = new Backbone.Collection;
  Projects.reset(<%=j @projects.to_json(:collaborators => true).html_safe %>);
</script>

See this post for further details.

Be aware that there's a naming conflict between j aliased to json_escape in ERB::Util and j aliased to escape_javascript in ActionView::Helpers::JavaScriptHelper. It's my hope that the JavaScriptHelper alias will be renamed to js.

John
  • 29,546
  • 11
  • 78
  • 79
  • 4
    This answer should be the accepted one - it's really shocking how many posts on the internet suggest using html_safe without escaping. – Ralf Aug 27 '12 at 09:20
  • 1
    I believe it should be `class ActionView::Base`, not `module`. Also, is there any reason to not make it html_safe by default? I can't think of any case where you'd want to json_escape and then html_escape. – Ralf Mar 28 '13 at 16:19
  • Quick question: where should the `module ActionView::Base ... end` definition be placed in the Rails project? Is there a standard path or file for it? – dB' Mar 17 '14 at 14:20
  • 1
    Rails 4: only `to_json.html_safe` is needed: `' – brauliobo Aug 02 '15 at 17:08
  • Do you mean `result.html_safe? ? result.html_safe : result`? – Sarah Vessels Jun 14 '17 at 17:35
1

To return json you have to write your render in the controller as follows:

render :json => @object

and the .to_json will automatically be called.

If you would want to include some relations, you could do the following:

render :json => @post.to_json(:include => [:comments, :authors])

I am not sure if it would work to use an erb to render your json.

nathanvda
  • 49,707
  • 13
  • 117
  • 139
0

Only to_json.html_safe is needed:

> `'<script>'.to_json`
=> "\"\\u003cscript\\u003e\""

Patch to make to_json respond to html_safe? and return true automatically:

# just use .to_json instead of .to_json.html_safe
ActiveSupport::JSON.class_eval do
  class << self
    def encode_with_html_safe *args
      self.encode_without_html_safe(*args).html_safe
    end
    alias_method_chain :encode, :html_safe
  end
end
brauliobo
  • 5,843
  • 4
  • 29
  • 34
0

You can call render in your controller, but that will be a problem if you need to possibly render more than a few partials for subsequent dom insertion by the handler. I needed to set multiple html fragments into a hash, and I've been able to return erb which basically uses hash.to_json.html_safe as neutrino suggests above and allows me to render multiple partials in the process.

wkhatch
  • 2,664
  • 7
  • 36
  • 45