Need to JSON stringify an object in ExtendScript
Asked Answered
C

2

10

I am working on processing meta data information of my Indesign document links, using ExtentdScript.

I want to convert the object to string using JSON.stringify but when I use it, I am getting error saying:

can't execute script in target engine.

If I remove linkObjStr = JSON.stringify(linksInfObj); from below code, then everything works fine.

What is the equivalent to JSON.stringify in ExtendScript, or is there any other possibilities to display linksInfObj with its proper contents instead [object object]?

for (var i = 0, len = doc.links.length; i < len; i++) {

    var linkFilepath = File(doc.links[i].filePath).fsName;
    var linkFileName = doc.links[i].name;

    var xmpFile = new XMPFile(linkFilepath, XMPConst.FILE_INDESIGN, XMPConst.OPEN_FOR_READ);
    var allXMP = xmpFile.getXMP();

    // Retrieve values from external links XMP.
    var documentID = allXMP.getProperty(XMPConst.NS_XMP_MM, 'DocumentID', XMPConst.STRING);
    var instanceID = allXMP.getProperty(XMPConst.NS_XMP_MM, 'InstanceID', XMPConst.STRING);
    linksInfObj[linkFileName] = {'docId': documentID, 'insId': instanceID};
    linkObjStr = JSON.stringify(linksInfObj);

    alert('Object' + linksInfObj, true); // I am getting [Object Object] here
    alert('String' + linkObjStr, true);

}
Chiles answered 30/5, 2019 at 9:11 Comment(1)
If you are using vscode debugger, from my experience, that error means either a syntax error, or it does not find an include file.Magnify
I
22

ExtendScript does not include a JSON object with the associated methods for parsing, namely JSON.parse() and JSON.stringify(). Nor does it provide any other builtin feature for parsing JSON.


Solution:

Consider utilizing a polyfill to provide JSON functionality such as JSON-js created by Douglas Crockford.

What you'll need to do:

  1. Download the JavaScript file named json2.js from the Github repo and save it in the same location/folder as your .jsx file.

    Note You can just copy and paste the raw version of json2.js from the same Github repo to create the json2.js file manually if you prefer.

  2. Then at the top of your current .jsx file you'll need to #include the json2.js file by adding the following line of code:

     #include "json2.js";
    

    This is analogous to how you might utilize the import statement to include a module in modern day JavaScript (ES6).

    A pathname to the location of the json2.js can be provided if you decide to save the file in a different location/folder than your .jsx file.

  3. By including json2.js in your .jsx file you'll now have working JSON methods; JSON.parse() and JSON.stringify().


Example:

The following ExtendScript (.jsx) is a working example that generates JSON to indicate all the links associated with the current InDesign document (.indd).

example.jsx

#include "json2.js";

$.level=0;

var doc = app.activeDocument;

/**
 * Loads the AdobeXMPScript library.
 * @returns {Boolean} True if the library loaded successfully, otherwise false.
 */
function loadXMPLibrary() {
  if (!ExternalObject.AdobeXMPScript) {
    try {
      ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript');
    } catch (e) {
      alert('Failed loading AdobeXMPScript library\n' + e.message, 'Error', true);
      return false;
    }
  }
  return true;
}

/**
 * Obtains the values f XMP properties for `DocumentID` and `instanceID` in
 * each linked file associated with an InDesign document (.indd). A returns the
 * information formatted as JSON,
 * @param {Object} doc - A reference to the .indd to check.
 * @returns {String} - The information formatted as JSON.
 */
function getLinksInfoAsJson(doc) {
  var linksInfObj = {};

  linksInfObj['indd-name'] = doc.name;
  linksInfObj.location = doc.filePath.fsName;
  linksInfObj.links = [];

  for (var i = 0, len = doc.links.length; i < len; i++) {
    var linkFilepath = File(doc.links[i].filePath).fsName;
    var linkFileName = doc.links[i].name;

    var xmpFile = new XMPFile(linkFilepath, XMPConst.FILE_INDESIGN, XMPConst.OPEN_FOR_READ);
    var allXMP = xmpFile.getXMP();

    // Retrieve values from external links XMP.
    var documentID = allXMP.getProperty(XMPConst.NS_XMP_MM, 'DocumentID', XMPConst.STRING);
    var instanceID = allXMP.getProperty(XMPConst.NS_XMP_MM, 'InstanceID', XMPConst.STRING);

    // Ensure we produce valid JSON...
    // - When `instanceID` or `documentID` values equal `undefined` change to `null`.
    // - When `instanceID` or `documentID` exist ensure it's a String.
    instanceID = instanceID ? String(instanceID) : null;
    documentID = documentID ? String(documentID) : null;

    linksInfObj.links.push({
      'name': linkFileName,
      'path': linkFilepath,
      'docId': documentID,
      'insId': instanceID
    });
  }

  return JSON.stringify(linksInfObj, null, 2);
}

if (loadXMPLibrary()) {
 var linksJson = getLinksInfoAsJson(doc);
 $.writeln(linksJson);
}

Output:

Running the script above will log JSON formatted something like the following example to your console:

{
  "indd-name": "foobar.indd",
  "location": "/path/to/the/document",
  "links":[
    {
      "name": "one.psd",
      "path": "/path/to/the/document/links/one.psd",
      "docId": "5E3AE91C0E2AD0A57A0318E078A125D6",
      "insId": "xmp.iid:0480117407206811AFFD9EEDCD311C32"
    },
    {
      "name": "two.jpg",
      "path": "/path/to/the/document/links/two.jpg",
      "docId": "EDC4CCF902ED087F654B6AB54C57A833",
      "insId": "xmp.iid:FE7F117407206811A61394AAF02B0DD6"
    },
    {
      "name": "three.png",
      "path": "/path/to/the/document/links/three.png",
      "docId": null,
      "insId": null
    }
  ]
}

Sidenote: Modelling your JSON:

You'll have noticed that the JSON output (above) is structured differently to how you were attempting to structure it in your given example. The main difference is that you were using link filenames as property/key names, such as the following example:

Example of a problematic JSON structure

{
  "one.psd": {
    "docId": "5E3AE91C0E2AD0A57A0318E078A125D6",
    "insId": "xmp.iid:0480117407206811AFFD9EEDCD311C32"
  },
  "two.jpg": {
    "docId": "EDC4CCF902ED087F654B6AB54C57A833",
    "insId": "xmp.iid:FE7F117407206811A61394AAF02B0DD6"
  }
  ...
}

Producing JSON like this example isn't ideal because if you were to have two links, both with the same name, you would only ever report one of them. You cannot have two properties/keys that have the same name within an Object.


Edit:

As a response to the OP's comment:

Hi RobC, other than using #include 'json2.js', is there any other way to include external js file in the JSX file?

There are a couple of alternative ways as follows:

  1. You could utilize $.evalFile(). For instance replace #include "json2.js"; with the following two lines:

     var json2 = File($.fileName).path + "/" + "json2.js";
     $.evalFile(json2);
    

    Note: This example assumes json2.js resides in the same folder as your .jsx

  2. Alternatively, if you're wanting to avoid the existence of the additional json2.js file completely. You could add a IIFE (Immediately Invoked Function Expression) at the top of your .jsx file. Then copy and paste the content of the json2.js file into it. For instance:

     (function () {
         // <-- Paste the content of `json2.js` here.
     })();
    

    Note: If code size is a concern then consider minifying the content of json2.js before pasting it into the IIFE.

Intuit answered 31/5, 2019 at 8:19 Comment(5)
FWIW, json2.js has some issues regarding to ExtendScript and noticebly Enumerations conversion. One may prefer Marc Autret's own implementation with IDExtenso framework : github.com/indiscripts/IdExtensoAcatalectic
Interesting, but unfortunately IDExtenso only works with ExtendScript ToolKit (ESTK), not Visual Studio Code.Indifferentism
JSON-js works great -- until one tries to use it in an ExtendScript CEP Panel in Visual Studio Code. Testing it in a stand-alone .jsx in VSCode, JSON.stringify() and and JSON.parse() encode and decode objects and arrays of objects. But send the exact same data through evalScript() and all you get is 'EvalScript error', if you're lucky. It really is too bad, but I'm back to join() and split().Indifferentism
Also, the include statement for a .jsx file in VSCode is: //@include "./JSON-js/json2.js"Indifferentism
As you can imagine, EvalScript probably won't work because any type of include depends on an actual file location and a script inside EvalScript has no physical location. I had a similar problem when using evalJSXFile and obviously it didn't work neither as ExtendScript would not fetch that included script. You can work around by embedding the entire json2.js into your script, if you have to use JSON in your script.Rephrase
A
0

I apply JavaScript Minifier to JSON-js

then put the result to my script.

Ance answered 25/9, 2020 at 12:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.