Mapper is a simple Swift library to convert JSON to strongly typed objects. One advantage to Mapper over some other libraries is you can have immutable properties.
Installation
With CocoaPods
use_frameworks! pod "ModelMapper"
With Carthage
Usage
Simple example:
import Mapper // Conform to the Mappable protocol struct User: Mappable { let id: String let photoURL: URL? // Implement this initializer init(map: Mapper) throws { try id = map.from("id") photoURL = map.optionalFrom("avatar_url") } } // Create a user! let JSON: NSDictionary = ... let user = User.from(JSON) // This is a 'User?'
Using with enums:
enum UserType: String { case Normal = "normal" case Admin = "admin" } struct User: Mappable { let id: String let type: UserType init(map: Mapper) throws { try id = map.from("id") try type = map.from("user_type") } }
Nested Mappable objects:
struct User: Mappable { let id: String let name: String init(map: Mapper) throws { try id = map.from("id") try name = map.from("name") } } struct Group: Mappable { let id: String let users: [User] init(map: Mapper) throws { try id = map.from("id") users = map.optionalFrom("users") ?? [] } }
Use Convertible to transparently convert other types from JSON:
extension CLLocationCoordinate2D: Convertible { public static func fromMap(_ value: Any) throws -> CLLocationCoordinate2D { guard let location = value as? NSDictionary, let latitude = location["lat"] as? Double, let longitude = location["lng"] as? Double else { throw MapperError.convertibleError(value: value, type: [String: Double].self) } return CLLocationCoordinate2D(latitude: latitude, longitude: longitude) } } struct Place: Mappable { let name: String let location: CLLocationCoordinate2D init(map: Mapper) throws { try name = map.from("name") try location = map.from("location") } } let JSON: NSDictionary = [ "name": "Lyft HQ", "location": [ "lat": 37.7603392, "lng": -122.41267249999999, ], ] let place = Place.from(JSON)
Custom Transformations
private func extractFirstName(object: Any?) throws -> String { guard let fullName = object as? String else { throw MapperError.convertibleError(value: object, type: String.self) } let parts = fullName.characters.split { $0 == " " }.map(String.init) if let firstName = parts.first { return firstName } throw MapperError.customError(field: nil, message: "Couldn't split the string!") } struct User: Mappable { let firstName: String init(map: Mapper) throws { try firstName = map.from("name", transformation: extractFirstName) } }
Parse nested or entire objects
struct User: Mappable { let name: String let JSON: AnyObject init(map: Mapper) throws { // Access the 'first' key nested in a 'name' dictionary try name = map.from("name.first") // Access the original JSON (maybe for use with a transformation) try JSON = map.from("") } }
See the docstrings and tests for more information and examples.
Open Radars
These radars have affected the current implementation of Mapper
- rdar://23376350 Protocol extensions with initializers do not work in extensions
- rdar://23358609 Protocol extensions with initializers do not play well with classes
- rdar://23226135 Can't conform to protocols with similar generic function signatures
- rdar://23147654 Generic functions are not differentiated by their ability to throw
- rdar://23695200
Using the
??operator many times is unsustainable. - rdar://23697280 Lazy collection elements can be evaluated multiple times.
- rdar://23718307
Non final class with protocol extensions returning
Selfdon't work
License
Mapper is maintained by Lyft and released under the Apache 2.0 license. See LICENSE for details