PMJSON will not be maintained in this repository going forward. Please use, create issues on, and make PRs to the fork of PMJSON located here.
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 a Data
, and DecimalNumber.swift
adds convenience accessors for converting values into NSDecimalNumber
.
Before diving into the details, here's a simple example of writing a decoder for a struct. There are a few different options for how to deal with malformed data (e.g. whether to ignore values of wrong types, and whether to try and coerce non-string values to strings or vice versa), but the following example will be fairly strict and throw an error for incorrectly-typed values:
struct Address {
var streetLine1: String
var streetLine2: String?
var city: String
var state: String?
var postalCode: String
var country: String?
init(json: JSON) throws {
streetLine1 = try json.getString("street_line1")
streetLine2 = try json.getStringOrNil("street_line2")
city = try json.getString("city")
state = try json.getStringOrNil("state")
postalCode = try json.toString("postal_code") // coerce numbers to strings
country = try json.getStringOrNil("country")
}
}
And here's an example of decoding a nested array of values:
struct Person {
var firstName: String
var lastName: String? // some people don't have last names
var age: Int
var addresses: [Address]
init(json: JSON) throws {
firstName = try json.getString("firstName")
lastName = try json.getStringOrNil("lastName")
age = try json.getInt("age")
addresses = try json.mapArray("addresses", Address.init(json:))
}
}
If you don't want to deal with errors and just want to handle optionals, you can do that too:
struct Config {
var name: String?
var doThatThing: Bool
var maxRetries: Int
init(json: JSON) {
name = json["name"]?.string
doThatThing = json["doThatThing"]?.bool ?? false
maxRetries = json["maxRetries"]?.int ?? 10
}
}
This library also provides support for Swift.Encoder
and Swift.Decoder
. See this section for details.
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(_:options:)
.
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 a Data
containing data encoded as UTF-8, UTF-16, or UTF-32:
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 TextOutputStream
:
JSON.encode(json, toStream: &output)
And, again, a convenience method is provided for working with Data
:
let data = JSON.encodeAsData(json)
PMJSON supports parsing JSON streams, which are multiple top-level JSON values with optional whitespace delimiters (such as {"a": 1}{"a": 2}
). The easiest way to use this is with JSON.decodeStream(_:)
which returns a lazy sequence of JSONStreamValue
s, which contain either a JSON
value or a JSONParserError
error. You can also use JSONParser
s and JSONDecoder
s directly for more fine-grained control over streaming.
JSONParser
and JSONDecoder
As mentioned above, the JSON decoder is split into separate parser and decoder stages. JSONParser
is the parser stage, and it wraps any sequence of UnicodeScalar
s, and itself is a sequence of JSONEvent
s. A JSONEvent
is a single step of JSON parsing, such as .objectStart
when a {
is encountered, or .stringValue(_)
when a "string"
is encountered. You can use JSONParser
directly to emit a stream of events if you want to do any kind of lazy processing of JSON (such as if you're dealing with a single massive JSON blob and don't want to decode the whole thing into memory at once).
Similarly, JSONDecoder
is the decoder stage. It wraps a sequence of JSONEvent
s, and decodes that sequence into a proper JSON
value. The wrapped sequence must also conform to a separate protocol JSONEventIterator
that provides line/column information, which are used when emitting errors. You can use JSONDecoder
directly if you want to wrap a sequence of events other than JSONParser
, or if you want a different interface to JSON stream decoding than JSONStreamDecoder
provides.
Because of this split nature, you can easily provide your own event stream, or your own decoding stage. Or you can do things like wrap JSONParser
in an adaptor that modfiies the events before passing them to the decoder (which may be more efficient than converting the resulting JSON
value).
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:
.string
. These accessors return the underlying value if it matches the type, or nil
if the value is not the right type. For example, .string
returns String?
. These accessors do not convert between types, e.g. JSON.Int64(42).string
returns nil
.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"
.get
, such as getString()
. These methods return non-optional values, and throw JSONError
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 in OrNil
, such as getStringOrNil()
, which does return an optional. These methods only return nil
if the value is null
, otherwise they throw an error.to
, such as toString()
. These are just like the get
methods except they convert between types when appropriate, using the same rules that the as
methods do, e.g. try JSON.Int64(42).toString()
returns "42"
. Like the get
methods, there are also variants ending in OrNil
.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:
get
accessor, there's a variant that takes a key or an index. These are equivalent to subscripting the receiver and invoking the get
accessor on the result, except they produce better errors (and they handle missing keys/out-of-bounds indexes properly). For example, getString("key")
or getString(index)
. The OrNil
variants also return nil
if the key doesn't exist or the index is out-of-bounds.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).
The last code snippet above looks very verbose, but in practice you don't end up writing code like that. Instead you'll often end up just writing things like
try json.mapArray("elements", Element.init(json:))
The JSON
type has static methods map()
, flatMap()
, and compactMap()
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: Any) 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
.
The JSON
type conforms to Codable
, so it can be encoded to a Swift.Encoder
and decoded from a Swift.Decoder
. This has been tested against the standard library-provided JSONEncoder
and JSONDecoder
. Due to limitations in the decoding protocol, decoding a JSON
must attempt to decode multiple different types of values, so it's possible that a poorly-written Swift.Decoder
may produce surprising results when decoding a JSON
.
Encoding to a JSON.Encoder
and decoding from a JSON.Decoder
is optimized to avoid unnecessary work.
Swift.Encoder
and Swift.Decoder
This library provides an implementation of Swift.Encoder
called JSON.Encoder
. This can encode any Encodable
to a JSON
, a String
, or a Data
. It's used similarly to Swift.JSONEncoder
(except at this time it doesn't have options to control encoding of specific types).
This library provides an implementation of Swift.Decoder
called JSON.Decoder
. This can decode any Decodable
from a JSON
, a String
, or a Data
. It's used similar to Swift.JSONDecoder
(except at this time it doesn't have options to control decoding of specific types).
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 a Data
is actually faster with PMJSON, taking around 75% of the time that NSJSONSerialization
does. These benchmarks were performed with Swift 2.x and it's possible the numbers have changed since then.
Installing as a framework requires a minimum of iOS 8, OS X 10.9, watchOS 2.0, or tvOS 9.0.
After installing with any mechanism, you can use this by adding import PMJSON
to your code.
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", from: "3.0.1")
]
)
To install using Carthage, add the following to your Cartfile:
github "postmates/PMJSON" ~> 3.0
This release supports Swift 4. If you want Swift 3.x support, you can use
github "postmates/PMJSON" ~> 2.0
To install using CocoaPods, add the following to your Podfile:
pod 'PMJSON', '~> 3.0'
This release supports Swift 4. If you want Swift 3.x support, you can use
pod 'PMJSON', '~> 2.0'
Licensed under either of
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.
URL
s with JSON.Encoder
and JSON.Decoder
, encode and decode their absolute string instead of relying on the native implementation which encodes them as an object. This matches the behavior of JSONEncoder
and JSONDecoder
.JSON.Encoder.DateEncodingStrategy.iso8601WithFractionalSeconds
.JSON.Encoder.DateEncodingStrategy.iso8601WithFractionalSeconds
and JSON.Encoder.DateEncodingStrategy.iso8601WithFractionalSeconds
to iOS 11.2+ and tvOS 11.2+ as, despite the constant being marked as available earlier, it's not supported at runtime. (#33)JSONObject.ns
and JSONObject.nsNoNull
to return a [String: Any]
instead of an [AnyHashable: Any]
. (#25)JSON.Encoder.encodeAs*
and JSON.Decoder.decode
methods into overload pairs where one takes options:
and the other doesn't. This makes it easier to replace function references to JSONEncoder
/JSONDecoder
methods with the equivalents from PMJSON.TopLevelEncoder
and TopLevelDecoder
, using Data
as the input/output type. This means that JSON.Encoder.encode(_:)
is now marked as deprecated instead of unavailable.JSON.flatMap*
and JSONObject.flatMap*
methods to .compactMap*
instead when the transformation returns an optional. (#28)@inlinable
.JSONError.withPrefix(_:)
that returns a new error by prepending a prefix onto the path. This can be used in custom parsing code to produce good errors if the existing convenience functions don't do what you want. (#26).pretty
output for empty arrays/dictionaries.JSON.encodeAsData()
pretty significantly. It's now very nearly as fast as JSON.encodeAsString()
.JSON.Encoder.encodeAsString()
and JSON.Encoder.encodeAsData()
.JSON
that mimic the enum cases: JSON.int(_:)
and JSON.cgFloat(_:)
. These can be used when JSON(_:)
triggers too much type complexity. Also add a JSON(_:)
override for CGFloat
.JSON.Encoder.keyEncodingStrategy
. This is very similar to Swift 4.1's JSONEncoder.keyEncodingStrategy
, although by default it won't apply to any nested values of type JSON
or JSONObject
(there's another option applyKeyEncodingStrategyToJSONObject
that controls this).JSON.Decoder.keyDecodingStrategy
. This is very similar to Swift 4.1's JSONDecoder.keyDecodingStrategy
, although by default it won't apply to decoding any values of type JSON
or JSONObject
(there's another option applyKeyDecodingStrategyToJSONObject
that controls this).JSON.Encoder.dateEncodingStrategy
. This is very similar to JSONEncoder.dateEncodingStrategy
except it includes another case for encoding ISO8601-formatted dates with fractional seconds (on Apple platforms).JSON.Decoder.dateDecodingStrategy
. This is very similar to JSONDecoder.dateDecodingStrategy
except it includes another case for decoding ISO8601-formatted dates with fractional seconds (on Apple platforms).JSON.Encoder.dataEncodingStrategy
. This is identical to JSONEncoder.dataEncodingStrategy
.JSON.Decoder.dataDecodingStrategy
. This is identical to JSONDecoder.dataDecodingStrategy
.JSONError.path
.JSONError.withPrefixedCodingPath(_:)
to make it easier to use JSONError
-throwing methods in a Decodable
implementation.Codable
on JSON
.Swift.Decoder
implementation called JSON.Decoder
.Swift.Encoder
implementation called JSON.Encoder
.Decimal
(on Swift 3.1 and later). NOTE: Decimal support is still buggy in Swift 3.1, and the workarounds we employ to get the correct values on Apple platforms don't work on Linux. You probably shouldn't rely on this working correctly on Linux until Swift fixes its Decimal implementation.Data
.LocalizedError
on the Error types (only really applies to Swift 3.1 and later).swift test
.JSON.parser(for:options:)
that returns a JSONParser<AnySequence<UnicodeScalar>>
from a Data
. Like JSON.decode(_:options:)
, this method automatically detects UTF-8, UTF-16, or UTF-32 input.JSON
variant .decimal
, any relevant accessors, and full parsing/decoding support with the new option .useDecimals
. With this option, any number that would have been decoded as a Double
will be decoded as a Decimal
instead.forEach
accessors for working with arrays, similar to the existing map
and flatMap
accessors.OptionSet
s). The old methods that take strict/pretty flags are now marked as deprecated.1e-1
or 1e+1
.strict
option is specified, stop accepting numbers of the form 01
or -01
.Data
that has a UTF-16 BOM.Data
.Hashable
to JSONEvent
and JSONParserError
.JSONParserError
conform to CustomNSError
for better Obj-C errors.JSONParser
and JSONDecoder
can now both operate in streaming mode, a new type JSONStreamDecoder
was added as a lazy sequence of JSON values, and a convenience method JSON.decodeStream(_:)
was added.JSONEventGenerator
to JSONEventIterator
and JSONParserGenerator
to JSONParserIterator
. The old names are available (but deprecated) for backwards compatibility.JSONParserError
. It should now work just like any other error, allowing you to say e.g. if case JSONParserError.invalidSyntax = error { … }
.json["foo"].object?["key"] = "bar"
.NSError
.JSONParser
for streams of JSON values (e.g. "[1][2]"
).JSON
and JSONObject
for mapping arrays returned by subscripting with a key or index: mapArray(_:_:)
, mapArrayOrNil(_:_:)
, flatMapArray(_:_:)
, and flatMapArrayOrNil(_:_:)
.JSON
initializers.description
and debugDescription
for JSON
and JSONObject
to be more useful.
description
is now the JSON-encoded string.CustomReflectable
for JSON
and JSONObject
.plist
in the API to ns
. The old names are still available but marked as deprecated.Initial release.
link |
Stars: 360 |
Last commit: 2 years ago |
URL
s with JSON.Encoder
and JSON.Decoder
, encode and decode their absolute string instead of relying on the native implementation which encodes them as an object. This matches the behavior of JSONEncoder
and JSONDecoder
.JSON.Encoder.DateEncodingStrategy.iso8601WithFractionalSeconds
.JSON.Encoder.DateEncodingStrategy.iso8601WithFractionalSeconds
and JSON.Encoder.DateEncodingStrategy.iso8601WithFractionalSeconds
to iOS 11.2+ and tvOS 11.2+ as, despite the constant being marked as available earlier, it's not supported at runtime. (#33)JSONObject.ns
and JSONObject.nsNoNull
to return a [String: Any]
instead of an [AnyHashable: Any]
. (#25)JSON.Encoder.encodeAs*
and JSON.Decoder.decode
methods into overload pairs where one takes options:
and the other doesn't. This makes it easier to replace function references to JSONEncoder
/JSONDecoder
methods with the equivalents from PMJSON.TopLevelEncoder
and TopLevelDecoder
, using Data
as the input/output type. This means that JSON.Encoder.encode(_:)
is now marked as deprecated instead of unavailable.JSON.flatMap*
and JSONObject.flatMap*
methods to .compactMap*
instead when the transformation returns an optional. (#28)@inlinable
.Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics