- Adds simple calls to include NULL values in output JSON.
- Adds ability to simply type cast JSON values to specified type.
- Adds ability to map Swift base type arrays into Realm arrays.
- Adds
TimestampTransform
to simply transform to/from UNIX timestamps. - Adds
ISO8601JustDateTransform
to simplty transform to/from ISO8601 date string. Because ObjectMapper'sISO8601DateTransform
actually is date and time transform.
To run the example project, clone the repo, and run pod install
from the Example directory first.
- In Xcode select
File
>Add Packages...
- Copy and paste the following into the search:
https://github.com/APUtils/ObjectMapperAdditions
‼️ Make sureUp to Next Major Version
is selected and put13.2.0
into the lower bound if needed. There is a bug in 14.2 Xcode, it does not select versions higher than 9.0.0 by default‼️ - Tap
Add Package
- Select
ObjectMapperAdditions
to add core functionality - Optionally, select
ObjectMapperAdditionsRealm
to addRealm
related functionality - Tap
Add Package
ObjectMapperAdditions is available through CocoaPods.
To install Core features, simply add the following line to your Podfile:
pod 'ObjectMapperAdditions/Core', '~> 13.2'
To add Realm transform to your project add the following line to your Podfile:
pod 'ObjectMapperAdditions/Realm', '~> 13.2'
It's a common case when app gets Int in JSON instead of String even if backend guy said you it'll be String. Worst of all sometimes it could be String and sometimes something else so it'll look like you released broken app even if you tested it well.
After several projects I made a rule for myself: Never trust a backend!
. I always make optional fields and cast values to type I'll use. Right now I'm using a great framework ObjectMapper
to map my objects but it doesn't have transforms I need so I wrote them as this separate pod.
Example model:
import Foundation
import ObjectMapper
import ObjectMapperAdditions
struct MyModel: Mappable {
var string: String?
var stringsArray: [String]?
var double: Double?
var myOtherModel: MyOtherModel?
var myOtherModelsArray: [MyOtherModel]?
init?(map: Map) {}
mutating func mapping(map: Map) {
// You could specify proper type transform directly
string <- (map["string"], StringTransform.shared)
// Or you could just use TypeCastTransform
string <- (map["string"], TypeCastTransform())
// No doubt it also works with Double
double <- (map["double"], TypeCastTransform())
// Works with arrays too but for TypeCastTransform you must specify type
stringsArray <- (map["stringsArray"], TypeCastTransform<String>())
// Or just use StringTransform directly
stringsArray <- (map["stringsArray"], StringTransform.shared)
// No need to transform your types. They should specify transforms by themselfs.
myOtherModel <- map["myOtherModel"]
myOtherModelsArray <- map["myOtherModelsArray"]
}
}
Right now there are 4 base type transforms you could use: BoolTransform
, DoubleTransform
, IntTransform
and StringTransform
. But for basic types it's easier to just use TypeCastTransform
which will type cast to proper type automatically.
Typecasting for Bool
, Double
, Int
and String
raw representable enums are also supported with EnumTypeCastTransform
.
Moreover this pod has extension to simplify creation of JSON with NULL values included from objects. Just call .toJSON(shouldIncludeNilValues: true)
on BaseMappable
object or array/set.
Date transformers example usage:
// If date in timestamp format (1506423767)
date <- (map["date"], TimestampTransform.shared)
// If date in ISO8601 full-date format (yyyy-MM-dd)
date <- (map["date"], ISO8601JustDateTransform.shared)
See example and tests projects for more details.
This part of ObjectMapperAdditions solves issues that prevent simply using ObjectMapper and Realm in one model. RealmListTransform
to transform custom types into realm lists was taken from ObjectMapper-Realm but it can't transform simple type arrays nor optional values.
import Foundation
import ObjectMapper
import ObjectMapperAdditions
import RealmSwift
class MyRealmModel: Object, Mappable {
@objc dynamic var id: Int = 0
@objc dynamic var double: Double = 0
let optionalDouble = RealmProperty<Double?>()
@objc dynamic var string: String?
@objc dynamic var myOtherRealmModel: MyOtherRealmModel?
let myOtherRealmModels = List<MyOtherRealmModel>()
let myRealmMap = RealmSwift.Map<String, String>()
var strings: List<String> = List<String>()
override class func primaryKey() -> String? { "id" }
override init() {
super.init()
}
required init?(map: ObjectMapper.Map) {
super.init()
// Primary kay should not be reassigned after object is added to the Realm so we make sure it is assigned during init only
id <- (map["id"], IntTransform.shared)
}
func mapping(map: ObjectMapper.Map) {
performMapping {
// Read-only primary key
id >>> map["id"]
// Same as for ordinary model
double <- (map["double"], DoubleTransform.shared)
// Using ObjectMapperAdditions's RealmPropertyTypeCastTransform
optionalDouble <- map["optionalDouble"]
// Custom transform support
// optionalDouble <- (map["optionalDouble"], DoubleTransform.shared)
// You could also use RealmPropertyTransform if you don't like type cast but you need to declare `optionalDouble` as a `var` then
// optionalDouble <- (map["optionalDouble"], RealmPropertyTransform<Double>())
string <- (map["string"], StringTransform.shared)
myOtherRealmModel <- map["myOtherRealmModel"]
// Using ObjectMapper+Realm's RealmListTransform to transform custom types
myOtherRealmModels <- map["myOtherRealmModels"]
myRealmMap <- map["myRealmMap"]
// Using ObjectMapperAdditions's RealmTypeCastTransform
strings <- map["strings"]
// // Custom transform support
// strings <- (map["strings"], StringTransform.shared)
// You could also use RealmTransform if you don't like type cast but you need to declare `optionalDouble` as a `var` then
// strings <- (map["strings"], RealmTransform())
}
}
}
Example @Persisted
Realm model:
import Foundation
import ObjectMapper
import ObjectMapperAdditions
import RealmSwift
class MyPersistedRealmModel: Object, Mappable {
@Persisted var id: Int = 0
@Persisted var double: Double = 0
@Persisted var optionalDouble: Double?
@Persisted var string: String?
@Persisted var myOtherRealmModel: MyOtherRealmModel?
@Persisted var myOtherRealmModels: List<MyOtherRealmModel>
@Persisted var myRealmMap: RealmSwift.Map<String, String>
@Persisted var strings: List<String>
override class func primaryKey() -> String? { "id" }
override init() {
super.init()
}
required init?(map: ObjectMapper.Map) {
super.init()
// Primary kay should not be reassigned after object is added to the Realm so we make sure it is assigned during init only
id <- (map["id"], IntTransform.shared)
}
func mapping(map: ObjectMapper.Map) {
performMapping {
// Read-only primary key
id >>> map["id"]
// Same as for ordinary model
double <- (map["double"], DoubleTransform.shared)
// Using ObjectMapperAdditions's RealmPropertyTypeCastTransform
optionalDouble <- (map["optionalDouble"], DoubleTransform.shared)
// Custom transform support
// optionalDouble <- (map["optionalDouble"], DoubleTransform.shared)
// You could also use RealmPropertyTransform if you don't like type cast but you need to declare `optionalDouble` as a `var` then
// optionalDouble <- (map["optionalDouble"], RealmPropertyTransform<Double>())
string <- (map["string"], StringTransform.shared)
myOtherRealmModel <- map["myOtherRealmModel"]
// Using ObjectMapper+Realm's RealmListTransform to transform custom types
myOtherRealmModels <- map["myOtherRealmModels"]
// For some reason, Xcode 15.3 can't properly select proper operator so we need a workaround
let myRealmMap = self.myRealmMap
myRealmMap <- map["myRealmMap"]
// Using ObjectMapperAdditions's RealmTypeCastTransform
strings <- map["strings"]
// // Custom transform support
// strings <- (map["strings"], StringTransform.shared)
// You could also use RealmTransform if you don't like type cast but you need to declare `optionalDouble` as a `var` then
// strings <- (map["strings"], RealmTransform())
}
}
}
Swift optionals cast to realm optionals this way: Int?
-> RealmProperty<Int>>
, Double?
-> RealmProperty<Double?>
, Bool?
-> RealmProperty<Bool?>
, etc.
Swift arrays cast to realm arrays this way: [String]
-> List<String>
, [Int]
-> List<String>
, [Double]
-> List<Double>
, [Bool]
-> List<Bool>
, etc.
Be sure to check that properties of type RealmProperty
and List
are not dynamic nor optional. Also despite of they defined as var
they should be handled as constants if model is added to Realm. Use .value
to change RealmOptional
value or use .removeAll()
and append(objectsIn:)
methods to change List
content
See example and tests projects for more details.
Any contribution is more than welcome! You can contribute through pull requests and issues on GitHub.
Anton Plebanovich, [email protected]
ObjectMapperAdditions is available under the MIT license. See the LICENSE file for more info.