{"id":32151899,"url":"https://github.com/dgrzeszczak/keyedcodable","last_synced_at":"2025-10-21T10:55:29.299Z","repository":{"id":56917929,"uuid":"133284787","full_name":"dgrzeszczak/KeyedCodable","owner":"dgrzeszczak","description":"Easy nested key mappings for swift Codable","archived":false,"fork":false,"pushed_at":"2023-02-02T11:14:52.000Z","size":1281,"stargazers_count":283,"open_issues_count":1,"forks_count":20,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-10-21T10:55:15.942Z","etag":null,"topics":["codable","decodable","encodable","json","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/dgrzeszczak.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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-05-14T00:16:37.000Z","updated_at":"2025-04-29T04:39:25.000Z","dependencies_parsed_at":"2023-02-17T19:00:59.528Z","dependency_job_id":null,"html_url":"https://github.com/dgrzeszczak/KeyedCodable","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/dgrzeszczak/KeyedCodable","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dgrzeszczak%2FKeyedCodable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dgrzeszczak%2FKeyedCodable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dgrzeszczak%2FKeyedCodable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dgrzeszczak%2FKeyedCodable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dgrzeszczak","download_url":"https://codeload.github.com/dgrzeszczak/KeyedCodable/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dgrzeszczak%2FKeyedCodable/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280248570,"owners_count":26297925,"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","status":"online","status_checked_at":"2025-10-21T02:00:06.614Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["codable","decodable","encodable","json","swift"],"created_at":"2025-10-21T10:55:24.192Z","updated_at":"2025-10-21T10:55:29.294Z","avatar_url":"https://github.com/dgrzeszczak.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![Version](https://img.shields.io/cocoapods/v/KeyedCodable.svg?style=flat)](https://cocoapods.org/pods/KeyedCodable)\n[![License](https://img.shields.io/cocoapods/l/KeyedCodable.svg?style=flat)](https://cocoapods.org/pods/KeyedCodable)\n[![Platform](https://img.shields.io/cocoapods/p/KeyedCodable.svg?style=flat)](https://cocoapods.org/pods/KeyedCodable)\n\n![](/keyedCodable.gif) \n\n# The goal \n\n*KeyedCodable* is an addition to swift's *Codable* and it's designed for automatic nested key mappings. The goal it to avoid manual implementation of *Encodable/Decodable* and make encoding/decoding easier, more readable, less boilerplate and what is the most important fully compatible with 'standard' *Codable*. \n\n# Nested keys\nFirst, please have a look on Codable example provided by Apple.\n### vanilla Codable:\n```swift\nstruct Coordinate {\n    var latitude: Double\n    var longitude: Double\n    var elevation: Double\n\n    enum CodingKeys: String, CodingKey {\n        case latitude\n        case longitude\n        case additionalInfo\n    }\n\n    enum AdditionalInfoKeys: String, CodingKey {\n        case elevation\n    }\n}\n\nextension Coordinate: Decodable {\n    init(from decoder: Decoder) throws {\n        let values = try decoder.container(keyedBy: CodingKeys.self)\n        latitude = try values.decode(Double.self, forKey: .latitude)\n        longitude = try values.decode(Double.self, forKey: .longitude)\n\n        let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)\n        elevation = try additionalInfo.decode(Double.self, forKey: .elevation)\n    }\n}\n\nextension Coordinate: Encodable {\n    func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(latitude, forKey: .latitude)\n        try container.encode(longitude, forKey: .longitude)\n\n        var additionalInfo = container.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)\n        try additionalInfo.encode(elevation, forKey: .elevation)\n    }\n}\n```\n\n### KeyedCodable:\n```swift\nstruct Coordinate: Codable {\n    var latitude: Double\n    var longitude: Double\n    var elevation: Double\n    \n    enum CodingKeys: String, KeyedKey {\n        case latitude\n        case longitude\n        case elevation = \"additionalInfo.elevation\"\n    }\n}\n```\n\n*By default dot is used as delimiter to separate the inner keys*\n\n# @Flat classes\nFlat classes feature allows you to group properties into smaller parts. In the example below you can see that: \n- longitude and latitude are placed in json's main class but it is 'moved' into separate struct ```Location``` in the swift's model\n- both longitude and latitude are optional. If any of them is missing, the location property will be nil.\n\n### Example JSON:\n```json\n{\n    \"longitude\": 3.2,\n    \"latitude\": 3.4,\n    \n    \"greeting\": \"hallo\"\n}\n```\n\n### KeyedCodable:\n```swift\nstruct InnerWithFlatExample: Codable {\n    @Flat var location: Location?\n    \n    let greeting: String\n}\n\nstruct Location: Codable {\n    let latitude: Double\n    let longitude: Double\n}\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eWithout property wrappers\u003c/summary\u003e\n  \n```swift\nstruct InnerWithFlatExample: Codable {\n    let location: Location?\n    \n    let greeting: String\n\n    enum CodingKeys: String, KeyedKey {\n        case greeting\n        case location = \"\"\n    }\n}\n```\n\n*By default empty string or whitespaces are used to mark flat class*\n\u003c/details\u003e\n\n# @Flat arrays\nProbably you found out that decoding an array will fail if decoding of any array's element fails. Flat array *KeyedCodable's* feature omits incorrect elements and creates array that contain valid elements only. In example below ```array``` property will contain three elements [1,3,4] though decoding second element fails.\n\n```json\n{\n    \"array\": [\n        {\n        \"element\": 1\n        },\n        {},\n        {\n        \"element\": 3\n        },\n        {\n        \"element\": 4\n        }\n    ]\n}\n```\n### KeyedCodable:\n```swift\nstruct OptionalArrayElementsExample: Codable {\n    @Flat var array: [ArrayElement]\n}\n\nstruct ArrayElement: Codable {\n    let element: Int\n}\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eWithout property wrappers\u003c/summary\u003e\n\n```swift\nstruct OptionalArrayElementsExample: Codable {\n    let array: [ArrayElement]\n\n    enum CodingKeys: String, KeyedKey {\n        case array = \".array\"\n    }\n}\n```\n\n*To enable flat array you have to add [flat][delimiter] before property name - by defult it is 'empty string + dot'*\n\u003c/details\u003e\n\n# KeyOptions\n\nIn case of conflicts between json's keys and delimiters used by ```KeyedCodable```, you may use ```KeyOptions``` to configure mapping features. You may do that by providing ```options: KeyOptions?``` property in your CodingKeys ( use ```nil``` to use default value). You may also disable the feature by setting ```.none``` value. \n\n\u003cdetails\u003e\n\u003csummary\u003eExamples\u003c/summary\u003e\n\n### Example JSON:\n```json\n{\n    \"* name\": \"John\",\n    \"\": {\n        \".greeting\": \"Hallo world\",\n        \"details\": {\n            \"description\": \"Its nice here\"\n        }\n    },\n    \"longitude\": 3.2,\n    \"latitude\": 3.4,\n    \"array\": [\n    {\n    \"element\": 1\n    },\n    {},\n    {\n    \"element\": 3\n    },\n    {\n    \"element\": 4\n    }\n    ],\n    \"* array1\": [\n    {\n    \"element\": 1\n    },\n    {},\n    {\n    \"element\": 3\n    },\n    {\n    \"element\": 4\n    }\n    ]\n}\n```\n### KeyedCodable:\n```swift\nstruct KeyOptionsExample: Codable {\n    let greeting: String\n    let description: String\n    let name: String\n    let location: Location\n    let array: [ArrayElement]\n    let array1: [ArrayElement]\n\n\n    enum CodingKeys: String, KeyedKey {\n        case location = \"__\"\n        case name = \"* name\"\n        case greeting = \"+.greeting\"\n        case description = \".details.description\"\n\n        case array = \"### .array\"\n        case array1 = \"### .* array1\"\n\n        var options: KeyOptions? {\n            switch self {\n            case .greeting: return KeyOptions(delimiter: .character(\"+\"), flat: .none)\n            case .description: return KeyOptions(flat: .none)\n            case .location: return KeyOptions(flat: .string(\"__\"))\n            case .array, .array1: return KeyOptions(flat: .string(\"### \"))\n            default: return nil\n            }\n        }\n    }\n}\n```\n\u003c/details\u003e\n\n# @Zero as default value\n\n@Zero feature sets default \"zero\" value for the property in case value is not set in the JSON (no key at all or null value is set). If you find the situation there is no difference between 0 and nil from the business perspective you can easily get rid off optional from your codable.\n\n### Example JSON:\n```json\n{\n    \"name\": \"Jan\"\n}\n```\n\n### KeyedCodable:\n```swift\nstruct ZeroTestCodable: Codable {\n    var name: String                    // Jan\n    \n    @Zero var secondName: String        // \"\" - empty string\n    @Zero var numberOfChildren: Int     // 0\n    @Zero var point: Point              // Point(x: 0.0, y: 0.0)\n}\n\nstruct Point: Codable {\n    let x: Float\n    let y: Float\n}\n```\n\n# Transformers - @CodedBy, @EncodedBy, @DecodedBy\n\nHave you ever had a need of decoding a JSON having multiple Date formats in it ? Or maybe you had to add custom transformation from one value to another during decode/encode process and you had to write a lot of boilerplate code for manual coding ? Transformers are designed for making custom transformation a lot easier ! \n\nTo add your custom transformer you have to implement ```DecodableTransformer```, ```EncodableTransformer``` or ```Transformer``` for two way transformation. \n\n## Date formater example \n\n### Example JSON:\n```json\n{\n    \"date\": \"2012-05-01\"\n}\n```\n\n### Usage \n```swift\nstruct DateCodableTrasform: Codable {\n\n    @CodedBy\u003cDateTransformer\u003e var date: Date\n}\n```\n\n### Transformer\n```swift\nenum DateTransformer\u003cObject\u003e: Transformer {\n\n    static func transform(from decodable: String) -\u003e Any? {\n        return formatter.date(from: decodable)\n    }\n\n    static func transform(object: Object) -\u003e String? {\n        guard let object = object as? Date else { return nil }\n        return formatter.string(from: object)\n    }\n    \n    \n    private static var formatter: DateFormatter {\n        let formatter = DateFormatter()\n        formatter.dateFormat = \"yyyy-MM-dd\"\n        return formatter\n    }\n}\n```\n\n## Non codable formater example \n\nYou can even implement transformer for non Codable model and use it in you Codable tree like that:\n\n### Example JSON:\n```json\n{\n    \"user\": 3\n}\n```\n\n### Usage \n```swift\nstruct DetailPage: Codable {\n\n    @CodedBy\u003cUserTransformer\u003e var user: User\n}\n\nstruct User {           // User do not implement Codable protocol\n    let id: Int\n}\n```\n\n### Transformer\n```swift\nenum UserTransformer\u003cObject\u003e: Transformer {\n    static func transform(from decodable: Int) -\u003e Any? {\n        return User(id: decodable)\n    }\n\n    static func transform(object: Object) -\u003e Int? {\n        return (object as? User)?.id\n    }\n}\n```\n\n# How to use?\n\nTo support nested key mappings you need to use ```KeyedKey``` intead of ```CodingKey``` for your ```CodingKeys``` enums and  ```KeyedJSONEncoder```/```KeyedJSONDecoder``` in place standard ```JSONEncoder```/```JSONDecoder```. Please notice that *Keyed* coders inherit from standard equivalents so they are fully compatible with Apple versions. \n\n### Codable extensions\n\n```swift\nstruct Model: Codable {\n    var property: Int\n}\n```\nDecode from string:\n```swift \nlet model = try Model.keyed.fromJSON(jsonString)\n```\nDecode from data: \n```swift\nlet model = try Model.keyed.fromJSON(data)\n```\nEndcode to string: \n```swift\nmodel.keyed.jsonString() \n```\n\nEncode to data: \n```swift\nmodel.keyed.jsonData() \n```\n\n*You can provide coders in method parameters in case you need additional setup*\n\n### Coders \n\nYou can also use *Keyed* coders the same way as standard versions. \n\n```swift\nlet model = try KeyedJSONDecoder().decode(Model.self, from: data)\n```\n```swift\nlet data = try KeyedJSONEncoder().encode(model)\n```\n\nIt's worth to mention that *Keyed* coders supports simple types ie. ```String```, ```Int``` etc. \nFor example when we try to decode ```Int``` using standard ```JSONDecoder``` \n```swift\nlet number = try JSONDecoder().decode(Int.self, from: data)\n```\nit will throw an incorrect format error. Keyed version will parse that with success. \n\n### Keyed\u003c\u003e wrapper\n\nThere is a possibility to use standard JSON coders and still encode/decode *KeyedCodables*. To do that you have to use ```Keyed\u003c\u003e``` wrapper: \n```swift\nlet model = try JSONDecoder().decode(Keyed\u003cModel\u003e.self, from: data).value\n```\n```swift\nlet data = try JSONEncoder().encode(Keyed(model))\n```\n\nIt may be useful in case you do not have an access to coder's initialization code. In that situation your model may looks like that: \n\n```swift\nstruct KeyedModel: Codable { \n... \n\n    enum CodingKeys: String, KeyedKey {\n        ....\n    }\n.... \n}\n\nstruct Model { \n    \n    let keyed: Keyed\u003cKeyedModel\u003e    \n}\n```\n\n### Manual nested key coding\n\nIn case you need implement *Codable* manually you can use ```AnyKey``` for simpler access to nested keys. \n\n```swift\nprivate let TokenKey = AnyKey(stringValue: \"data.tokens.access_token\")\n\nstruct TokenModel: Codable {\n\n    let token: Token\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: AnyKey.self)\n        token = try container.decode(Token.self, forKey: TokenKey)\n    }\n\n    func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy:  AnyKey.self)\n        try container.encode(token, forKey: TokenKey)\n    }\n}\n```\n\n### KeyedConfig\n\nAs mentioned earlier there is possibility to setup key options eg. delimiters on ```KeyedKey``` level but there is also possibility to setup it globally. \n\nTo do that you need to set the value of  ```KeyedConfig.default.keyOptions```. \n\nBeside key opptions there is also possibility to setup coders used by default in *Codable* extensions.\n\n## Migration to 3.x.x version \n\nVersion 3.0.0 is backward compatible with version 2.x.x however you have to use **swift 5.1** for all new features connected with ```@PropertyWrapper```s. \n\n## Migration to 2.x.x version \n\nUnfortunately 2.x.x version is not compatible with 1.x.x versions but I believe that new way is much better and it brings less boilerplate than previous versions. There is no need to add any manual mapping implementation, it's really simple so I strongly recommend to migrate to new version. All you need is to: \n- use ```KeyedJSONEncoder``` \\ ```KeyedJSONDecoder``` instead of ```JSONEncoder``` \\ ```JSONDecoder``` !!\n- change you CodingKeys to ```KeyedKey``` and move your mappings here\n- remove ```KeyedCodable``` protocol\n- remove constructor and map method\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdgrzeszczak%2Fkeyedcodable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdgrzeszczak%2Fkeyedcodable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdgrzeszczak%2Fkeyedcodable/lists"}