DynamicMapper is a framework written in Swift for dynamically decoding and encoding models (reference and value types) using Swift native Decodable and Encodable protocols.
- Installation
 - Features
 - The Basics
 - Easy Transformation from 
Codable - Mapping Nested Objects
 - Easy Json Insersion
 - DynamicMapper + Realm
 - Contributing
 - Author
 - License
 
To run the example project, clone the repo, and run pod install from the Example directory first.
DynamicMapper is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'DynamicMapper', '~> 2.0.1' (check releases to make sure this is the latest version)To add DynamicMapper to a Swift Package Manager based project, add:
.package(url: "https://github.com/AbdulrahmanQasem95/DynamicMapper.git", .upToNextMajor(from: "2.0.1")),to your Package.swift files dependencies array.
- Decoding and encoding using native 
JSONDecoderandJSONEncoder - Has the full functionality of 
Codable = Decodable & Encodableprotocols - Nested Objects encoding and decoding (Dynamic Mapping)
 - Rreference & Value types support
 - Dynamic object insertion and creation
 - Safely nested object fetching
 - Support subclassing
 - Smooth transformation since it works directly with your 
Codablemodels without any changes - Compatible with Realm
 - Native replacment of ObjectMapper
 - Support Back to iOS 11, macOS 10.13, tvOS 11, and watchOS 4.
 
Like native JSONDecoder and JSONEncoder To support dynamic mapping, a class or struct just needs to implement the DynamicDecodable protocol for decoding, DynamicEncodable protocol for encoding or DynamicCodable protocol for both decoding and encoding togoether
 protocol DynamicCodable:DynamicDecodable,DynamicEncodable var dynamicSelf: DynamicClass?
 func dynamicMapping(mappingType: DynamicMappingType) {}DynamicMapper uses the <-- operator and ds to set nested member variable.
ds is a safe non optional alias of dynamicSelf
class User: DynamicDecodable {
    var dynamicSelf: DynamicClass?
    
    var username: String?
    var lastname: String?
    var age: Int?
    var weight: Double!
    var birthday: Date?
    var bestFamilyPhoto:URL?                    // Nested URL 
    var numberOfChildren:Int?                   // Nested Int 
    var bestFriend: User?                       // Nested User object
    var friends: [User]?                        // Array of Users
    
    func dynamicMapping(mappingType: DynamicMappingType) {
        bestFamilyPhoto   <--  ds.alboms.familyAlbom.bestPhoto
        numberOfChildren  <--  ds.familyInfo.childrenCount
        bestFriend?.dynamicMapping(mappingType: mappingType)
        friends?.dynamicMapping(mappingType: mappingType)
    }
}
struct Temperature: DynamicDecodable {
    var dynamicSelf: DynamicClass?
    
    var celsius: Double?
    var fahrenheit: Double?
    
    mutating func dynamicMapping(mappingType: DynamicMapper.DynamicMappingType) {
        // nothing to do with model's level parameters, Codable will take care of them
        // unless you want to use custom name
    }
}
// Custom names
struct Temperature: DynamicDecodable {
    var dynamicSelf: DynamicClass?
    
    var celsiusTemperature: Double?
    var fahrenheitTemperature: Double?
    
    mutating func dynamicMapping(mappingType: DynamicMappingType) {
        celsiusTemperature     <--  ds.celsius
        fahrenheitTemperature  <--  ds.fahrenheit
    }
}Once your class implements DynamicDecodable it can be easily decoded using DynamicJSONDecoder class.
Decoding:
 do {
     let userModel = try DynamicJSONDecoder().decode(User.self, from: userDataFromServer)
 } catch  {
     print(error.localizedDescription)
 }Encoding:
  do {
      let userData = try DynamicJSONEncoder().encode(userModel)
  } catch  {
      print(error.localizedDescription)
  }DynamicMapper can decode and encode all types supported by JSONDecoder and JSONEncoder:
