Property lists (plist) are ubiquitous in macOS. Apple’s Foundation
framework contains classes and methods dealing with them. Though they’re not exactly fun to work with, it’s a lot easier than using System Events methods.
There is also plutil
: a command line utility delivered with macOS which permits to convert property lists between different formats as well as modifying them. However, plutil
fails miserably when converting a property list to JSON if the list contains data that can not readily be converted into a string representation. So you might be better off to craft your own plist to JSON
function.
The first step is to convert an external property list (coming from a file or a program) into a JavaScript object. The function propertyListToDictionary
expects a property list in clear text format as a string (plist
) and returns a NSDictionary
object containing the list’s data. You can convert it to a JavaScript object by appending .js
to the result.
function propertyListToDictionary(plist) {
ObjC.import("Foundation");
const data = $(plist).dataUsingEncoding($.NSUTF8StringEncoding);
const err = Ref();
const format = Ref();
return $.NSPropertyListSerialization.propertyListWithDataOptionsFormatError(
data,
{},
format,
err
);
}
However, this JavaScript consists of property names that you can treat as JavaScript strings and property values belonging to NS…
classes. To use them in JavaScript, you’ll have to convert the complete object like so:
function pListToJS(objcObj) {
const obj = objcObj.js;
const objcClass = $.NSStringFromClass($.object_getClass(objcObj)).js;
if (obj instanceof Array) {
return obj.map(o => {
return ObjCToJS(o);
})
} else if (objcClass.includes('Dictionary')) {
const newObj = {};
for (const [key,value] of Object.entries(obj)) {
newObj[key] = ObjCToJS(value);
}
return newObj;
}
} /* pListToJS */
function ObjCToJS(o) {
const JSObj = o.js;
const objcClass = $.NSStringFromClass($.object_getClass(o)).js;
if (objcClass.includes('Data')) {
o.base64EncodedStringWithOptions($.NSDataBase64Encoding64CharacterLineLength).js
} else if (objcClass.includes('Date')) {
return JSObj.toISODateString();
} else if (objcClass.includes('Dictionary') || JSObj instanceof Array) {
return pListToJS(o);
} else {
return JSObj;
}
}
Call pListToJS
with the object returned by propertyListToDictionary
and use its return value as you would a JavaScript object. The property values are of scalar types, i.e. String or Number, or they are arrays or objects again. Property list values of type NSData
will be converted to Base64-encoded strings. Values of type NSDate
will be converted to a date string in ISO format. These conversations are handled by ObjCToJS
, where you can modify them to suit your purposes.
The code shown here handles NSData
and NSDate
properties, while both types seem to throw plutil
off its tracks.