JSON encoding wrongly escaped (Rails 3, Ruby 1.9.2) - ruby-on-rails

In my controller, the following works (prints "oké")
puts obj.inspect
But this doesn't (renders "ok\u00e9")
render :json => obj
Apparently the to_json method escapes unicode characters. Is there an option to prevent this?

To set the \uXXXX codes back to utf-8:
json_string.gsub!(/\\u([0-9a-z]{4})/) {|s| [$1.to_i(16)].pack("U")}

You can prevent it by monkey patching the method mentioned by muu is too short. Put the following into config/initializers/patches.rb (or similar file used for patching stuff) and restart your rails process for the change to take affect.
module ActiveSupport::JSON::Encoding
class << self
def escape(string)
if string.respond_to?(:force_encoding)
string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY)
end
json = string.gsub(escape_regex) { |s| ESCAPED_CHARS[s] }
json = %("#{json}")
json.force_encoding(::Encoding::UTF_8) if json.respond_to?(:force_encoding)
json
end
end
end
Be adviced that there's no guarantee that the patch will work with future versions of ActiveSupport. The version used when writing this post is 3.1.3.

If you dig through the source you'll eventually come to ActiveSupport::JSON::Encoding and the escape method:
def escape(string)
if string.respond_to?(:force_encoding)
string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY)
end
json = string.
gsub(escape_regex) { |s| ESCAPED_CHARS[s] }.
gsub(/([\xC0-\xDF][\x80-\xBF]|
[\xE0-\xEF][\x80-\xBF]{2}|
[\xF0-\xF7][\x80-\xBF]{3})+/nx) { |s|
s.unpack("U*").pack("n*").unpack("H*")[0].gsub(/.{4}/n, '\\\\u\&')
}
json = %("#{json}")
json.force_encoding(::Encoding::UTF_8) if json.respond_to?(:force_encoding)
json
end
The various gsub calls are forcing non-ASCII UTF-8 to the \uXXXX notation that you're seeing. Hex encoded UTF-8 should be acceptable to anything that processes JSON but you could always post-process the JSON (or monkey patch in a modified JSON escaper) to convert the \uXXXX notation to raw UTF-8 if necessary.
I'd agree that forcing JSON to be 7bit-clean is a bit bogus but there you go.
Short answer: no.

Characters were not escaped to unicode with the other methods in Rails2.3.11/Ruby1.8 so I used the following:
render :json => JSON::dump(obj)

That is the correct encoding. JSON doesn't requre Unicode characters to be escaped, but it is common for JSON libraries to produce output which contains only 7-bit ASCII characters, to avoid any potential encoding problems in transit.
Any JSON interpreter will be able to consume that string and reproduce the original. To see this in action, just type javascript:alert("ok\u00e9") into your browser's location bar.

render :json will call .to_json on the object if it's not a string. You can avoid this problem by doing:
render :json => JSON.generate(obj)
This will by pass a string directly and therefore avoid the call to ActiveSupport's to_json.
Another approach would be to override to_json on the object you are serializing, so in that case, you could do something like:
class Foo < ActiveRecord::Base
def to_json(options = {})
JSON.generate(as_json)
end
end
And if you use ActiveModelSerializers, you can solve this problem by overriding to_json in your serializer:
# controller
respond_with foo, :serializer => MySerializer
# serializer
attributes :bar, :baz
def to_json(options = {})
JSON.generate(serializable_hash)
end

I have got a very tricky way to solve this problem. Well, if to_json did not allow you to have the correct code, then you could directly try to write :
render text: tags
render json: tags or render json: tags.to_json will always auto transfer the encoding style, but if you use render text:tags, then the string will stay as it is. And I think jQuery could still recognize the data.

Related

Stopping a string being encoded within a hash Ruby on Rails

