Ruby - JSON - JavaScript >> Unexpected token \ in JSON

Let’s assume the user created:

  • a Scene and named it to page 7".
  • a Tag and named it to tag 2"\\3"
    (Names containing double quote and backslash)
    image

.
I would like to display this names as it is in HtmlDialog, and I’m using JSON to transfer.
See simplified snippet:

module DezmoTest
  extend self
  
  def strange_names
    model = Sketchup.active_model
    layername = model.layers[1].name
    pagename = model.pages.first.name
    display_result_html( [layername, pagename] )
  end
  
  def display_result_html(array)
    json = array.to_json
    html = %{
      <!DOCTYPE html>
      <html>
        <body>
          <div id="example-div"></div>
          <script>
            var data = JSON.parse('#{json}');
            var mydiv = document.getElementById("example-div");
            mydiv.innerHTML = data.join(' ');
          </script>
        </body>
      </html>
    }

    @dialog = UI::HtmlDialog.new(
      :dialog_title => "Test Dezmo 2022",
      :width => 300,
      :height => 100,
      :left => 100,
      :top => 150,
      :style => UI::HtmlDialog::STYLE_DIALOG
    )
    @dialog.set_html(html)
    @dialog.show
  end
end
DezmoTest.strange_names

In DevTools console I got an error: (click to see animation):

In DevTools console I got an error:
jsonerr

Uncaught SyntaxError: Unexpected token \ in JSON at position...
Or if no backslash in the name:
Uncaught SyntaxError: Unexpected number in JSON at position...

For double quotes I find out to use e.g.

pagename = model.pages.first.name.gsub('"','\"')

But my questions:
How can I display backslash in Html as you see in the UI?
Do you know what other special characters can cause similar issues?
How are you dealing with this kind of strange names?

Since you’re inserting the JSON data into an HTML page within a JS script element you need to escape special characters. \ is an escape character in most languages.

