Coder Social home page Coder Social logo

abdulrahmanqasem95 / dynamicmapper Goto Github PK

View Code? Open in Web Editor NEW
25.0 1.0 0.0 4.32 MB

Dynamic decoding and encoding using native Codable protocol

License: MIT License

Ruby 3.23% Swift 96.77%
alamofire ios json macos objectmapper realmswift swift swiftyjson tvos watchos

dynamicmapper's Introduction

DynamicMapper

Version License Platform

DynamicMapper is a framework written in Swift for dynamically decoding and encoding models (reference and value types) using Swift native Decodable and Encodable protocols.

Example

To run the example project, clone the repo, and run pod install from the Example directory first.

Installation

Cocoapods

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)

Swift Package Manager

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.

Features:

  • Decoding and encoding using native JSONDecoder and JSONEncoder
  • Has the full functionality of Codable = Decodable & Encodable protocols
  • 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 Codable models without any changes
  • Compatible with Realm
  • Native replacment of ObjectMapper
  • Support Back to iOS 11, macOS 10.13, tvOS 11, and watchOS 4.

The Basics

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:

  • String
  • Int
  • Float
  • Double
  • Bool
  • Array (as long as the elements are also Codable)
  • Dictionary (as long as the keys and values are Codable)
  • 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)
  • Date
  • URL
  • Data

Easy Transformation from Codable

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)
 }

DynamicCodable Protocol

mutating func dynamicMapping(mappingType:DynamicMappingType)

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.

var dynamicSelf:DynamicClass?

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)}        
        }
}

Easy Mapping of Nested Objects

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) {}
}

Easy Json Insersion

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
                            }
                        ]
                    }
                }
            }
        }
    }
}

Extend JSONDecoder & JSONEncoder

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 + Realm

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)}
        }
    }
}

Contributing

Contributions are very welcome ๐Ÿ‘๐Ÿ˜ƒ.

Before submitting any pull request, If you are including new functionality, please write test cases for it.

Author

Abdulrahman Qasem, [email protected]

LinkedIn

License

DynamicMapper is available under the MIT license. See the LICENSE file for more info.

dynamicmapper's People

Contributors

abdulrahmanqasem95 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.