I am trying to not jsonify a string within a hash. It already has been escaped.
Reading around the way of handling this for PORO is to overwrite as_json. So I wrapped the string in another object. But as I'm dealing with just a string that leads to a stack level too deep when I return the object. When I return the already encoded string, it obviously tries to escape it. activesupport-6.0.3.2/lib/active_support/json/encoding.rb
def jsonify(value)
case value
when String
EscapedString.new(value)
when Numeric, NilClass, TrueClass, FalseClass
value.as_json
when Hash
Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }]
when Array
value.map { |v| jsonify(v) }
else
jsonify value.as_json
end
end
What I could do is monkey patch the above method to accept the class I have wrapped the string in and do nothing.
I guess the other option is to parse the JSON string back into a hash and let it follow the normal procedure. That would impact our performance too much though.
So my question is, Is there are more elegant way of telling Rails to do nothing when encountering a specified object when it tries to jsonify it.?
Edit:
Im using this already jsonified string like:
{foo: MyStringJsonWrapper.new('{"bar":"foobar"}')}.to_json
I was looking around in the file activesupport-6.0.3.2/lib/active_support/json/encoding.rb and noticed you can configure which class is used to perform the json encoding. It is currently JSONGemEncoder which is contained within that class.
So I made my own class:
class MyJSONEncoder < ActiveSupport::JSON::Encoding::JSONGemEncoder
class AlreadyEscapedString < String
def to_json(*)
self
end
end
private
def jsonify(value)
if value.is_a?(AlreadyEncodedStringWrapper)
AlreadyEscapedString.new(value.already_jsonified)
else
super
end
end
end
and
class AlreadyEncodedStringWrapper
attr_accessor :already_jsonified
def initialize already_jsonified
#already_jsonified = already_jsonified
end
def as_json(options = {})
self
end
end
The extra class of AlreadyEscapedString exists to overwrite the behaviour of EscapedString(found in the same encoding class) but do nothing.
Then just set:
ActiveSupport.json_encoder = MyJSONEncoder
Example:
jsonified_hash = {foobar: "barbaz"}.to_json
already_encoded_string = AlreadyEncodedStringWrapper.new(jsonified_hash)
{foo: already_encoded_string}.to_json
=> "{\"foo\": {\"foobar\": \"barbaz\"}}"

to_json not converting special characters to unicode style

I'm having problem with special characters when casting a hash to a json string.
Everything works fine with Ruby 2.0 / Rails 3.2.21, that is,
puts "“".to_json
#"\u201c"
But with Ruby 2.3.0 / Rails 4.2.5.1 I get
puts "“".to_json
#"“"
Is there any way to force Ruby 2.3.0 to convert special characters to unicode style strings (\uXXXX) ?
Remark:
Notice that in Ruby 2.3 / Rails 4, we get
"“".to_json.bytesize == 5 #true
However, in 2.0 we get
"“".to_json.bytesize == 8 #true
So clearly it's the string itself that is different, not different output formats.
I ❤ Rails (just kidding.)
In Rails3 there was a hilarious method to damage UTF-8 in JSON. Rails4, thanks DHH, freed from this drawback.
So, whether one wants the time-back machine, the simplest way is to monkeypatch ::ActiveSupport::JSON::Encoding#escape:
module ::ActiveSupport::JSON::Encoding
def self.escape(string)
if string.respond_to?(:force_encoding)
string = string.encode(::Encoding::UTF_8, :undef => :replace)
.force_encoding(::Encoding::BINARY)
end
json = string.
gsub(escape_regex) { |s| ESCAPED_CHARS[s] }.
gsub(/([\xC0-\xDF][\x80-\xBF]|
[\xE0-\xEF][\x80-\xBF]{2}|
[\xF0-\xF7][\x80-\xBF]{3})+/nx) { |s|
s.unpack("U*").pack("n*").unpack("H*")[0].gsub(/.{4}/n, '\\\\u\&')
}
json = %("#{json}")
json.force_encoding(::Encoding::UTF_8) if json.respond_to?(:force_encoding)
json
end
end
More robust solution would be to corrupt the result:
class String
def rails3_style
string = encode(::Encoding::UTF_8, :undef => :replace).
force_encoding(::Encoding::BINARY)
json = string.
gsub(/([\xC0-\xDF][\x80-\xBF]|
[\xE0-\xEF][\x80-\xBF]{2}|
[\xF0-\xF7][\x80-\xBF]{3})+/nx) { |s|
s.unpack("U*").pack("n*").unpack("H*")[0].gsub(/.{4}/n, '\\\\u\&')
}
json = %("#{json}")
json.force_encoding(::Encoding::UTF_8) if json.respond_to?(:force_encoding)
json
end
end
puts "“".to_json.rails3_style
#⇒ "\u201c"
I hardly could understand why anybody might want to do this on purpose, but the solution is here.

Removing quotes from JSON Rails 4 and JBuilder

I'm trying to send a JSON API response from my rails app to Dasheroo which expects the following format:
{
my_statistic: { type: 'integer', value: 1, label: 'My Statistic' }
}
However it is not happy with my data structure generated by the following code:
In controller:
def count_foo_members
#foo = Foo.all.count
end
In count_foo_members.json.jbuilder:
json.foo_members do
json.type 'integer'
json.value #foo
json.label 'Foo Members'
end
If I open this route in my browser I can see the following:
{
"foo_members":{"type":"integer","value":1,"label":"Foo Members"}
}
From the results above, the only thing that I can see that could have an effect on the result is the fact that my JSON result has quotation marks around the JSON Key values.
My question thus is: How can I remove these quotation marks in Rails 4 and JBuilder?
JSON.parse(you_response) and you get standart hash.
You cannot remove the quotation marks from the keys. The responsibility is on the consumer (Dasheroo) to parse your JSON string into a JavaScript Object, which will "remove" the quotes from the keys.
Read json-object-with-or-without-quotes for further practical insight.

