CocoaPods trunk is moving to be read-only. Read more on the blog, there are 17 months to go.
TestsTested | ✓ |
LangLanguage | SwiftSwift |
License | MIT |
ReleasedLast Release | Feb 2016 |
SPMSupports SPM | ✗ |
Maintained by Jiantang Li.
ObjectMapper is a framework written in Swift that makes it easy for you to convert your Model objects (Classes and Structs) to and from JSON.
To support mapping, a Class or Struct just needs to implement the Mappable
protocol.
public protocol Mappable {
init?(_ map: Map)
mutating func mapping(map: Map)
}
ObjectMapper uses the <-
operator to define how each member variable maps to and from JSON.
class User: Mappable {
var username: String?
var age: Int?
var weight: Double!
var array: [AnyObject]?
var dictionary: [String : AnyObject] = [:]
var bestFriend: User? // Nested User object
var friends: [User]? // Array of Users
var birthday: NSDate?
required init?(_ map: Map){
}
// Mappable
func mapping(map: Map) {
username <- map["username"]
age <- map["age"]
weight <- map["weight"]
array <- map["arr"]
dictionary <- map["dict"]
bestFriend <- map["best_friend"]
friends <- map["friends"]
birthday <- (map["birthday"], DateTransform())
}
}
struct Temperature: Mappable {
var celcius: Double?
var fahrenheit: Double?
init?(_ map: Map){
}
mutating func mapping(map: Map) {
celcius <- map["celcius"]
fahrenheit <- map["fahrenheit"]
}
}
Once your class implements Mappable, the Mapper class handles everything else for you:
Convert a JSON string to a model object:
let user = Mapper<User>().map(JSONString)
Convert a model object to a JSON string:
let JSONString = Mapper().toJSONString(user, prettyPrint: true)
Object mapper can map classes composed of the following types:
ObjectMapper supports dot notation within keys for easy mapping of nested objects. Given the following JSON String:
"distance" : {
"text" : "102 ft",
"value" : 31
}
You can access the nested objects as follows:
func mapping(map: Map){
distance <- map["distance.value"]
}
You can also map deep array object into your flat properties:
func mapping(map: Map){
currentEmployerName <- map["employments.0.employer.name"]
}
If you have a key that contains .
, you can disable the above feature as follows:
func mapping(map: Map){
identifier <- map["app.identifier", nested: false]
}
To verify the server response, you can put required parameter in mapping rule spec. The verification uses system assert
function which will not impact your performance for release.
func mapping(map: Map){
identifier <- map["employment.Carma", required: true]
}
In tests, put following codes in your test setUp
function to verify requred fields:
MapRequiredField.assume = { (condition: Bool, message: String) in
XCTAssert(condition, message)
}
ObjectMapper also supports custom Transforms that convert values during the mapping process. To use a transform, simply create a tuple with map["field_name"]
and the transform of choice on the right side of the <-
operator:
birthday <- (map["birthday"], DateTransform())
The above transform will convert the JSON Int value to an NSDate when reading JSON and will convert the NSDate to an Int when converting objects to JSON.
You can easily create your own custom transforms by adopting and implementing the methods in the TransformType protocol:
public protocol TransformType {
typealias Object
typealias JSON
func transformFromJSON(value: AnyObject?) -> Object?
func transformToJSON(value: Object?) -> JSON?
}
In a lot of situations you can use the built in transform class TransformOf
to quickly perform a desired transformation. TransformOf
is initialized with two types and two closures. The types define what the transform is converting to and from and the closures perform the actual transformation.
For example, if you want to transform a JSON String value to an Int you could use TransformOf
as follows:
let transform = TransformOf<Int, String>(fromJSON: { (value: String?) -> Int? in
// transform value from String? to Int?
return value?.toInt()
}, toJSON: { (value: Int?) -> String? in
// transform value from Int? to String?
if let value = value {
return String(value)
}
return nil
})
id <- (map["id"], transform)
Here is a more condensed version of the above:
id <- (map["id"], TransformOf<Int, String>(fromJSON: { $0?.toInt() }, toJSON: { $0.map { String($0) } }))
Classes that implement the Mappable protocol can easily be subclassed. When subclassing Mappable classes, follow the structure below:
class Base: Mappable {
var base: String?
required init?(_ map: Map){
}
func mapping(map: Map) {
base <- map["base"]
}
}
class Subclass: Base {
var sub: String?
required init?(_ map: Map){
super.init(map)
}
override func mapping(map: Map) {
super.mapping(map)
sub <- map["sub"]
}
}
Note: This is an expiremental feature. Not all ObjectMapper functionality is guaranteed to work for immutable mappings.
If you have a class or struct whose properties are immutable (let
) and want to map it using ObjectMapper, you can use the following approach.
In the failable initializer, assign values to your properties using the valueOrFail()
function on the map
object. Once all propeties are set, check isValid
to determine if the mapping succeeded for all properties. If isValid
returns false, return nil
to indicate that initialization failed.
class Model: Mappable {
let name: String // Non-optional property
required init?(_ map: Map) {
name = map["name"].valueOrFail()
if !map.isValid {
return nil
}
}
func mapping(map: Map) {
}
}
if let model = Mapper<Model>().map(JSONString) {
// Now we have valid model.
} else {
// Something wrong...
}
If you are using Alamofire for networking and you want to convert your responses to swift objects, you can use AlamofireObjectMapper. It is a simple Alamofire extension that uses ObjectMapper to automatically map JSON response data to swift objects.
ObjectMapper and Realm can be used together. Simply follow the Class structure below and you will be able to use ObjectMapper to generate your Realm models:
class Model: Object, Mappable {
dynamic var name = ""
required convenience init?(_ map: Map) {
self.init()
}
func mapping(map: Map) {
name <- map["name"]
}
}
Note: Generating a JSON string of a Realm Object using ObjectMappers' `toJSON` function only works within a Realm write transaction. This is caused because ObjectMapper uses the `inout` flag in its mapping functions (`<-`) which are used both for serializing and deserializing. Realm detects the flag and forces the `toJSON` function to be called within a write block even though the objects are not being modified.
Contributions are very welcomed
Before submitting any Pull Request, please ensure you have run the included tests and that they have passed. If you are including new functionality, please write test cases for it as well.
ObjectMapper uses Nimble to ensure test success. It is included using Carthage. Run the following command in the ObjectMapper root directory to fetch the Nimble depency and get the environment ready for running tests:
carthage checkout
From this point on, you should open the project using ObjectMapper.xcworkspace and NOT ObjectMapper.xcodeproj
ObjectMapper can be added to your project using Cocoapods 0.36 (beta) by adding the following line to your Podfile:
pod 'ObjectMapper', '~> 0.19'
Otherwise, ObjectMapper can be added as a submodule:
cd
-ing into your top-level project directory, and entering the command git submodule add https://github.com/Hearst-DD/ObjectMapper.git
ObjectMapper
folder, and drag ObjectMapper.xcodeproj
into the file navigator of your app project.ObjectMapper.framework
.+
button at the top left of the panel and select “New Copy Files Phase”. Rename this new phase to “Copy Frameworks”, set the “Destination” to “Frameworks”, and add ObjectMapper.framework
.