{"id":32151076,"url":"https://github.com/jarmourato/kodable","last_synced_at":"2025-10-21T10:35:00.929Z","repository":{"id":39718161,"uuid":"325054218","full_name":"JARMourato/Kodable","owner":"JARMourato","description":"A supercharged extension of Codable","archived":false,"fork":false,"pushed_at":"2025-04-11T17:35:02.000Z","size":166,"stargazers_count":85,"open_issues_count":0,"forks_count":9,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-10-01T11:15:05.989Z","etag":null,"topics":["codable","decodable","json","swift","swiftpackage"],"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/JARMourato.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-12-28T15:58:21.000Z","updated_at":"2025-04-11T17:34:06.000Z","dependencies_parsed_at":"2025-07-16T05:35:57.128Z","dependency_job_id":"02d41108-0401-4bf5-b094-ebe2f2f7f632","html_url":"https://github.com/JARMourato/Kodable","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/JARMourato/Kodable","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JARMourato%2FKodable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JARMourato%2FKodable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JARMourato%2FKodable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JARMourato%2FKodable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JARMourato","download_url":"https://codeload.github.com/JARMourato/Kodable/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JARMourato%2FKodable/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280246275,"owners_count":26297711,"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","json","swift","swiftpackage"],"created_at":"2025-10-21T10:34:55.968Z","updated_at":"2025-10-21T10:35:00.924Z","avatar_url":"https://github.com/JARMourato.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Kodable\n\n[![Build Status][build status badge]][build status]\n[![codebeat badge][codebeat status badge]][codebeat status]\n[![codecov][codecov status badge]][codecov status]\n![Platforms][platforms badge]\n\n`Kodable` is an extension of the `Codable` functionality through property wrappers. The main goal is to remove boilerplate while also adding useful functionality. \n\n**Features:**\n- No need to write your own `init(from decoder: Decoder)` or `CodingKeys` \n- Provide a custom key for decoding\n- Access nested values using the `.` notation\n- Add a default value in case the value is missing\n- Overriding the values decoded (i.e. trimming a string)\n- Validation of the values decoded\n- Automatically tries to decode `String` and `Bool` from other types as a fallback \n- Transformer protocol to implement your own additional functionality on top of the existing ones\n- Special error handling and better readability of Swift's `DecodingError`\n\n\nTable of contents\n=================\n\n\u003c!--ts--\u003e\n\n   * [Kodable](#kodable)\n   * [Table of contents](#table-of-contents)\n      * [Installation](#installation)\n         * [Swift Package Manager](#swift-package-manager)\n      * [Usage](#usage)\n         * [Provided Wrappers](#provided-wrappers)\n            * [Coding](#coding)\n            * [CodableDate](#codabledate)\n      * [Advanced Usage](#advanced-usage)\n         * [Lossy Type Decoding](#lossy-type-decoding)\n            * [Array](#array)\n            * [Bool](#bool)\n            * [String](#string)\n         * [Overriding Values](#overriding-values)\n         * [Validating Values](#validating-values)\n         * [Custom Wrapper](#custom-wrapper)\n         * [Encode null values](#encode-null-values)\n      * [Debugging](#debugging)\n      * [Contributions](#contributions)\n      * [License](#license)\n      * [Contact](#contact)\n\n\u003c!-- Added by: jarmourato, at: Mon Dec 28 12:22:31 WET 2020 --\u003e\n\n\u003c!--te--\u003e\n\n\n## Installation\n\n### Swift Package Manager\n\nIf you're working directly in a Package, add Kodable to your Package.swift file\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/JARMourato/Kodable.git\", .upToNextMajor(from: \"1.1.0\")),\n]\n```\n\nIf working in an Xcode project select `File-\u003eSwift Packages-\u003eAdd Package Dependency...` and search for the package name: `Kodable` or the git url:\n\n`https://github.com/JARMourato/Kodable.git`\n\n\n## Usage\n\n\n### Provided Wrappers\n\n#### Coding\n\nJust make your type conform to `Kodable` and you'll have access to all of the features `Coding` brings. \nYou can mix and match `Codable` values with `Coding` properties. \n\nDeclare your model:\n\n```Swift\nstruct User: Kodable {\n    var identifier: String = \"\"\n    var social: String?\n    @Coding(\"first_name\") var firstName: String\n    @Coding(default: \"+1 123456789\") var phone: String\n    @Coding(\"address.zipCode\") var zipCode: Int\n}\n\n// Instead of\n\nstruct CodableUser: Codable {\n\n    enum Keys: String, CodingKey {\n        case identifier, social, firstName = \"first_name\", phone, address\n    }\n\n    enum NestedKeys: String, CodingKey {\n        case zipCode\n    }\n\n    var identifier: String = \"\"\n    var social: String?\n    var firstName: String\n    var phone: String\n    var zipCode: Int\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: Keys.self)\n        identifier = try container.decode(String.self, forKey: .identifier)\n        social = try container.decodeIfPresent(String.self, forKey: .social)\n        firstName = try container.decode(String.self, forKey: .firstName)\n        phone = try container.decodeIfPresent(String.self, forKey: .phone) ?? \"+1 123456789\"\n        let addressContainer = try container.nestedContainer(keyedBy: NestedKeys.self, forKey: .address)\n        zipCode = try addressContainer.decode(Int.self, forKey: .zip)\n    }\n}\n```\n\nThen\n\n```Swift\nlet json = \"\"\"\n{\n    identifier: \"1\",\n    \"social\": 987654321,\n    \"first_name\": John,\n    \"address\": {\n        \"zipCode\": 94040,\n    }\n}\n\"\"\".data(using: .utf8)!\n\nlet result = try JSONDecoder().decode(User.self, from: json)\n\n// or using the provided syntactic sugar\n\nlet user = try User.decode(from: json)\n```\n\n#### CodableDate\n\nThis wrapper allows decoding dates on per-property strategy basis. By default, `CodableDate` uses the `iso8601` strategy. The built-in strategies are: \n`iso8601`, `iso8601WithMillisecondPrecision`, `rfc2822`, `rfc3339`, and `timestamp`. There is also the option of using a custom format by providing a valid string format to the option `.format()`.\n\n```Swift\nstruct Dates: Kodable {\n    @CodableDate var iso8601: Date\n    @CodableDate(.format(\"y-MM-dd\"), .key(\"simple_date\")) var simpleDate: Date\n    @CodableDate(.rfc2822, .key(\"rfc2822\")) var rfc2822Date: Date\n    @CodableDate(.rfc3339, .key(\"rfc3339\")) var rfc3339Date: Date\n    @CodableDate(.timestamp, .key(\"timestamp\")) var timestamp: Date\n}\n\nlet json = \"\"\"\n{\n    \"iso8601\": \"1996-12-19T16:39:57-08:00\",\n    \"simple_date\": \"2001-01-01\",\n    \"rfc2822\": \"Thu, 19 Dec 1996 16:39:57 GMT\",\n    \"rfc3339\": \"1996-12-19T16:39:57-08:00\",\n    \"timestamp\": 978307200.0,\n}\n\"\"\".data(using: .utf8)!\n\nlet dates = Dates.decode(from: json)\nprint(dates.iso8601.description) // Prints \"1996-12-20 00:39:57 +0000\"\nprint(dates.simpleDate.description) // Prints \"2001-01-01 00:00:00 +0000\"\nprint(dates.rfc2822Date.description) // Prints \"1996-12-19 16:39:57 +0000\"\nprint(dates.rfc3339Date.description) // Prints \"1996-12-20 00:39:57 +0000\"\nprint(dates.timestamp.description) // Prints \"2001-01-01 00:00:00 +0000\"\n````\n\nNote that there's no built-in support for ISO8601 dates with precision greater than millisecond (e.g. microsecond or nanosecond), because Apple doesn't officially supports such precision natively, yet. Should you feel the necessity to have those, or any other custom date formatter, you can implement your own `DateConvertible` and use `.custom(dateConvertible)` DateCodingStrategy. If you think your use case should make its way into the official library, PRs are always welcome!\n\n## Advanced Usage\n\n\n### Lossy Type Decoding\n\nFor the types `Array`,  `Bool`, and  `String`, some lossy decoding was introduced. More types can be added later on, but for now these sufficed my personal usage. To disable this behavior for a specific property, in case you want decoding to fail when the type is not correct, just provide the  `enforceTypeDecoding` option to the  `Coding` property wrapper. \n\n#### Array\n\nThe lossy decoding on `Array` is done by trying to decode each element from a `Array.Element` type in a non-lossy way (even if they are `Bool` or `String`) and ignores values that fail decoding. \n\n```Swift\nstruct LossyArray: Kodable {\n    @Coding(\"failable_array\", .lossy) var array: [String]\n}\n\nlet json = \"\"\"\n{\n    \"failable_array\": [ \"1\", 1.5, \"2\", true, \"3\", null, 4 ]\n}\n\"\"\".data(using: .utf8)!\n\nlet lossy = try LossyArray.decode(from: json)\nprint(lossy.array) // Prints [ \"1\", \"2\", \"3\" ]\n```\n\n#### Bool\n\nTries to decode a `Bool` from `Int` or `String` if `Bool` fails\n\n```Swift\nstruct Fail: Kodable {\n    @Coding(\"string_bool\", .enforceTypeDecoding) var notBool: Bool\n}\n\nstruct Success: Kodable {\n    @Coding(\"string_bool\") var stringBool: Bool\n    @Coding(\"int_bool\") var intBool: Bool\n}\n\nlet json = \"\"\"\n{\n    \"string_bool\": \"false\",\n    \"int_bool\": 1,\n}\n\"\"\".data(using: .utf8)!\n\nlet success = try Success.decode(from: json)\nprint(success.stringBool) // prints false\nprint(success.intBool) // prints true\n\nlet fail = try Fail.decode(from: json) // Throws KodableError.invalidValueForPropertyWithKey(\"string_bool\")\n```\n\n#### String\n\nTries to decode a `String` from `Double` or `Int` if `String` fails\n\n```Swift\nstruct Amounts: Kodable {\n    @Coding(\"double\") var double: String\n    @Coding(\"int\") var integer: String\n    @Coding var string: String\n}\n\nlet json = \"\"\"\n{\n    \"double\": 629.9,\n    \"int\": 1563,\n    \"string\": \"999.9\"\n}\n\"\"\".data(using: .utf8)!\n\nlet amounts = try Amounts.decode(from: json)\nprint(amounts.double) // prints \"629.9\"\nprint(amounts.integer) // prints \"1563\"\nprint(amounts.string) // prints \"999.9\"\n```\n\n\n### Overriding Values\n\nYou can provide a `KodableModifier.custom` modifier with an overriding closure so that you can modify the decoded value before assigning it to the property. \n\n```Swift\nstruct Project: Kodable {\n    @Coding(.modifier(Project.trimmed)) var title: String\n    \n    static var trimmed: KodableModifier\u003cString\u003e { \n        KodableModifier { $0.trimmingCharacters(in: .whitespacesAndNewlines) } \n    }\n}\n\nlet json = #\"{ \"title\": \"  A New Project    \" }\"#.data(using: .utf8)!\n\nlet project = try Project.decode(from: json)\nprint(project.title) // Prints \"A New Project\"\n````\n\nThere are a few built in modifiers provided already: \n\n**String**\n- `trimmed` : Applies `trimmingCharacters(in: .whitespacesAndNewlines)` to the value decoded\n\n**String?** \n- `trimmed` : Applies `trimmingCharacters(in: .whitespacesAndNewlines)` to the value decoded\n- `trimmedNifIfEmpty` : Applies `trimmingCharacters(in: .whitespacesAndNewlines)` to the value decoded, returns nif if empty\n\n**Sorting**\nWhen the type conforms to the `Comparable` protocol:\n\n- `ascending` or `descending`: Sorts the elements of an array in ascending (or descending) order, using the type's underlying comparison function.\n\nWhen the type doesn't conform to the `Comparable` protocol, but one of its properties does:\n\n- `ascending(by: KeyPath)` or `descending(by: KeyPath)`: Sorts the elements of an array in ascending (or descending) order, based on the _KeyPath_ property passed.\n\nIf there's no conformance to `Comparable` at all, you can resort to basic sorting functionality:\n- `sorted(using: Comparator)`: Sorts the elements of an array using a _Comparator_ closure that determines whether the items are in increasing order. In Swifty words: `func \u003c (lhs: Value.Element, rhs: Value.Element) -\u003e Bool`\n\n**Comparable**\n- `clamping(to:)` : Clamps the value in a range.\n- `range()` : Constrains the value inside a provided range.\n- `max()` :  Constrains the value to a maximum value.\n- `min()` :  Constrains the value to a minimum value.\n\n\n### Validating Values\n\nYou can provide a `KodableModifier.validation` modifier with a validation closure, where you can verify if the value is valid. \n\n```Swift\nstruct Image: Kodable {\n    @Coding(.validation({ $0 \u003e 500 })) var width: Int\n}\n\nlet json = #{ \"width\": 400 }#.data(using: .utf8)!\n\nlet image = try Image.decode(from: json)\n// Throws KodableError.validationFailed(property: \"width\", parsedValue: 400)\n````\n\n### Custom Wrapper\n\n`Kodable` was built based on a protocol called `KodableTransform`\n\n```Swift\npublic protocol KodableTransform {\n    associatedtype From: Codable\n    associatedtype To\n    func transformFromJSON(value: From) throws -\u003e To\n    func transformToJSON(value: To) throws -\u003e From\n    init()\n}\n```\n\nIf you want to add your own custom behavior, you can create a type that conforms to the `KodableTransform` protocol.:\n\n```Swift\nstruct URLTransformer: KodableTransform {\n    \n    enum Error: Swift.Error {\n        case failedToCreateURL\n    }\n\n    func transformFromJSON(value: String) throws -\u003e URL {\n        guard let url = URL(string: value) else { throw Error.failedToCreateURL }\n        return url\n    }\n    \n    func transformToJSON(value: URL) throws -\u003e String {\n        value.absoluteString\n    }\n}\n```\n\nThen use the `KodableTrasformable` property wrapper, upon which all other wrappers are based: \n\n```Swift \ntypealias CodingURL = KodableTransformable\u003cURLTransformer\u003e\n```\n\nAnd voilà\n\n```Swift\nstruct Test: Kodable {\n    @CodingURL(\"html_url\") var url: URL\n}\n```\n\n### Encode Null Values\n\nBy default optional values won't be encoded so: \n\n```swift\nstruct User: Kodable {\n    @Coding var firstName: String\n    @Coding var lastName: String?\n}\n\nlet user = User()\nuser.firstName = \"João\"\n```\n\nWhen encoded will output: \n\n```js\n{\n    \"firstName\": \"João\"\n}\n```\n\nHowever, if you want to explicitly encode null values, then you can add the `encodeAsNullIfNil` option: \n\n```swift\nstruct User: Kodable {\n    @Coding var firstName: String\n    @Coding(.encodeAsNullIfNil) var lastName: String?\n}\n\nlet user = User()\nuser.firstName = \"João\"\n```\n\nWhich will then output: \n\n```js\n{\n    \"firstName\": \"João\",\n    \"lastName\": null\n}\n```\n\n## Debugging\n\nWhile developing it might be useful to know what JSON is being received, so that we can be sure that the options chosen lead to correct decoding. There are several ways to do this, however, for simplicity sake, Kodable provides a simple way to print the JSON value received. \n\nLet's take for example the following JSON and Kodable models:\n\n```swift\n{\n    \"identifier\": \"1\",\n    \"social\": 987654321,\n    \"first_name\": \"John\",\n    \"address\": {\n        \"zipCode\": 94040,\n        \"state\": \"CA\"\n    },\n    \"aliases\": [ \"Jay\", \"Doe\" ]\n}\n\nstruct Address: Codable {\n    let zipCode: Int\n    let state: String \n}\n\nstruct User: Kodable {\n    var identifier: String = \"\"\n    var social: String?\n    @Coding(\"first_name\") var firstName: String\n    @Coding(default: \"+1 123456789\") var phone: String\n    @Coding var address: Address\n}\n```\n\nKodable provides 2 ways to debug the JSON that will be used to decode the `User` model. The first is to check the whole JSON value for the model. To achieve that, conform the model to the `DebugJSON` protocol: \n\n```swift\nstruct User: Kodable, DebugJSON {\n    /.../\n}\n```\n\nWhenever an instance of the `User` model is decoded you'll get the following message in the console\n\n```js\nDecoded JSON for type User:\n{\n    \"identifier\": \"1\",\n    \"social\": 987654321,\n    \"first_name\": \"John\",\n    \"address\": {\n        \"zipCode\": 94040,\n        \"state\": \"CA\"\n    },\n    \"aliases\": [ \"Jay\", \"Doe\" ]\n}\n```\n\nHowever, sometimes the model can be quite extensive and you're only interested in a specific nested model. In that case, there is a second option which is to mark only the property you want with the option `.debugJSON`: \n\n```Swift\nstruct User: Kodable {\n    var identifier: String = \"\"\n    var social: String?\n    @Coding(\"first_name\") var firstName: String\n    @Coding(default: \"+1 123456789\") var phone: String\n    @Coding(.debugJSON) var address: Address\n}\n```\n\nIn which case, for every instance of the `User` model that is decoded, you'll get the following message in the console:\n\n```js\nDecoded JSON for the address property of type User:\n{\n    \"zipCode\": 94040,\n    \"state\": \"CA\"\n}\n```\n\n## Contributions\n\nIf you feel like something is missing or you want to add any new functionality, please open an issue requesting it and/or submit a pull request with passing tests 🙌\n\n## License\n\nMIT\n\n## Special thanks to\n\nBetter Decoding Error Messages - via [@nunogoncalves](https://gist.github.com/nunogoncalves/4852077f4e576872f72b70d9e79942f3)\n\n## Contact\n\nJoão ([@_JARMourato](https://twitter.com/_JARMourato))\n\n[build status]: https://github.com/JARMourato/Kodable/actions?query=workflow%3ACI\n[build status badge]: https://github.com/JARMourato/Kodable/workflows/CI/badge.svg\n[codebeat status]: https://codebeat.co/projects/github-com-jarmourato-kodable-main\n[codebeat status badge]: https://codebeat.co/badges/5b666fbb-93ee-41ca-92ab-da7d5a8681ce\n[codecov status]: https://codecov.io/gh/JARMourato/Kodable\n[codecov status badge]: https://codecov.io/gh/JARMourato/Kodable/branch/main/graph/badge.svg?token=XAHCCI1JNM\n[platforms badge]: https://img.shields.io/static/v1?label=Platforms\u0026message=iOS%20|%20macOS%20|%20tvOS%20|%20watchOS%20\u0026color=brightgreen\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjarmourato%2Fkodable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjarmourato%2Fkodable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjarmourato%2Fkodable/lists"}