Mastodon

JSON and Property Lists: Alternate Universe Siblings

Update: This post is old but still accurate as far as comparing JSON with property lists, with examples for Objective-C code. If you’re using Swift, see my newer post that updates this one for Codable and Swift examples.

Mac OS X and iOS include support for JSON and property lists, two generic structured data formats, both in common use in different places. If Stack Overflow is any indication, there’s a fair amount of confusion regarding the two. It’s true, they’re very similar, so much that it’s tempting to think the differences are merely syntax choices. In some cases it’s possible to read data from one format and write it to the other as-is, with no conversion. But not always. JSON isn’t a property list and a property list isn’t JSON, but they do have a lot in common.

Both of these are generic data formats, in that they’re designed to be flexible enough to store structured data for a wide variety of uses. For any given use, neither is likely to be the optimum format by any metric– except flexibility and familiarity. Each handles so many different uses that they’ve become the go-to format in their respective domains, regardless of the nature of the data.

What’s a Property List?

Property lists have been used with Objective-C and Cocoa since before either were part of Apple. The origins are lost in the mists of antiquity. In general, a property list is any collection of property list objects, which are NSDictionary, NSArray, NSString, NSNumber, NSData, and NSDate. There’s also a rule that for an NSDictionary to be considered a property list, the keys must be NSStrings (this isn’t true for non-property list dictionaries, where any object that conforms to NSCopying can be a key). Any of these objects can be a property list all by itself– there’s no requirement for a specific root object.

This format is ubiquitous in Mac and iOS development, not least because there’s been direct API support pretty much forever. You can easily read a property list into an NSDictionary via the +dictionaryWithContentsOfFile: method, or write one back out to a property list -writeToFile:atomically:. Either direction is just one method call. These methods only work if the data is a valid property list. No custom objects, no other Cocoa objects, and making your class comply with NSCoding doesn’t make any difference. (If you want to read and write objects with NSCoding, use NSKeyedArchiver and NSKeyedUnarchiver).

NSUserDefaults uses property lists, so only property list types can go into user defaults. So does pretty much every app you’ve used on Mac OS X and iOS, via the ubiquitous Info.plist file.

Property lists have used different file formats over the years, starting with an ASCII format that was remarkably similar to JSON. These days either XML or an undocumented binary format are preferred, and the older format can only be read, not written.

What’s JSON?

JSON serves more or less the same purpose in web apps and web-based APIs. It wasn’t widely used on Mac OS X, but with the debut of iOS apps, interest took off. Initially there was no direct JSON support from iOS frameworks, so Jon Wight’s TouchJSON became the de facto standard. Beginning with iOS 5 and Mac OS X 10.7, Apple provides NSJSONSerialization to convert between JSON and Cocoa objects.

In Cocoa terms, JSON can include NSDictionary, NSArray, NSString, NSNumber, and NSNull (more on that later). As with property lists, dictionary keys must be strings. There are a couple of other rules that don’t apply to property lists:

  • The root object must be an NSDictionary or NSArray.
  • Numbers must not be infinite or NaN.

Why can’t I convert my JSON to a property list?

The differences described above mean that while it’s often possible to take some valid JSON and write it to a property list, this is not always the case. The most common error seems to relate to JSON’s use of null values.

With JSON, the usual approach seems to be that all keys that are part of an object must be represented. If there’s no value for some key, the JSON way is to use a value of null. What’s null in Cocoa? It’s the singleton instance of NSNull. If you’ve been paying attention, you’ll notice that NSNull is not a property list type. As a result, if your JSON might contain a null value, there’s a decent chance that you can’t save it as a property list.

Not without some work, anyway. If you really want to use property list formatting, you’ll need to recursively run through your JSON, find any null values, and replace or remove them. With Cocoa, if there’s no value for a key, the usual approach is to just not put that key in the dictionary. That makes its value implicitly nil (which is not null, and definitely not NSNull). It would be reasonable, then, to just drop any nulls.

Before doing that though, ask yourself, is this conversion necessary? NSJSONSerialization can read and write files. If you have JSON structured data, you may be better off just leaving it as JSON and not bothering with conversions.

Why can’t I convert my property list to JSON?

Going this direction, the potential conflicts are numerous. In approximate order of how likely you are to run into them:

  • Property lists can contain NSDates. JSON doesn’t have a native date format. ISO8601 formatting is common but not actually standard. It would be possible to use a default date format when writing JSON, but there’s no guarantee it would be compatible with any particular API. Worse, when reading JSON, it would be necessary to match every string value against date format regular expressions to see if anything matched.
  • With NSData, a property list can contain an arbitrary binary blob as an array element or a dictionary value. This could probably be base64 encoded when saving as JSON, but it isn’t. As with dates, it would be possible to make default conversions, but writing wouldn’t guarantee compatibility and reading would be error-prone and slow.
  • Numeric values in property lists can be infinite, or can indicate an invalid value by simply reading and writing NaN. JSON doesn’t like this.

As a simple example, this array is a valid property list:

NSArray *myArray = @[@123, @456.789, @YES, [NSDecimalNumber notANumber],
    [NSNumber numberWithFloat:1.0/0.0], [NSDate date] ];

Write this to a property list file via the built-in writeToFile:atomically: and you get:

<?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>
    <integer>123</integer>
    <real>456.78899999999999</real>
    <true/>
    <real>nan</real>
    <real>+infinity</real>
    <date>2013-04-03T17:00:12Z</date>
</array>
</plist>

If you tried to write this as JSON, well, you wouldn’t, because it’s invalid in multiple ways. It’s interesting to see that the XML tags reflect on the type of numeric values– rather than number or something like that, we get integer, real, and the Boolean shows up as simply true. They all convert to/from NSNumber transparently, since it can handle all three.

As an aside, holy crap, “456.78899999999999”? Wow.

How can I find out if I can’t convert?

If you must convert your data format, the best approach is to just not make any assumptions. JSON is allowed to contain stuff that’s illegal in property lists and vice versa. When converting, scan your data and make whatever changes are needed. Don’t assume that because it’s working today that there won’t be unexpected changes tomorrow. The web service you’re using might change their API tomorrow, and you don’t want that to crash your app.

There are a couple of useful methods you can use to find out if any given object is valid for either format. If you want JSON and aren’t sure if you have it, ask NSJSONSerialization:

BOOL validJSON = [NSJSONSerialization isValidJSONObject:myArray];

When writing as a property list, check out the extremely useful NSPropertyListSerialization class:

BOOL validPlist = [NSPropertyListSerialization propertyList:myArray isValidForFormat:NSPropertyListXMLFormat_v1_0];