It writes a JSON string
returns a JSON string
JSON as a good medium for data storage
Well, JSON strings are certainly a useful storage medium (a bit more compact and legible than XML, a bit less fragile under manual editing than YAML), but funnily enough JSON is not actually involved at any stage in the two functions above
Perhaps the confusion arises in part from the 'String' in the comments on function type (which just refer to the type of the strPath
argument), but more likely the impression of JSON arises because at first glance JavaScript object literals look quite similar to their JSON serialisations.
In AppleScript, the object code in the example might be written something like:
which still has some visual overlap with a JSON string, though if we were actually to serialize the objects to JSON, we would notice the compulsory double-quoting of keys.
In JS for Automation, you can generate JSON from JavaScript object code with JSON.stringify():
var strClip = (function() {
'use strict';
var lstObjects = [
// dict
{
alpha: 1,
beta: 2,
gamma: 3
},
// array
[{
delta: 4,
epsilon: 5,
zeta: 6
}, {
eta: 'seven',
theta: 'eight',
iota: 'nine'
}]
];
return JSON.stringify(lstObjects, null, 2);
})();
var a = Application.currentApplication(),
sa = (a.includeStandardAdditions = true, a);
sa.setTheClipboardTo(strClip);
strClip
Rather than using JSON, the writePlist() and readPlist() above are taking array and dictionary objects (just like those in the Applescript example above) and writing them straight out to (or reading them straight in from) plist XML like:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>alpha</key>
<real>1</real>
<key>beta</key>
<real>2</real>
<key>gamma</key>
<real>3</real>
</dict>
</plist>
or
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>delta</key>
<real>4</real>
<key>epsilon</key>
<real>5</real>
<key>zeta</key>
<real>6</real>
</dict>
<dict>
<key>eta</key>
<string>seven</string>
<key>iota</key>
<string>nine</string>
<key>theta</key>
<string>eight</string>
</dict>
</array>
</plist>
To answer Dan's question, the breakdown of stages is:
(see the general documentation on .writeToFileAtomically and .stringByStandardizingPath and the specifics of JS and ObjC interactions in the JavaScript for Automation release notes)
// writePlist :: Object -> String -> IO ()
function writePlist(jsObject, strPath) {
$(jsObject) // The $() operator converts the JavaScript array or dictionary
// to an ObjC type, in preparation for using ObjC functions like:
.writeToFileAtomically(
$(strPath) // which requires an ObjC path string
.stringByStandardizingPath, true // which we have normalised from ~/ etc
);
}
and
(see .dictionaryWithContentsOfFile and .arrayWithContentsOfFile)
// readPlist :: String -> Object
function readPlist(strPath) {
// Get the output path as an ObjC datatype, with ~ etc resolved
var strFullPath = $(strPath)
.stringByStandardizingPath;
// ObjC.unwrap and .deepUnwrap convert from ObjC objects back to JS objects
// Here we first try to read the .plist as a dictionary, and if that returns
// an undefined, the boolean expression instead returns the result of trying to
// read the .plist as an array
return ObjC.deepUnwrap(
$.NSDictionary.dictionaryWithContentsOfFile(
strFullPath
)
) || ObjC.deepUnwrap(
$.NSArray.arrayWithContentsOfFile(strFullPath)
);
}
In the example run, we map over the two objects, applying to each a function which uses the second argument of the map callback function (an index into the array) to pull in a different filePath for each object.
- First we write the javascript dictionary or array straight out as a .plist file, and then
- we read it back in from the .plist to a js dictionary or array
(no JSON used, just plist XML)