How to return 2 json objects at once?

I have a controller returning a json structure like so:
def show
# .......
o_json = deep_object_1_to_json(o)
render :json => o_json
end
private
def deep_object_1_to_json(o)
o.to_json(
:include => {....})
end
Now I need to extend it to return 2 objects. However the obvious solution is giving me problems:
def show
# .......
o1_json = deep_object_1_to_json(o)
o2_json = deep_object_2_to_json(o)
render :json =>
{
:object_1 => o1_json,
:object_2 => o2_json
}
end
This returns a json object with 2 strings of escaped json data!
The deep_object_2_to_json functions already have several layers of nested includes so I would rather not have to refactor these into a single function. Is there a way to make this easily extendable to add more objects in the future without the double escaping problem above?
Thanks for any pointers.
Sounds like you should be constructing something upon which to_json can easily be called.
The obvious candidate for active record objects is as_json. This does everything that to_json does (include the :include option and so on) except actually turning the object into json. Instead you get back a ruby hash which you can manipulate as you want and then call to_json. For example you could do
render :json => {
:o1 => object1.as_json(:include => :blah),
:o2 => object2.as_json(:include => :blah)
}
Your controller shouldn't be serializing the object as JSON until right before it hands it off to be rendered.
In other words deep_object_1_to_json should just be deep_object_1. Then you can package both return values into an array or hash and render the as JSON.
def show
# .......
o1 = deep_object_1(o)
o2 = deep_object_2(o)
render :json =>
{
:object_1 => o1,
:object_2 => o2
}
end
It might be a pain to change it now, but for the future of your system, you really ought to be doing it this way. JSON is just a format for sending objects over the wire or to disk; none of your code should have any references whatsoever to JSON unless it is passing it off to be rendered.
If you can refactor your class to return it's JSON via the to_json method, you can simply stick two or more objects into an array and call to_json on the array:
1.9.3-p125 :001 > require 'json'
=> true
1.9.3-p125 :002 > [{foo: "bar"}, {bar: "foo"}].to_json
=> "[{\"foo\":\"bar\"},{\"bar\":\"foo\"}]"
Example:
def to_json
super(include: [:some_association])
end

convert ruby hash to URL query string ... without those square brackets

In Python, I can do this:
>>> import urlparse, urllib
>>> q = urlparse.parse_qsl("a=b&a=c&d=e")
>>> urllib.urlencode(q)
'a=b&a=c&d=e'
In Ruby[+Rails] I can't figure out how to do the same thing without "rolling my own," which seems odd. The Rails way doesn't work for me -- it adds square brackets to the names of the query parameters, which the server on the other end may or may not support:
>> q = CGI.parse("a=b&a=c&d=e")
=> {"a"=>["b", "c"], "d"=>["e"]}
>> q.to_params
=> "a[]=b&a[]=c&d[]=e"
My use case is simply that I wish to muck with the values of some of the values in the query-string portion of the URL. It seemed natural to lean on the standard library and/or Rails, and write something like this:
uri = URI.parse("http://example.com/foo?a=b&a=c&d=e")
q = CGI.parse(uri.query)
q.delete("d")
q["a"] << "d"
uri.query = q.to_params # should be to_param or to_query instead?
puts Net::HTTP.get_response(uri)
but only if the resulting URI is in fact http://example.com/foo?a=b&a=c&a=d, and not http://example.com/foo?a[]=b&a[]=c&a[]=d. Is there a correct or better way to do this?
In modern ruby this is simply:
require 'uri'
URI.encode_www_form(hash)
Quick Hash to a URL Query Trick :
"http://www.example.com?" + { language: "ruby", status: "awesome" }.to_query
# => "http://www.example.com?language=ruby&status=awesome"
Want to do it in reverse? Use CGI.parse:
require 'cgi'
# Only needed for IRB, Rails already has this loaded
CGI::parse "language=ruby&status=awesome"
# => {"language"=>["ruby"], "status"=>["awesome"]}
Here's a quick function to turn your hash into query parameters:
require 'uri'
def hash_to_query(hash)
return URI.encode(hash.map{|k,v| "#{k}=#{v}"}.join("&"))
end
The way rails handles query strings of that type means you have to roll your own solution, as you have. It is somewhat unfortunate if you're dealing with non-rails apps, but makes sense if you're passing information to and from rails apps.
As a simple plain Ruby solution (or RubyMotion, in my case), just use this:
class Hash
def to_param
self.to_a.map { |x| "#{x[0]}=#{x[1]}" }.join("&")
end
end
{ fruit: "Apple", vegetable: "Carrot" }.to_param # => "fruit=Apple&vegetable=Carrot"
It only handles simple hashes, though.

Resources