StringIntFloatDoubleBoolArray(as long as the elements are alsoCodable)Dictionary(as long as the keys and values areCodable)- Custom structs and classes that conform to 
Codable - Optional types that conform to 
Codable - Enumerations with associated values (as long as the associated values are 
Codable) DateURLData
Since DynamicJSONDecoder inherit JSONDecoder and DynamicJSONEncoder inherit JSONEncoder, all your Codable classes will work same like before with the new dynamic decoder and encoder without any changes.
This will allow you to move smoothly and easily from ordinary Codable to DynamicCodable and you can even mix them togethor.
You can keep the ordinary Codable and use DynamicCodable only for models where you need to access nested object without defining the implecit objects or where you need to use custom names ...
{
  "title": "Swift",
  "category": "Programming Languages",
  "teacher": {
    "firstName": "Abdulrahman",
    "lastName": "Qasem"
  }
}class Subject:Codable {
    var title:String
    var category:String
    var teacher:User?
}
class User:Codable {
    var firstName:String
    var lastName:String
}
 do {
     let subjectModel = try DynamicJSONDecoder().decode(Subject.self, from: subjectData)
     print(subjectModel.teacher.firstName) //Abdulrahman
 } catch  {
     print(error.localizedDescription)
 }or using DynamicMapper
class Subject:DynamicCodable {
    var dynamicSelf: DynamicClass?
    
    var title:String
    var category:String
    var teacherName:String?  //Abdulrahman
    
    func dynamicMapping(mappingType: DynamicMappingType) {
        teacherName   <--   ds.teacher.firstName
    }
}
 do {
     let subjectModel = try DynamicJSONDecoder().decode(Subject.self, from: subjectData)
     print(teacherName)
 } catch  {
     print(error.localizedDescription)
 }This function is where all nested items and models definitions should go. this function is executed during encoding and decoding proccess. It is the only function that is called on the object.
Dynamic copy of the object that we will use to dynamically access the nested properties or models, we can use either dynamicSelf or its safe non optional alias ds to access the dynamic model when dynamicMapping(mappingType:DynamicMappingType) function get called
| DynamicCodable | |
|---|---|
| Properties | |
var bestFamilyPhoto: URL? var numberOfChildren: Int?  | 
  |
| Data -> Model (Decoding) | |
func dynamicMapping(mappingType: DynamicMappingType) {
    bestFamilyPhoto   <--  ds.alboms.familyAlbom.bestPhoto
    numberOfChildren  <--  ds.familyInfo.childrenCount
}
   | 
  |
| Model -> Data (Encoding) | |
func dynamicMapping(mappingType: DynamicMappingType) {
    bestFamilyPhoto   -->  {ds.alboms.familyAlbom.bestPhoto.set($0)}
    numberOfChildren  -->  {ds.familyInfo.childrenCount.set($0)}
}
     | 
    
  
  |
| Decoding & Encoding | |
func dynamicMapping(mappingType: DynamicMappingType) {
    switch mappingType {
        case .decoding:
            bestFamilyPhoto   <--  ds.alboms.familyAlbom.bestPhoto
            numberOfChildren  <--  ds.familyInfo.childrenCount
        case .encoding:
            bestFamilyPhoto   -->  {ds.alboms.familyAlbom.bestPhoto.set($0)}
            numberOfChildren  -->  {ds.familyInfo.childrenCount.set($0)}        
        }
}
     | 
