{"id":17043409,"url":"https://github.com/leavez/mappable","last_synced_at":"2025-04-12T15:11:19.456Z","repository":{"id":56920889,"uuid":"135706971","full_name":"leavez/Mappable","owner":"leavez","description":"flexible JSON to Model converter, specially optimized for immutable properties","archived":false,"fork":false,"pushed_at":"2020-03-17T17:27:55.000Z","size":57,"stargazers_count":27,"open_issues_count":0,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-05-01T01:42:36.132Z","etag":null,"topics":["decoder","deserialization","immutable","json","let","mapping","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leavez.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-06-01T10:59:14.000Z","updated_at":"2023-11-27T01:03:38.000Z","dependencies_parsed_at":"2022-08-20T21:50:25.704Z","dependency_job_id":null,"html_url":"https://github.com/leavez/Mappable","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leavez%2FMappable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leavez%2FMappable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leavez%2FMappable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leavez%2FMappable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leavez","download_url":"https://codeload.github.com/leavez/Mappable/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248586230,"owners_count":21128997,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["decoder","deserialization","immutable","json","let","mapping","swift"],"created_at":"2024-10-14T09:29:29.163Z","updated_at":"2025-04-12T15:11:19.438Z","avatar_url":"https://github.com/leavez.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mappable\n\n[![Swift](https://img.shields.io/badge/swift-5-orange.svg?style=flat)](#) [![Swift Package Manager](https://rawgit.com/jlyonsmith/artwork/master/SwiftPackageManager/swiftpackagemanager-compatible.svg)](https://swift.org/package-manager/)\n[![Build Status](https://travis-ci.org/leavez/Mappable.svg?branch=master)](https://travis-ci.org/leavez/Mappable)\n[![Codecov branch](https://img.shields.io/codecov/c/github/leavez/Mappable/master.svg?style=flat)](https://codecov.io/gh/leavez/Mappable)\n\n\nMappable is a lightweight, flexible, easy-to-use framework to convert JSON to model, specially optimized for immutable property initialization.\n\n```swift\nstruct Flight: Mappable {\n    let number: String\n    let time: Date\n    \n    init(map: Mapper) throws {\n\n        // with the help of @dynamicMemberLookup feature\n        number = try map.id()\n        time   = try map.time()\n\n        // or use the old way\n        // number = try map.from(\"id\")\n        // time   = try map.from(\"time\")\n    }\n}\n// Flight(JSONString: json)\n```\n\nA [xcode plugin](https://github.com/leavez/Mappable_xcode_plugin) is also provided to genereate implementation automatically.\n\n## Features\n\n- JSON to object by just specifying mapping relationships\n- Optimized for immutable and optional\n- Flexible: easy to mix with manual initailization\n- Compatible types conversion : e.g. a Int property could be initialized with String value \n- Key path support\n\n## Why Another?\n\nMost JSON to model libraries cannot handle immutable property initialization well. They require to declare properties with `var` and nullable types, which break the sprint of Swift and lead to bad code. Mappable was born for solving this problem.\n\n\n|              | Pros          | Cons                                                                                                                                                                                                                                         |\n|--------------|---------------|---------------|\n| Codable      | - Native in Swift\u003cbr\u003e- Automatic (no mapping relationships)\u003cbr\u003e- support 2-direction conversion | -  Inflexible\u003cbr\u003e- Doesn't support inherented class                                                                                                                                                                                               |\n| HandyJSON    | - Automatic (no mapping relationships)\u003cbr\u003e- 2-direction conversion   | No immutable properties support                                                                                                                                                                               |\n| ObjectMapper |  2-direction conversion  | - Immutable properties support is weak* \u003cbr\u003e- Multiple partterns led to chaos \u003cbr\u003e- Missing support for some combinations of types.|\n| SwiftyJSON   |    | Not a JSON object convertor.\u003cbr\u003e It's only a convenient tool to deal with JSON data.                                                                                                                                                              |\n\n\\* 1) Cannot handle optional conveniently. 2) Doesn't support of compatible types conversion, which fall the whole object for every small mal-format in JSON. \n\nMappable is highly inspired by ObjectMapper. You could tread Mappable as an improved version of `ImmutableMappable` in ObjectMapper.\n\n\n## Usage\n\n### The basics\n\nTo support mapping, a type should implement `Mappable` protocol, which have only an initializer method:\n\n```swift\nclass Country: Mappable {\n    let name: String\n    let cities: [City]   // struct City: Mappable { ... }\n    let atContinent: Continent // enum Continent: Mappable { ... }\n    \n    required init(map: Mapper) throws {\n        name        = try map.from(\"name\")\n        cities      = try map.from(\"city\")\n        atContinent = try map.from(\"location.continent\")\n    }\n}\n```\n\nYou just need write the mapping relationship: a key path for a property. Although these lines are just normal assignment statements, types aren't needed to specified, so you could tread these lines as a special representation of mapping relationships. (You could read the line as \"try (to) map (value) from XXX\" 😆 )\n\nThen you could initialize a object like this:\n\n```swift\n// NOTE: these initializer throw errors, you should do error handling\nlet c = try Country(JSON: jsonDict)\nlet d = try? Country(JSONString: jsonString)\n```\n\n### Supported types\n\n- Primitive types: `Int`, `Double`, `String`, `Bool`, `URL`, `Date` ...\n- Container types: `Array`, `Dictionary`, `Set`\n- Optional type\n- Enum, Struct, Object\n- Any combination of the types above\n\n### Default value\n\n```swift\n// just use `??`\ncities = try map.from(\"city\") ?? []\n```\n\n### Optional handling\n\n`Optional` types won't throw an error even if there's no corresponding date in JSON or the date is in mal-format. A `nil` will be assigned in this situation. \n\nIf you declare a property as an optional, it may mean this data isn't strictly required in JSON. So you wish to get a nil value if there's no data actually. \n\n```swift\nstruct User: Mappable {\n    let ID: String\n    let summary: String?\n    \n    init(map: Mapper) throws {\n        ID      = try map.from(\"id\")\n        summary = try map.from(\"summary\")\n    }\n}\nlet json = [\"id\": \"a123\"]\nlet user = try! User(JSONObject: json) // It won't crash.\n```\n\n### Compatible types conversion\n\n|                             | Convert from                                                 |\n| :-------------------------- | ------------------------------------------------------------ |\n| Int, Double, Float, CGFloat | String                                                       |\n| Bool                        | Int,  \"true\", \"True\", \"TRUE\", \"YES\", \"false\", \"False\", \"FALSE\", \"NO\", \"0\", \"1\" |\n| String                      | Int, NSNumber  |\n| URL                         | String  |\n| Date                        | Double(secondsSince1970),  String (RFC 3339, e.g. `2016-06-13T16:00:00+00:00`)                                             |\n\nMore detail at [here](https://github.com/leavez/Mappable/blob/master/Sources/Mappable/Mappable%2BBasicType.swift).\n\n### Custom conversion\n\nThe content in initializer is just plain assignment, so you could do anything with the data. Use `map.getRootValue()` and `map.getValue(keyPath:)` to the get the raw JSON value and do what you want.\n\nFor convenient date conversion, there's also a `options` property in `Mapper` to set custom date strategy. (More complex example [here](https://github.com/leavez/Mappable/blob/d492956c0a8626b44cd96987353f9c9b467a3f44/Tests/MappableTests/BasicTypeTest.swift#L287))\n\n### Enum\n\nEnums conforming `RawRepresentable` have a default implementation of `Mappable`. You just need to declare the conforming of `Mappable` to your enum types, then it will work.\n\nFor enum with associated values, you could do the manual implementation:\n\n```swift\nenum EnumWithValues: Mappable {\n    case a(Int)\n    case b(String)\n    \n    init(map: Mapper) throws {\n        // It could be initialized with a json date like:\n        // {\"type\": \"a\", value: 123}\n        let value = try map.getValue(\"type\", as: String.self)\n        switch value {\n        case \"a\":\n            self = .a(try map.from(\"value\"))\n        case \"b\":\n            self = .b(try map.from(\"value\"))\n        default:\n            throw ErrorType.JSONStructureNotMatchDesired(value, \"string a/b\")\n        }\n    }\n}\n```\n\n### Class inheritance\n\n```swift\nclass ChildModel: BaseModel {\n    let b : Int\n    required init(map: Mapper) throws {\n        b = try map.from(\"b\")\n        try super.init(map: map)\n    }\n}\n```\n\n### Nested key path\n\nUse key path \"AAA.BBB\" to map a multi-level path value in JSON:\n\n```swift\n// let json = \"\"\" {\"AAA\": {\"BBB\": 1}} \"\"\"\nb = try map.from(\"AAA.BBB\")\n\n// use `n` to get a n-th value in array\n// let json = \"\"\" {\"AAA\": [11,22,33]} \"\"\"\nb = try map.from(\"AAA.`2`\") // b = 33\n```\n\nIf a normal key contains `.` naturally, you could use like `map.from(\"a.file\", keyPathIsNested: false)`, to treat the key as a single-level path.\n\n## Installation\n\n- for Swift 5 \u0026 4.2   : v1.3+\n- for Swift 4.1 and below : v1.2.2\n\n### Cocoapods\n\n```ruby\npod 'Mappable'\n```\n\n### Swift Package Manager\n\n```swift\n.Package(url: \"https://github.com/leavez/Mappable.git\", from: \"1.5.0\"),\n```\n\n### Carthage\n\n```ruby\ngithub \"leavez/Mappable\"\n```\n\n## License\n\nMappable is available under the MIT license. See the LICENSE file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleavez%2Fmappable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleavez%2Fmappable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleavez%2Fmappable/lists"}