I am getting a response from a Google book.

As it is, not changing anything:

16:34:04.1 (100890464)|USER_DEBUG|[15]|DEBUG|response: ProcessGBSBookInfo({"ISBN:0451526538":{"bib_key":"ISBN:0451526538","info_url":"https://books.google.com/books?id=7osYCoz_gdgC\u0026source=gbs_ViewAPI","preview_url":"https://books.google.com/books?id=7osYCoz_gdgC\u0026source=gbs_ViewAPI","thumbnail_url":"https://books.google.com/books/content?id=7osYCoz_gdgC\u0026printsec=frontcover\u0026img=1\u0026zoom=5","preview":"noview","embeddable":false,"can_download_pdf":false,"can_download_epub":false,"is_pdf_drm_enabled":false,"is_epub_drm_enabled":false}});

But when attempt to deserialize it as untyped, I'm receiving an error. This is how I'm trying to deserialize it:

 Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());

And this is the error I got:

16:34:04.1 (106368272)|FATAL_ERROR|System.JSONException: Unexpected character ('P' (code 80)): expected a valid value (number, String, array, object, 'true', 'false' or 'null') at input location [1,2]

Seems the response is not valid as it is: ProcessGBSBookInfo({...}

Should I build a class to deserialize to that type? just interested in one of the fields in the response.

What am I missing here?

Thanks in advance

2 Answers 2


In the documentation, google says that the callback parameter is:

Name of the JavaScript function we pass the return to.

Now since you are not in a javascript environment (plus you'd need to call eval on the response even if you were), you can't use this callback.

If you remove the callback param, Google responds with a slightly different format:

var _GBSBookInfo = {"0596000278"{.... ...}};

Either way, you are going to have to parse your original response or this one. I'd say that it's better to specify the callback, because you can use it as a self defined token to extract the data.

so if you callback is mycallback, you can then extract the json like:

String body = response.getBody();
String rawJSON = body.substringBetween('mycallback(',');');

Then you can proceed as per normal:

Map<String, Object> jsonObj = (Map<String, Object>)JSON.deserializeUntyped(rawJSON);

After doing a little digging, it appears that this API is returning JSONP (JSON with Padding) rather than plain JSON. It looks like it's meant to be used within a <script> tag to sidestep CORS.

Since that's not an option in Apex, you'll need to do some string manipulation to extract the JSON (before you try to deserialize it).

Provided that the string you get from the API isn't too long (i.e. less than 1 million characters), you can use String.replaceFirst() to get at the JSON proper.

String apiResult = apiCall.response().toString();
 .* - find any number of any character
 \\( - then an open parenthesis
 (.*) - make a capturing group for any number of any character
 \\) - then a close parenthesis
// Using '$1' for the replacement tells apex to give you back the data
//   for the first capturing group (you'd use $2 for a second capturing group
//   ...if a second one was defined)
String jsonResult = apiResult.replaceFirst('.*\\((.*)\\)', '$1');

// Now that you have the plain JSON, you can deserialize
Map<String, Object> deserialized = (Map<String, Object>)JSON.deserializeUntyped(jsonResult);
  • Thank you @Derek F, yes the response is adding some extra content, so removing this chars at the beggining and at the end solved the issue. Thank you both, did't know about that JSONP. Commented Aug 7, 2022 at 0:12
  • 1
    Also, instead of worrying about a 1M char limit, you could also just do that client-side.
    – sfdcfox
    Commented Aug 7, 2022 at 0:25

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .