PMJSON
PMJSON provides a pure-Swift strongly-typed JSON encoder/decoder as well as a set of convenience methods for converting to/from Foundation objects and for decoding JSON structures.
The entire JSON encoder/decoder can be used without Foundation, by removing the files ObjectiveC.swift
and DecimalNumber.swift
from the project. The only dependency the rest of the project has is on Darwin
, for strtod()
and strtoll()
. The file ObjectiveC.swift
adds convenience methods for translating between JSON
values and Foundation objects as well as decoding from an NSData
, and DecimalNumber.swift
adds convenience accessors for converting values into NSDecimalNumber
.
Usage
Parsing
The JSON decoder is split into separate parser and decoder stages. The parser consums any sequence of unicode scalars, and produces a sequence of JSON "events" (similar to a SAX XML parser). The decoder accepts a sequence of JSON events and produces a JSON
value. This architecture is designed such that you can use just the parser alone in order to decode directly to your own data structures and bypass the JSON
representation entirely if desired. However, most clients are expected to use both components, and this is exposed via a simple method JSON.decode(_:strict:)
.
Parsing a JSON string into a JSON
value is as simple as:
let json = try JSON.decode(jsonString)
Any errors in the JSON parser are represented as JSONParserError
values and are thrown from the decode()
method. The error contains the precise line and column of the error, and a code that describes the problem.
A convenience method is also provided for decoding from an NSData
containing UTF8-encoded data:
let json = try JSON.decode(data)
Encoding a JSON
value is also simple:
let jsonString = JSON.encodeAsString(json)
You can also encode directly to any OutputStreamType
:
JSON.encode(json, toStream: &output)
And, again, a convenience method is provided for working with NSData
:
let data = JSON.encodeAsData(json)
Accessors
Besides encoding/decoding, this library also provides a comprehensive suite of accessors for getting data out of JSON
values. There are 4 types of basic accessors provided:
- Basic property accessors named after types such as
.string
. These accessors return the underlying value if it matches the type, ornil
if the value is not the right type. For example,.string
returnsString?
. These accessors do not convert between types, e.g.JSON.Int64(42).string
returnsnil
. - Property accessors beginning with the word
as
, such as.asString
. These accessors also return an optional value, but they convert between types if it makes sense to do so. For example,JSON.Int64(42).asString
returns"42"
. - Methods beginnning with
get
, such asgetString()
. These methods return non-optional values, and throwJSONError
s if the value's type does not match. These methods do not convert between types, e.g.try JSON.Int64(42).getString()
throws an error. For every method of this type, there's also a variant ending inOrNil
, such asgetStringOrNil()
, which does return an optional. These methods only returnnil
if the value isnull
, otherwise they throw an error. - Methods beginning with
to
, such astoString()
. These are just like theget
methods except they convert between types when appropriate, using the same rules that theas
methods do, e.g.try JSON.Int64(42).toString()
returns"42"
. Like theget
methods, there are also variants ending inOrNil
.
JSON
also provides both keyed and indexed subscript operators that return a JSON?
, and are always safe to call (even with out-of-bounds indexes). And it provides 2 kinds of subscripting accessors:
- For every basic
get
accessor, there's a variant that takes a key or an index. These are equivalent to subscripting the receiver and invoking theget
accessor on the result, except they produce better errors (and they handle missing keys/out-of-bounds indexes properly). For example,getString("key")
orgetString(index)
. TheOrNil
variants also returnnil
if the key doesn't exist or the index is out-of-bounds. - Similarly, there are subscripting equivalents for the
to
accessors as well.
And finally, the getObject()
and getArray()
accessors provide variants that take a closure. These variants are recommended over the basic accessors as they produce better errors. For example, given the following JSON:
{
"object": {
"elements": [
{
"name": null
}
]
}
}
And the following code:
try json.getObject("object").getArray("elements").getObject(0).getString("name")
The error thrown by this code will have the description "name: expected string, found null"
.
But given the following equivalent code:
try json.getObject("object", { try $0.getArray("elements", { try $0.getObject(0, { try $0.getString("name") }) }) })
The error thrown by this code will have the description "object.elements[0].name: expected string, found null"
.
All of these accessors are also available on the JSONObject
type (which is the type that represents an object).
Helpers
The JSON
type has static methods map()
and flatMap()
for working with arrays (since PMJSON does not define its own array type). The benefit of using these methods over using the equivalent SequenceType
methods is the PMJSON static methods produce better errors.
There are also helpers for converting to/from Foundation objects. JSON
offers an initializer init(ns: AnyObject) throws
that converts from any JSON-compatible object to a JSON
. JSON
and JSONObject
both offer the property .ns
, which returns a Foundation object equivalent to the JSON
, and .nsNoNull
which does the same but omits any null
values instead of using NSNull
.
Performance
The test suite includes some basic performance tests. Decoding ~70KiB of JSON using PMJSON takes about 2.5-3x the time that NSJSONSerialization
does, though I haven't tested this with different distributions of inputs and it's possible this performance is specific to the characteristics of the test input. However, encoding the same JSON back to an NSData
is actually faster with PMJSON, taking around 75% of the time that NSJSONSerialization
does.
Requirements
Installing as a framework requires a minimum of iOS 8, OS X 10.9, watchOS 2.0, or tvOS 9.0.
Installation
Carthage
To install using Carthage, add the following to your Cartfile:
github "postmates/PMJSON" ~> 0.9
To install using [CocoaPods][], add the following to your Podfile:
pod 'PMJSON', '~> 0.9'
Once installed, you can use this by adding import PMJSON
to your code.
Swift Package Manager
When using a recent Swift development snapshot, the Swift Package Manager may be used to install PMJSON by adding it to your dependencies
list:
let package = Package(
name: "YourPackage",
dependencies: [
.Package(url: "https://github.com/postmates/PMJSON.git",
versions: Version(major: 0, minor: 9, patch: 3)..<Version(major: 0, minor: 10, patch: 0))
]
)
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) at your option.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual licensed as above, without any additional terms or conditions.
Version History
Development
- Add setters for basic accessors so you can write code like
json["foo"].object?["key"] = "bar"
. - Add optional
NSError
userInfo provider forJSONError
. Setting this up requires callingJSONError.registerNSErrorUserInfoProvider()
.
v0.9.3 (2016-05-23)
- Add a set of convenience methods on
JSON
andJSONObject
for mapping arrays returned by subscripting with a key or index:mapArray(_:_:)
,mapArrayOrNil(_:_:)
,flatMapArray(_:_:)
, andflatMapArrayOrNil(_:_:)
. - Add new set of convenience
JSON
initializers. - Change
description
anddebugDescription
forJSON
andJSONObject
to be more useful.description
is now the JSON-encoded string. - Implement
CustomReflectable
forJSON
andJSONObject
.
v0.9.2 (2016-03-04)
- CocoaPods support.
v0.9.1 (2016-02-19)
- Linux support.
- Swift Package Manager support.
- Rename instances of
plist
in the API tons
. The old names are still available but marked as deprecated. - Support the latest Swift snapshot (2012-02-08).
v0.9 (2016-02-12)
Initial release.