json = array.to_json.gsub('\\', '\\\\)

Now, this might look odd because of the multiple \ characters, but that’s because you need to take into account that \ is an escape character in Ruby itself. So '\\' gets interpreted by Ruby as '\ and '\\\\' is interpreted as '\\'.

That means your json string has all \ characters escaped as \\ and when the JS engine parses the JS code it will expand them into \.

That is one way of doing it. Personally I pass JSON data to web/html dialogs as a response to the DOM being ready; htmldialog-examples/step05.rb at main · SketchUp/htmldialog-examples · GitHub

That saves me from dealing with all this escaping etc.

1 Like

That was a quick answer… :+1:

…almost
if I use like this:

json =  array.to_json.gsub('\\', '\\\\').gsub('"','\"')

there will be one less backslash in Html
image

Actually I’m doing the same, just my example above is simplified.
I will go more deep into your code. Thanks for the link!

hm… I wonder, is there yet another level of parsing? Does it have to be \\\\ => \\\\\\\\? (Yes, I’ve run into such cases)

Because you shouldn’t need to escape " within a single quoted JS string. Within a single quoted JS string only a single quote character should need escaping.

I’d suggest you use the Developer Tools in the webdialog and set breakpoint to the function that receives the data and inspect it. Verify every transformation your data is going through.

That does not help too…



Lets see this:

    json = array.to_json
    puts json

Returns to the Ruby console: ["tag 2\"\\\\3\"","page 7\""]
So, it seams the to_json giving the “right amount” of escape characters…

In DevTools

          <script>
            console.log('#{json}');
            var data = JSON.parse('#{json}');
            var mydiv = document.getElementById("example-div");
            mydiv.innerHTML = data.join(' ');
          </script>

the console shows:
["tag 2"\\3"","page 7""]
Uncaught SyntaxError: Unexpected token \ in JSON at position 8

This is looks like the string came through with double quotes inside the double quotes (Could be that the console did some formatting… ), but afterwards the JSON.parse() can not handle it.
__
(BTW: I was trying to check the example tutorial above, but since I have no experience with Vue.js I’m not sure if it is parse the given json-string or what it is doing there…)

Avoid puts when inspecting the full content of a string. Use .inspect as that will display the string with escape characters etc.

I just realized that what I do in my extension is a little different from the example (it should really be updated).
Have a look at TestUp:

With that utility method I call my JavaScript functions such as:

call('myJavascriptFunc', 'Hello "Quoted" world', { foo: 123, bar: 456 })

.to_json should take care of the escaping for you.

irb(main):015:0> json = str.to_json
=> "\"Hello \\\"quoted\\\" world\""
irb(main):016:0>

That being said, this all assumes you have a separate HTML page. If you embed the HTML/JS etc in the Ruby source you will have to deal with the confusion of multiple levels of escaping special characters.

:blush: I don’t get it…

Now I created a separate html file too. (test.html beside main.rb)
From the Ruby side I want to send an array (with two string in it). So I’m converting the array to json
Then I’m sending it after the html loaded. (1 sec Timer for now)

What is strange for me it is “arriving” to JavaScript as Array (according to console log in Devtools). Why? I assuming I have to parse back?!
so I can display it without any use of JSON.parse().
What I’m missing here? What is wrong?

# encoding: UTF-8
#dezmo_test/main.rb
module DezmoTest
  extend self
  
  def strange_names
    model = Sketchup.active_model
    layername = model.layers[1].name
    pagename = model.pages.first.name
    display_result_html( [layername, pagename] )
  end
  
  def display_result_html(array)
    p array
    argument_js = array.to_json
    p argument_js
    @dialog = UI::HtmlDialog.new(
      :dialog_title => "Test Dezmo 2022",
      :width => 300,
      :height => 100,
      :left => 100,
      :top => 150,
      :style => UI::HtmlDialog::STYLE_DIALOG
    )
    @dialog.set_file(File.dirname( __FILE__ ) + "/test.html")
    @dialog.show
    UI.start_timer(1, false) {
      @dialog.execute_script("update(#{argument_js});")
    }
  end
end
DezmoTest.strange_names
<!DOCTYPE html>
<html>
  <body>
    <div>Test</div>
    <div id="example"></div>
    <script>
      function update(json){
        console.log(json);
        <!-- var data = JSON.parse(json); -->
        var data = json;
        document.getElementById("example").innerHTML = data.join(' ');
      }
    </script>
  </body>
</html>

Ps (In a realty I’m trying to send an array, containing hashes. The hash values can be the string with the above mentioned characters, or an array with a same structure as the parent… like in this topic )

Although not a specific solution, I find it problematic that SketchUp and the API would allow the naming of collection items (tags, components, materials, etc.) that contain characters that serve mosly to cause issues.

Ie … \ / & ? + ~ "

All of the above are needed to URLencode data fields or might cause errors in JSON or other String serialization.

I would also discourage the use of … @ % : * < > |
… because often code will try to use the name property of collection items as a filename when saving out these resources. (This might not apply to some items like tag-layers which don’t save out as standalone resource files.)

1 Like

Parse? The argument you get on the JS side should be an collection of Array/Object with key,value pairs. Shouldn’t need to parse the incoming data.

Maybe your issues relates to how you are displaying the JS object in your HTML. innerHTML might be doing some processing here. It’s been a while since I used innerHTML, instead I’ve relied on data binding libraries like Vue to abstract away the details of displaying things correctly.

You are getting an Array on the JS side because that’s what you are passing along from Ruby:

argument_js = array.to_json

From your screenshot, are you not displaying the values as you expect them to appear?

Btw, can you share a test model that goes along with your code snippets?

I vaguely remember that it does. Ie, it is expecting the argument to be HTML.
If just inserting plain strings, then I think innerText is the DOM property to use.

I’m creating a JSON string for “communication” (by “converting” the Array to JSON string):

argument_js = array.to_json

So, the argument_js is a string. Right?
Then I’m sending this string to JavaScript:

@dialog.execute_script("update(#{argument_js});")

My assumption is that in the JavaScript:

      function update(json){
        console.log(json);
        <!-- var data = JSON.parse(json); -->
        var data = json;
        document.getElementById("example").innerHTML = data.join(' ');
      }

the argument ( json ) in update function is also a string. Isn’t it?

To parse a JSON string received by another application NORMALLY I assuming I have to use the JSON.parse( ) method on the received JSON string. But it is already an Array!! As you see on a screenshot above. So I commented out a line where the parse should happen… That is what I don’t understand, why?
.


I checked it with innerText = as well as imput.value = and it seams does not matter here.
.


Actually my last code above working perfectly, I’m getting the “strange names” displayed properly.
I just don’t understand why I do need to NOT USE

 var data = JSON.parse(json);

on a JavaScript side?
.

strange_names.skp (11.2 KB)
Noting special, an empty model with the scene/layer names mentioned in my first post.
For reference here are my code (both .html and rb files)
dezmo_test.zip (969 Bytes)

NO actually it is not as you used …

To send as a String (or more accurately have JS see a String,) you must use:

@dialog.execute_script("update('#{argument_js}');")

… or …

@dialog.execute_script(%[update("#{argument_js}");])

EDIT: Scratch the 2nd example, as JSON uses embedded double quotes, so I think you need to wrap in single quotes.

Oh yes, it’s also possible that I’m not fully aware of “String Literals” either … :blush:
__

If I’m using this :

then this

<!DOCTYPE html>
<html>
  <body>
    <div>Test</div>
    <div id="example"></div>
    <script>
      function update(json){
        console.log(json);
        var data = JSON.parse(json);
        console.log(data);
        document.getElementById("example").innerText = data.join(' ');
      }
    </script>
  </body>
</html>

I’m still getting this


.


Honestly, I’m happy with the solution of sending and receiving “Array” … but I want to understand what’s going on with JSON?

Well the error from JS’s JSON.parse says it cannot handle embedded \ at position 8 of the string.

IMO, allowing or using such problematic characters in resource (tags, materials, etc.) names is just silly.
Agonizing over this is a waste of time. I would just strip out any characters that JSON.parse cannot handle, … OR … replace them with & codes if you intend them to be displayed in the HTML.

Aggre, but it wasn’t me, I inherited it. :slight_smile:

I’ve wasted my time on several others, too :wink:


Seriously.
It seams to be my last code ( post#7 ) is doing what I want.
So I will keep sending “Array” or “Array_like_string” or whatever is this

"#{array.to_json}"

syntax means. :innocent:

Do you think this will cause any (more) problems ?

No, this is perfect.
The json string on the ruby side will become a javascript object on the javascript side. No need to re parse it, since you are not actually passing the data as a string, you are creating a dynamic script that is getting interpreted by the javascript v8 engine. Imagine you have a hash {"a" =>1}. When you serialize that into a json string it will be the equivalent to json ="{\"a\":1}"

If you now do js = "doAction(#{json}) you actally create "doAction({\"a\":1})". When being sent over to javascript, javascript will interprete this as if you manually typed this in the console:
doAction({"a":1}) (you can try that by opening the developer console in the html dialog)

Not the easiest to write on my portable phone, if something is not clear, please feel free to respond.

2 Likes

I guess, I have to immerse myself in it a little more, but your explanation sounds completely logical.
Thanks for pointing this out! :+1:

The string your are sending to javascript is getting evaluated, just like ruby has an eval method.
So, a json string being sent to javascript will evaluate to an object.

like this in ruby:

hash = {"a" => 5}
dynamic_code = "UI.messagebox(#{hash}.keys.first)"
eval dynamic_code

this will show you a messagebox containing a:
image

When you do execute_script, the same thing is happening, you are creating dynamic code. A ruby object serialized to json will evaluate to an object in javasript.

hash = {"a" => 5}
json  = JSON.dump(hash)
dynamic_code = "alert(Object.keys(#{json})[0])"
@dialog.execute_script(dynamic_code)

should give you:
image

it really is the equivalent as if you would have the following javscript code:

alert(Object.keys({"a": 5})[0])

Now, alerting the first key of an object does not make much sense, but the thing is: it is very safe to send json to htmldialogs, and it is in my opinion the only right thing to do, since json translates to native javascript objects (hence the reason json stands for javascript object notation)

3 Likes

@kengey I will mark your answer as a solution because I have better understood some of the methods with your explanations.
But the posts of @tt_su and @DanRathbun also helped a lot. Thanks everyone!

I can now handle objects even if the user gives them “interesting” names:

Spoiler...;-)

3 Likes