#JSONCodable Hassle-free JSON encoding and decoding in Swift
Installation
- Simply add the following to your
Cartfile
and runcarthage update
:
github "matthewcheok/JSONCodable" ~> 2.1
- or add the following to your
Podfile
and runpod install
:
pod 'JSONCodable', '~> 2.1'
-
or clone as a git submodule,
-
or just copy files in the
JSONCodable
folder into your project.
TLDR
- Uses Protocol Extensions
- Error Handling
- Supports
let
properties - Supports
enum
properties backed by compatible values
Change Log
- Moved encoding and decoding methods to a helper class
JSONCodable
is made of two separate protocols JSONEncodable
and JSONDecodable
.
JSONEncodable
allows your structs and classes to generate NSDictionary
or [String: AnyObject]
equivalents for use with NSJSONSerialization
.
JSONDecodable
allows you to generate structs from NSDictionary
coming in from a network request for example.
We'll use the following models in this example:
struct User {
let id: Int
let name: String
var email: String?
var company: Company?
var friends: [User] = []
}
struct Company {
let name: String
var address: String?
}
Using JSONEncodable
Simply add conformance to JSONEncodable
(or to JSONCodable
):
extension User: JSONEncodable {
func toJSON() throws -> AnyObject {
return try JSONEncoder.create({ (encoder) -> Void in
try encoder.encode(id, key: "id")
try encoder.encode(name, key: "full_name")
try encoder.encode(email, key: "email")
try encoder.encode(company, key: "company")
try encoder.encode(friends, key: "friends")
})
}
}
extension Company: JSONEncodable {}
The default implementation of func toJSON()
inspects the properties of your type using reflection (see Company
.) If you need a different mapping, you can provide your own implementation (see User
.)
Instantiate your struct, then use the func toJSON()
method to obtain a equivalent form suitable for use with NSJSONSerialization
:
let dict = try user.toJSON()
print("dict: \(dict)")
Result:
[full_name: John Appleseed, id: 24, email: john@appleseed.com, company: {
address = "1 Infinite Loop, Cupertino, CA";
name = Apple;
}, friends: (
{
friends = (
);
"full_name" = "Bob Jefferson";
id = 27;
},
{
friends = (
);
"full_name" = "Jen Jackson";
id = 29;
}
)]
##Using JSONDecodable
Simply add conformance to JSONDecodable
(or to JSONCodable
):
extension User: JSONDecodable {
init(object: JSONObject) throws {
let decoder = JSONDecoder(object: object)
id = try decoder.decode("id")
name = try decoder.decode("full_name")
email = try decoder.decode("email")
company = try decoder.decode("company")
friends = try decoder.decode("friends")
}
}
extension Company: JSONDecodable {
init(object: JSONObject) throws {
let decoder = JSONDecoder(object: object)
name = try decoder.decode("name")
address = try decoder.decode("address")
}
}
Simply provide the implementations for init(object: JSONObject) throws
where JSONObject
is a typealias for [String:AnyObject]
.
As before, you can use this to configure the mapping between keys in the Dictionary to properties in your structs and classes.
let user = try! User(object: JSON)
print("\(user)")
Result:
User(
id: 24,
name: "John Appleseed",
email: Optional("john@appleseed.com"),
company: Optional(Company(
name: "Apple",
address: Optional("1 Infinite Loop, Cupertino, CA")
)),
friends: [
User(
id: 27,
name: "Bob Jefferson",
email: nil,
company: nil,
friends: []
),
User(
id: 29,
name: "Jen Jackson",
email: nil,
company: nil,
friends: []
)
]
)
Working with JSON Strings
The convenience initializer init?(JSONString: String)
is provided on JSONDecodable
. You may also use func toJSONString() throws -> String
to obtain a string equivalent of your types.
Transforming values
To transform values, create an instance of JSONTransformer
:
let JSONTransformerStringToNSURL = JSONTransformer<String, NSURL>(
decoding: {NSURL(string: $0)},
encoding: {$0.absoluteString})
A JSONTransformer
converts between 2 types, in this case, String
and NSURL
. It takes a closure for decoding and another for encoding, and in each case, you return an optional value of the corresponding type given an input type (you can return nil
if a transformation is not possible).
Next, use the overloaded versions of func encode()
and func decode()
to supply the transformer:
struct User {
...
var website: NSURL?
}
init(object: JSONObject) throws {
...
website = try JSONDictionary.decode("website", transformer: JSONTransformerStringToNSURL)
}
func toJSON() throws -> AnyObject {
return try JSONEncoder.create({ (encoder) -> Void in
...
try result.encode(website, key: "website", transformer: JSONTransformerStringToNSURL)
})
}
The following transformers are provided by default:
JSONTransformers.StringToNSURL
:String <-> NSURL
JSONTransformers.StringToNSDate
:String <-> NSDate
ISO format
Feel free to suggest more!
Extending JSONCodable (thanks to @raylillywhite)
This allows for JSONDecoder extensions that allow the type system to better aid in decoding. For example, you could do:
extension JSONDecoder {
public func decode(key: String) throws -> NSURL {
return try decode(key, transformer: JSONTransformers.StringToNSURL)
}
}
then you only need to do:
try url = decoder.decode("url")
instead of
try url = decoder.decode("url", JSONTransformers.StringToNSURL)
Example code
Refer to the included playground in the workspace for more details.
License
JSONCodable
is under the MIT license.