I'm working with Adobe CEP (it lets developers create windowed extensions for Adobe CC products). The bulk of my code is modern JavaScript (the platform uses Chromium 57, Node.js 7.7.4). However, in order to access the DOM, I need to write some functions in Adobe ExtendScript and execute them from normal JS. The only way is to execute a script using their provided csInterface.evalScript(script, callback)
. script
has to be a string, which in my case is a function call converted to a string. I want to be able to pass an object to and from ExtendScript via evalScript
, but evalScript
only takes and returns a string.
Currently, I am passing each object property as its own argument. This is unwieldy, but it works.
My first though was JSON.stringify()
, but unfortunately ExtendScript is a dialect of ECMAScript 3, which means no JSON.parse()
support.
I can't just concat the object argument into the script function call, because then the string evaluates to foo([object Object])
.
I've seen there are functions like eval()
/uneval()
or Object.toSource()
, but those are not supported by Chromium.
Here's an example, similar to my current method:
functions.js (ES3/ExtendScript)
function drawCircle(x, y, name) {
// pick a layer
var layer = app.activeDocument.layers[0];
var diameter = 10;
var top = y + diameter / 2;
var left = x - diameter / 2;
// draw ellipse in layer
var circle = layer.pathItems.ellipse(top, left, diameter, diameter);
circle.name = name;
circle.filled = true;
return true;
}
app.js (ES6)
const csInterface = new CSInterface(); // provided by Adobe
async function circle() {
const dataObject = {x: 10, y: 10, name: 'Hello world!'};
// the script to call
// evaluates to drawCircle(10,10,'Hello world!');
const script = "drawCircle(" + dataObject.x + "," + dataObject.y + ",'" + dataObject.name + "');";
return new Promise((resolve, reject) => {
csInterface.evalScript(script, (result) => {
resolve(result);
});
});
}
As expected, circle()
calls drawCircle()
just fine, and an ellipse appears in the document I'm working on. However, executing a script/calling a function by concatenation feels very wrong. So in summary,
dataObject
into a string and passing it to drawCircle()
via evalScript()
,dataObject
from drawCircle()
and receive it back as an object. Currently, returning an object only results in "[object Object]"
as a return value.The only way to pass objects from Javascript to ExtendScript is to send it as a JSON string with JSON.stringify()
.
Yes, you are right about no JSON.parse()
support, however, you don't need to.
You can still send the stringified object and will arrive to ExtendScript as an object.
const dataObject = {x: 10, y: 10, name: 'Hello world!'};
const script = "drawCircle(" + JSON.stringify(dataObject) + ")";
And then in ExtendScript, you can get way with doing something like this:
function drawCircle(obj) {
var layer = app.activeDocument.layers[0];
var radius = 10;
var top = obj.y + 5;
var left = obj.x - 5;
var circle = layer.pathItems.ellipse(top, left, radius, radius);
circle.name = obj.name;
circle.filled = true;
return true;
}
You will need this ExtendScript module, copied in the same folder as your jsx
Link to Indiscripts ExtendScript JSON module
Then include it with #include 'json.jsx';
(or //@include 'json.jsx'
to avoid linter errors) in the top of your jsx. This adds a JSON global function that provides two methods: JSON.eval()
and JSON.lave()
.
The method we need is lave()
which allows you to stringify the object back to Javascript. Consider it a friendlier version of JSON.stringify()
.
function drawCircle(obj) {
var layer = app.activeDocument.layers[0];
var radius = 10;
var top = obj.y + 5;
var left = obj.x - 5;
// draw ellipse in layer
var circle = layer.pathItems.ellipse(top, left, radius, radius);
circle.name = obj.name;
circle.filled = true;
return JSON.lave(circle);
}
Then in javascript you can parse to an object yet again:
const dataObject = {x: 10, y: 10, name: 'Hello world!'};
const script = "drawCircle(" + JSON.stringify(dataObject) + ")";
csInterface.evalScript(script, (result) => {
console.log(JSON.parse(result));
});
I tested this in the latest CEP Runtime version (v9).