Read property lists

Read property lists

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.