|
DynamicMapper supports dot notation within keys for easy mapping of nested objects. Given the following JSON String:
{
    "property0": "Value 0",
    "level1": {
        "property1": "Value 1",
        "level2": {
            "property2": 10,
            "level3": {
                "property3": "Value 3",
                "level4": {
                    "property4": "Value 4",
                    "level5": {
                        "property5": "Value 5",
                        "level6Array": [
                            {
                                "item1": "Item A",
                                "item2": 100
                            },
                            {
                                "item1": "Item C",
                                "item2": 200
                            },
                            {
                                "item1": "Item E",
                                "item2": 300
                            }
                        ]
                    }
                }
            }
        }
    }
}You can access the nested objects as follows:
 var property2:Int?
 func dynamicMapping(mappingType: DynamicMappingType) {
     property2   <--   ds.level1.level2.property2
 }Nested keys also support accessing values from an array
 var secondArrayItem_1_OfLevel_6:String?
 func dynamicMapping(mappingType: DynamicMappingType) {
    secondArrayItem_1_OfLevel_6 <--  ds.level1.level2.level3.level4.level5.level6Array[1].item1
 }fetching array item by index is safe
// this will do nothing
 var secondArrayItem_1_OfLevel_6:String?
 func dynamicMapping(mappingType: DynamicMappingType) {
    secondArrayItem_1_OfLevel_6 <--  ds.level1.level2.level3.level4.level5.level6Array[1200].item1
 }You can access nested custom model as follows:
var level_6_Array:[ArrayItemModel]? 
func dynamicMapping(mappingType: DynamicMappingType) {
    level_6_Array  <--  ds.level1.level2.level3.level4.level5.level6Array
}
struct ArrayItemModel:DynamicCodable{
    var dynamicSelf:DynamicClass?
    
    var item1:String
    var item2:Int
    
    func dynamicMapping(mappingType: DynamicMappingType) {}
}Using the same Json above, You can insert any object or array same like when you access it
ds.level1.level2.level3.insertedProperty.set("nested item")
ds.level1.level2.insertedArray.set(["inserted item 1","inserted item 2"])
ds.level1.level2.level3.level4.level5.level6Array[4].set(ArrayItemModel(item1: "inserted item 1", item2: 5))json will be
{
    "property0": "Value 0",
    "level1": {
        "property1": "Value 1",
        "level2": {
            "property2": 10,
            "insertedArray": [
             "inserted item 1",
             "inserted item 2"
            ],
            "level3": {
                "property3": "Value 3",
                "insertedProperty": "nested item",
                "level4": {
                    "property4": "Value 4",
                    "level5": {
                        "property5": "Value 5",
                        "level6Array": [
                            {
                                "item1": "Item A",
                                "item2": 100
                            },
                            {
                                "item1": "Item C",
                                "item2": 200
                            },
                            {
                                "item1": "Item E",
                                "item2": 300
                            },
                            {
                                "item1": "inserted item 1",
                                "item2": 5
                            }
                        ]
                    }
                }
            }
        }
    }
}DynamicJSONDecoder and DynamicJSONEncoder inherit  JSONDecoder and JSONEncoder respectively, so they have the full power of native Codable
,for example date decoding
{
    "user":{
        "personalInfo":{
            "birthDate": "2023-10-30T19:00:00Z"
            .
            .
            .
        }
        .
        .
        .
    }
    .
    .
    .
} let decoder = DynamicJSONDecoder()
 decoder.dateDecodingStrategy = .iso8601
 
 var birthDate:Date?
 func dynamicMapping(mappingType: DynamicMappingType) {
     birthDate  <--  ds.user.personalInfo.birthDate
 }DynamicMapper and Realm can be used together
import RealmSwift
class Model: Object, DynamicCodable {
    var dynamicSelf:DynamicClass?
    
    @objc dynamic var name:String? = ""
    
    func dynamicMapping(mappingType: DynamicMappingType) {
        switch mappingType {
        case .decoding:
            name  <--  ds.user.personalInfo.name
        case .encoding:
            name  -->  {ds.user.personalInfo.name.set($0)}
        }
    }
}Contributions are very welcome ππ.
Before submitting any pull request, If you are including new functionality, please write test cases for it.
Abdulrahman Qasem, [email protected]
DynamicMapper is available under the MIT license. See the LICENSE file for more info.