Plugin development: JSON returned but could not be parsed

To get in touch with plugin development, I am working on a small project to display visitor statistics in the panel. The data is coming from an external service. To start with, I largely follow the guides My first Panel view and Plugin setup for Panel plugins.

One deviation is that the data doesn’t get fetched by Remote::get(). Instead I use a direct curl-request within my route:

$ch = curl_init($requestUrl);

curl_setopt_array($ch, [
   CURLOPT_HTTPHEADER => array(
      'Content-Type: application/json',
      'Api-Key: ' . $this->apiKey
   )
]);

return curl_exec($ch);

This works fine. When I open the route in the browser via URL, the requested data gets returned:

{
  "ok": true,
  "start": "2021-01-26T23:00:00.000Z",
  "end": "2021-02-26T23:59:59.999Z",
  "timezone": "Europe/Berlin",
  "pageviews": 168,
  "generated_in_ms": 22
}{
    "status": "ok",
    "message": "ok",
    "code": 200
}

But when I call the route in the vue component, the following error occurs:

The JSON response of the API could not be parsed:

{ "ok": true, "start": "2021-01-26T23:00:00.000Z", "end": "2021-02-26T23:59:59.999Z", "timezone": "Europe/Berlin", "pageviews": 168, "generated_in_ms": 48 }{"status":"ok","message":"ok","code":200}

So the expected data does get returned but can’t be parsed at the same time.

My vue component uses get() like suggested in the guide:

methods: {
   load() {
      this.$api
         .get("test")
         .then(pageviews => {
            this.pageviews = pageviews;
         });
   }
}

Any leads as to what is causing the error?

Check what you get back

methods: {
   load() {
      this.$api
         .get("test")
         .then(pageviews => {
            console.log(pageviews);
            //this.pageviews = pageviews;
         });
   }
}

Well… nothing. It seems not to get there at all :thinking:

Of course regardless of console.log() these two are showing:

app.js:1 Error: The JSON response from the API could not be parsed
    at Object.onParserError (app.js:1)
    at Object.request (app.js:1)

app.js:1 Uncaught (in promise) Error: The JSON response from the API could not be parsed
    at Object.onParserError (app.js:1)
    at Object.request (app.js:1)

Could you please post your code for the api endpoint?

First of all, a CSRF token is set for debugging in site/config/config.php:

'api' => [
   'csrf' => 'test',
]

Now the plugin. In index.php:

'api' => [
    'routes' => require_once __DIR__ . '/src/routes.php'
],

Here’s the route in /src/routes.php, requesting data for the field “pageviews”:

[
   'pattern' => 'test',
   'action' => function() {
      $sa = new Sa();
      $content = $sa->statsApi([
         'fields' => 'pageviews',
         'info' => 'false'
         ]);
      return $content;
   }
]

In the class Sa there’s the function statsApi() with the curl-request:

public function statsApi($parameters)
{

   // Setup cURL
   $requestUrl = $this->buildRequestUrl($parameters);
   $ch = curl_init($requestUrl);

   curl_setopt_array($ch, [
      CURLOPT_HTTPHEADER => array(
         'Content-Type: application/json',
         'Api-Key: ' . $this->apiKey
      )
   ]);

   // Send the request
   return curl_exec($ch);
}

And also the function buildRequestUrl():

public function buildRequestUrl($parameters)
{
   $p = $parameters;
   $requestUrl   = 'https://simpleanalytics.com/' . $this->domain . '.json';	

   // parameter, default values if nothing is set
   $requestUrl .= "?version=";
   $requestUrl .= (isset($p['version'])) ? $p['version'] : '4';

   // fields (remove possible whitespaces)
   $requestUrl .= "&fields=" . Str::replace($p['fields'], ' ', '',);
   
   
   return $requestUrl;
}

Have you removed the csrf token from your config again for the real request?

Yes, but without effect.

Then the problem is probably the way your JSON is formatted. I think you have to parse what comes back from curl_exec first to return an array.

Indeed, that is exactly the right hint!

I have now found out that I don’t need the direct curl request at all, but can use the Kirby method Remote::get() instead. I definitely prefer that.

$requestUrl = $this->buildRequestUrl($parameters);
$response = Remote::get($requestUrl, [
   'headers' => [
      'Content-Type: application/json',
      'Api-Key: ' . $this->apiKey
   ]
]);
return $response->json();

The solution to the actual error is to json() the response:

return $response->json();

Thank you both, @pixelijn and @texnixe for your support!

And by the way, is there any visual overview of the UI kit and the individual components? The list is very clear but somewhat abstract. A gallery would be really nice. I seem to remember that there was some talk of this on slack once?

Good decision, I was wondering why you wanted to mess with curl when the Remote class does everything under the hood for you :wink:

Not at the moment…