5

I want to fetch auto suggestions from Youtube using the following call:

await fetch('https://suggestqueries.google.com/complete/search?client=youtube&ds=yt&q=skrillex&callback=suggestCallback')
.then(response => console.log(response))

This call obtains the following JSON as a result:

"suggestCallback && suggestCallback(["skrillex",[["skrillex",0],["skrillex bangarang",0],["skrillex make it bun dem",0],["skrillex scary monsters and nice sprites",0],["skrillex cinema",0],["skrillex live",0],["skrillex first of the year",0],["skrillex sicko mode",0],["skrillex kyoto",0],["skrillex remix",0]],{"k":1,"q":"bEJKh2TVzWqSICI3U3xEdXTC00g"}])"

I am struggling however to convert this result to an array of the "actual suggestions". For example, I want to store the JSON obtained above in the following format:

let myArray = ["skrillex", "skrillex bangarang", "skrillex make it bun dem", "skrillex scary monsters and nice sprites", "skrillex cinema", "skrillex live", "skrillex first of the year", "skrillex sicko mode", "skrillex kyoto" ,"skrillex remix"]

What would be a viable solution in order to achieve this?

EDJ
  • 843
  • 3
  • 17
  • 37
  • 2
    Your result is actually JSONP, you should be able to strip the non-JSON part of it before running `JSON.parse()` on it with regular expressions. With the value, you can get to the path you're interested in, that is `[1]`, and then use `Array.prototype.map()` to convert it to the data structure you're interested in. –  Mar 01 '19 at 22:28

2 Answers2

3

One way of doing this would be to use regex to filter a json response out of the response you get, and then create an array from that result.

let jsonp = 'suggestCallback && suggestCallback(["skrillex",[["skrillex",0],["skrillex bangarang",0],["skrillex make it bun dem",0],["skrillex scary monsters and nice sprites",0],["skrillex cinema",0],["skrillex live",0],["skrillex first of the year",0],["skrillex sicko mode",0],["skrillex kyoto",0],["skrillex remix",0]],{"k":1,"q":"bEJKh2TVzWqSICI3U3xEdXTC00g"}])';
    
    let myRe = /\[(.*)\]/g;
    let regExGroups = myRe.exec(jsonp);
    let json = JSON.parse(regExGroups[0]);
    
    let myArray = [];
    
    json[1].forEach((element) => {
     myArray.push(element[0]);
    });
    
    console.log('suggestions array', myArray);

https://jsfiddle.net/zeonfrost/s7b4aLr6/

But what you really should do is probably define a suggestCallback function what is jsonp

Nils Kähler
  • 2,645
  • 1
  • 21
  • 26
0

Option 1

This API you're using is not meant to be used as a regular JSON response, but rather a JSONP, which is a technique created to not only return you a JSON payload as response, but actually it returns executable JavaScript code, based on the request.

You see, on the request URL, https://suggestqueries.google.com/complete/search?client=youtube&ds=yt&q=skrillex&callback=suggestCallback , notice the last parameter on the call, which is the name of the callBack to be called automatically, from the response returned JavaScript code.

In this case your callback is named suggestCallback which means, the response JavaScript code, expects that there is a function declared with the name suggestCallback within the page, which gets automatically called when the response arrives, passing it the correct JSON payload.

To execute this code as a JSONP, the correct code would be :

(...)
    <script>
      let myArray = [];

      const url='https://suggestqueries.google.com/complete/search?client=youtube&ds=yt&q=skrillex&callback=suggestCallback';

      function suggestCallback(j) {
        console.log("myArray is currently ", myArray);
        myArray = j;
        console.log("myArray is now ", myArray);
      }

      function go() {
        const script = document.createElement("script");
        script.type = "text/javascript";
        script.src = url;
        document.body.appendChild(script);
      }
    </script>
(...)

So at this point, the only thing you would need to do is trigger the go() function from whatever action you want (ex: click of a button, body onload, etc.) and this code, would dynamically create a <script> tag, pointing to the API specified by url in the example. Of course you could pass the url as an argument of a go(url) function if for instance some parameters on the URL would be more dynamic.

Option 2

Let's say you still want to use the fetch call as you have it. In that case, the top part of the code would remain the same, only the go() function from my code would need to be changed.

In the new version, you also have 2 options, 1 is to execute directly eval which immediately tries to execute whatever JavaScript code you pass as a string to this function, or you can dynamically create a new <script> element, setting its text property to the JavaScript source to be executed.

The code for the go() function would then be :

(...)
      function go() {
        fetch(url)
          .then((response) => response.text())
          .then((src) => {
            // eval(src); // you could directly eval the response JS code

            // or you can use the traditional way of creating a script element
            const script = document.createElement("script");
            script.type = "text/javascript";
            script.text = src;
            document.body.appendChild(script);
          });
      }
(...)

I wrote this post to explain the expected way how to handle URLs that return JSONP payloads. The previous solutions are what I would consider a workaround, to extract the JSON payload from a JSONP payload, which is not the expected way to handle this kind of calls.

Telmo Dias
  • 3,938
  • 2
  • 36
  • 48
  • 1
    I removed the rant from your answer *on purpose*, please do not add it back. It's noise and doesn't add anything for readers. If you have a complaint please post on Meta. Thanks. – Eric Aya May 25 '22 at 10:13
  • I flagged the previous answer, for moderator review, as I think it was deleted unjustifiedly. Although not complete, it contains valid information to the user. – Telmo Dias May 25 '22 at 13:44