0

I need to encode some hash containing URL string. I use to_json method and I need backslash in front of each slash (as PHP print such strings). For example:

hash = {"url":"http:\\/\\/example.com\\/test"}
hash.to_json

The result is

{:url=>"http:\\/\\/example.com\\/test"}

While I need (and PHP's json_encode returns string with a single backslash).

{:url=>"http:\/\/example.com\/test"}

It's very important to keep the string as in PHP in case of encoding. Because strings with double and single backslashes get different results.

UPD: The problem is not in communication. I need to encode my JSON using HMAC (SHA384). And the result is different in PHP and Ruby when I'm using URL strings. If the string doesn't contain backslash all works fine...

PHP implementation introduces the backslashes. JSON using by PHP looks so {"url":"http:\/\/example.com\/test"} while Ruby's JSON is {"url":"http:\\/\\/example.com\\/test"}

Eugene
  • 39
  • 8
  • I know the difference between `puts hash` and `p hash`. But inside Ruby, as I think, it's anyway double backslash. – Eugene Jul 01 '19 at 07:33
  • Your question is unclear. There are exactly three backslashes in your string, one before each slash. None of them is a double backslash. Also, the result that you claim comes from `hash.to_json` is actually a Ruby literal and not JSON. – Jörg W Mittag Jul 01 '19 at 08:23
  • @JörgWMittag `{:url=>"http:\\/\\/example.com\\/test"}` here is 2 backslashes before every slash, isn't it? I need a line where these 2 backslashes will be one backslash. Sorry for the misunderstanding, I asked as I see the problem. – Eugene Jul 01 '19 at 08:45
  • No, there is only one backslash before every slash. In the output of `String#inspect`, backslashes are used as escape characters *in the display* to escape special characters. In this case, the first backslash that is *displayed* tells you that the backslash after it is actually part of the string and not an escape character. So, there are two backslashes *displayed* so that you know that there is only *one* backslash actually in the string. You can trivially test this by checking the length of the string. – Jörg W Mittag Jul 13 '19 at 13:34

4 Answers4

3

My apologies, you do seem to have a valid issue on your hand. The key is this: Why is the slash an escapable character in JSON? and its duplicate target, JSON: why are forward slashes escaped?. Since both unescaped slashes and escaped slashes are allowed, Ruby chose to not escape them, and PHP chose to escape them, and both approaches are correct.

(Aside: there's a bit of a complication in talking about this because \ is an escape character both for a string literal, and for JSON strings. Thus, in this answer, I take care to puts (or echo/print_r) all the values, to see the strings that do not have the string literal backslash escapes, only the backslashes that are actually present in the strings.)

Thus, the JSON {"url":"http:\/\/example.com\/test"} is a representation of the Ruby hash { 'url' => 'http://example.com/test' }, where slashes are escaped (as PHP's json_encode would do it). Ruby's to_json' would render that as{"url":"http://example.com/test"}`:

# Ruby
json1 = '{"url":"http:\/\/example.com\/test"}'
puts json1                        # => {"url":"http:\/\/example.com\/test"}
puts JSON.parse(json1)            # => {"url"=>"http://example.com/test"}
puts JSON.parse(json1).to_json    # => {"url":"http://example.com/test"}

# PHP
$json1 = '{"url":"http:\/\/example.com\/test"}';
echo $json1;                           # {"url":"http:\/\/example.com\/test"}
print_r(json_decode($json1));          # stdClass Object
                                       # (
                                       #     [url] => http://example.com/test
                                       # )
echo json_encode(json_decode($json1)); # {"url":"http:\/\/example.com\/test"}

On the other hand, {"url":"http:\\/\\/example.com\\/test"} (represented in Ruby and PHP as the string '{"url":"http:\\\\/\\\\/example.com\\\\/test"}') is a representation of the Ruby hash { 'url' => 'http:\/\/example.com\/test' }, where there are actual backslashes, but the slashes are not escaped. PHP's json_encode would render this value as {"url":"http:\\\/\\\/example.com\\\/test"}.

# Ruby
json2 = '{"url":"http:\\\\/\\\\/example.com\\\\/test"}'
puts json2                        # => {"url":"http:\\/\\/example.com\\/test"}
puts JSON.parse(json2)            # => {"url"=>"http:\\/\\/example.com\\/test"}
puts JSON.parse(json2).to_json    # => {"url":"http:\\/\\/example.com\\/test"}

# PHP
$json2 = '{"url":"http:\\\\/\\\\/example.com\\\\/test"}';
echo $json2;                           # {"url":"http:\/\/example.com\/test"}
print_r(json_decode($json2));          # stdClass Object
                                       # (
                                       #     [url] => http:\/\/example.com\/test
                                       # )
echo json_encode(json_decode($json2)); # {"url":"http:\\\/\\\/example.com\\\/test"}

PHP json_encode has an option to prevent the PHP's default of escaping of backslashes:

# PHP
echo json_encode('/');                         # "\/"
echo json_encode('/', JSON_UNESCAPED_SLASHES); # "/"

Ruby does not have a similar option to force escaping of slashes, but since a slash has no special meaning in JSON, we can just manually replace / with \/:

# Ruby
puts '/'.to_json                  # "/"
puts '/'.to_json.gsub('/', '\/')  # "\/"
Amadan
  • 191,408
  • 23
  • 240
  • 301
1

Use single quotes around strings if you don't want to deal with escaping backslashes.

hash = { url: 'http:\/\/example.com\/test' }
json = hash.to_json
puts json

# => {"url":"http:\\/\\/example.com\\/test"}

Just a quick reminder: in JSON, backslashes need to be escaped because they are considered as control characters.

This way, when PHP parses this JSON document, you will get your string with a single backslash before each slash.

Richard-Degenne
  • 2,892
  • 2
  • 26
  • 43
  • Yes, that's right about single quotes. But I need to keep them in my JSON result as a single backslash, while Ruby gives me only double backslash. Even with double quotes. – Eugene Jul 01 '19 at 08:49
  • I mean `{ url: 'http:\/\/example.com\/test' }` and `{ url: "http:\/\/example.com\/test" }` got the same result `{"url":"http:\\/\\/example.com\\/test"}` while I need `{"url":"http:\/\/example.com\/test"}` – Eugene Jul 01 '19 at 08:50
0

The problem behind your question is probably the real problem. I'm not sure because your question is not totally clear to me so I'm taking a guess/assumption here with my answer.

My assumption here is that you want to communicate between ruby and php, with json.

Well, in that case your don't have to have a problem (with backslashes).

Let ruby .to_json (JSON.generate(..)) and JSON.parse(..) solve the ruby part and let json_encode() and json_decode() solve the the php part and you are done.

so in ruby:
- don't use extra escaping-backslashes, let .to_json solve that for you
- use the literal url string you would type in your browser like so:

hash = {"url":"http://example.com/test"} # hash is a ruby object
puts hash.to_json                        # => {"url":"http://example.com/test"} is JSON (string)

then in php:

var_dump( json_decode('{"url": "http://example.com/test"}') );

gives you:

object(stdClass)#1 (1) {
  ["url"]=>
  string(23) "http://example.com/test"
}
var_dump( json_decode('{"url": "http:\/\/example.com\/test"}') );

gives you:

object(stdClass)#1 (1) {
  ["url"]=>
  string(23) "http://example.com/test"
}

Note that both JSON strings end up to be parsed correctly in PHP and end up as a normal PHP object

Alphons
  • 303
  • 2
  • 6
  • Thank you for your answer. But the problem is not in communication. I need to encode my JSON using HMAC (SHA384). And the result is different in PHP and Ruby when I'm using URL strings. If the string doesn't contain backslash all works fine... – Eugene Jul 01 '19 at 09:01
  • Ah, that's more clear, thanks. So does the PHP implementation (of HMAC(SHA384)) introduces the backslashes in JSON? – Alphons Jul 01 '19 at 09:06
  • PS suggestion: Add your comment to your original question for more clarity ('I need to encode my JSON using HMAC (SHA384). And the result is different in PHP and Ruby when I'm using URL strings. If the string doesn't contain backslash all works fine') – Alphons Jul 01 '19 at 09:08
  • yes, PHP implementation introduces the backslashes. JSON using by PHP looks so `{"url":"http:\/\/example.com\/test"}` while Ruby's JSON is `{"url":"http:\\/\\/example.com\\/test"}` – Eugene Jul 01 '19 at 09:18
  • if you use the option: JSON_UNESCAPED_SLASHES the backslashes won't be generated. Like so: echo json_encode(array("url" => "http://example.com/test"), JSON_UNESCAPED_SLASHES); result ==> {"url":"http://example.com/test"} – Alphons Jul 01 '19 at 09:36
  • better formatted: if you use the option: JSON_UNESCAPED_SLASHES the backslashes won't be generated. Like so: `echo json_encode(array("url" => "http://example.com/test"), JSON_UNESCAPED_SLASHES);` result ==> `{"url":"http://example.com/test"}` – Alphons Jul 01 '19 at 09:49
  • Sure, but now PHP's result is right. And I need the same in Ruby. Not by modifying PHP's opts or codes, but modifying Ruby's string to get something like `qwe\/123`. Now Ruby gets me only `qwe\\/123`. – Eugene Jul 01 '19 at 10:06
  • Still not sure if this is the real problem. But a solution is: `hash = {"url": "http://example.com/test"} # hash is a ruby object puts hash.to_json.split('/').join('\/') # ==> {"url":"http:\/\/example.com\/test"}` – Alphons Jul 01 '19 at 10:45
0

Try like below

hash = {"url":"http:\\/\\/example.com\\/test"}
hash[:url] = hash[:url].delete("\\")
hash.to_json  #"{\"url\":\"http://example.com/test\"}"

Hope it will helps you

  • Yes, but this removes all backslashes from the string. While I need to keep ONE backslash. For example, if the string looks like this `"qwe\\/123"` I need to get `"qwe\/123"`. – Eugene Jul 01 '19 at 10:02
  • There is two back slash on before and after the string –  Jul 01 '19 at 10:19