{"id":17160604,"url":"https://github.com/mikhailmaslo/macro-codable-kit","last_synced_at":"2025-03-22T18:33:37.006Z","repository":{"id":198332416,"uuid":"694841603","full_name":"mikhailmaslo/macro-codable-kit","owner":"mikhailmaslo","description":"Efficient, flexible Codable with Swift Macros","archived":false,"fork":false,"pushed_at":"2024-12-15T20:39:07.000Z","size":177,"stargazers_count":38,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-18T13:51:26.636Z","etag":null,"topics":["codable","macros","openapi","swift-package-manager"],"latest_commit_sha":null,"homepage":"https://swiftpackageindex.com/mikhailmaslo/macro-codable-kit/0.3.0/documentation/macrocodablekit","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/mikhailmaslo.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}},"created_at":"2023-09-21T20:04:05.000Z","updated_at":"2025-02-22T13:29:11.000Z","dependencies_parsed_at":null,"dependency_job_id":"3becda36-7bcd-4d8c-bebf-b7d14bbc9b54","html_url":"https://github.com/mikhailmaslo/macro-codable-kit","commit_stats":null,"previous_names":["mikhailmaslo/macro-codable-kit"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikhailmaslo%2Fmacro-codable-kit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikhailmaslo%2Fmacro-codable-kit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikhailmaslo%2Fmacro-codable-kit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikhailmaslo%2Fmacro-codable-kit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mikhailmaslo","download_url":"https://codeload.github.com/mikhailmaslo/macro-codable-kit/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245003358,"owners_count":20545598,"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":["codable","macros","openapi","swift-package-manager"],"created_at":"2024-10-14T22:25:22.965Z","updated_at":"2025-03-22T18:33:36.624Z","avatar_url":"https://github.com/mikhailmaslo.png","language":"Swift","readme":"# MacroCodableKit\n\n**MacroCodableKit** enhances your `Codable` experience in Swift, leveraging macros to generate precise and efficient code with zero additional memory allocations, thanks to the usage of pure (static) functions. It's a comprehensive solution providing support for `AllOf`, `OneOf`, and customizable `CodingKeys`, extending the native Codable capabilities to keep up with OpenAPI specification seamlessly.\n\n## Table of Contents\n\n- [MacroCodableKit](#macrocodablekit)\n- [Table of Contents](#table-of-contents)\n- [Motivation](#motivation)\n- [Features](#features)\n- [Usage](#usage)\n  - [Basics - @Codable](#basics---codable)\n  - [@AllOfCodable](#allofcodable)\n  - [@OneOfCodable](#oneofcodable)\n  - [Annotations](#annotations)\n    - [@CodingKey](#codingkey)\n    - [@OmitCoding](#omitcoding)\n    - [@DefaultValue](#defaultvalue)\n    - [@ValueCodable](#valuecodable)\n    - [@CustomCoding](#customcoding)\n- [Installation](#installation)\n  - [Swift Package Manager](#swift-package-manager)\n- [Known Limitations](#known-limitations)\n- [Acknowledgments](#acknowledgments)\n\n## Motivation\n\nCodable with property wrappers is an alternative way to approach `Codable`, it's quite a flexible approach and surely greatly reduces amount of boilerplate, at the same time it has its limitations\n\n- You can't implement things like `OneOf` or `AllOf` from [OpenAPI](https://spec.openapis.org/oas/v3.1.0) which is quite common in nowdays APIs\n- You can't alter `CodingKeys` with `@propertyWrapper`\n- `@propertyWrapper` is an additional struct next to a property, so you might endup with twice as much allocations then needed\n- `@propertyWrapper` should be settable, so you can't use `let` and you'll probably endup with `private(set)`\n\u003e There is a [proposal](https://github.com/apple/swift-evolution/pull/1910) though to allow `let` for `@propertyWrappers` which I hope will be eventually completed\n\nApproaching `Codable` with [Macros](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/macros/) allows zero additional allocation, immutable properties, and the implementation of any desirable Codable strategy inside one tool - **MacroCodableKit**, which provides `OneOf` and `AllOf` coding implementations from OpenAPI spec, and allows `CodingKeys` altering via annotations.\n\n## Features\n\n- **OpenAPI Compatibility**: Embrace OpenAPI specifications with [@OneOfCodable](#oneofcodable), [@AllOfCodable](#allofcodable) with ease.\n- **Custom Coding Keys**: Specify custom coding keys with `CodingKey(_ key: String)` annotation.\n- **Skip Coding of a Property**: Opt to ignore certain properties from being coded with `OmitCoding`.\n- **Default Values and Strategies**: Use [@DefaultValue](#defaultvalue) to handle failed decoding gracefully or [@ValueStrategy](#valuestrategy) to decode values your way, such as handling dates in various formats.\n- **Custom Coding Strategies**: Utilize [@CustomCoding](#customcoding) to define your encoding and decoding logic. Use build-in `SafeDecoding` to handle arrays and dictionaries in a safe manner.\n- **Error Handling**: Handle ignored decoding errors with `CustomCodingDecoding.errorHandler` \u0026 encoding errors with `CustomCodingEncoding.errorHandler`.\n- **Zero additional allocations**: Coding is done by predefined clean (static) functions, so there's no need to allocate additional memory\n\n... and more!\n\n## Usage\n\n### Basics - @Codable\n\nAnnotate a struct with `@Codable`, without additional annotations on properties it will generate default `Codable` conformance\n\n\u003e **Note**\n\u003e Do not conform to `Codable` protocol yourself, it will prevent macro from generating code\n\n```swift\n@Codable\nstruct User {\n    let birthday: Double\n    let name: String\n    let isVerified: Bool\n}\n```\n\nLet's convert `birthday` to `Date`, change coding key of `isVerified` and make it default to `false`\n\n\u003e **Note**\n\u003e Conform only to `@Decodable` if you don't need encoding\n\n```swift\n@Decodable\nstruct User {\n    @ValueStrategy(ISO8601Default)\n    let birthday: Date\n    let name: String\n\n    @CodingKey(\"is_verified\")\n    @DefaultValue(BoolFalse)\n    let isVerified: Bool\n}\n\n// json: { \"birthday\": 1696291200.0, \"name\": \"Mikhail\" }\n// is_verified is not specified, so the default value is \"false\" as specified by `@DefaultValue`\n```\n\n### @AllOfCodable\n\n@AllOfCodable describes [OpenAPI AllOf](https://spec.openapis.org/oas/v3.1.0#composition-and-inheritance-polymorphism) relationship\n\nImagine you have `SocialUser` OpenAPI specification which inherits from `User` and have additional properties\n\n```yaml\nSocialUser:\n  allOf:\n    - $ref: '#components/schema/User'\n    - type: object\n      properties:\n        username:\n          type: string\n        isPublic:\n          type: boolean\n```\n\nIn Swift code it could be implemented with just `AllOfCodable` annotation\n```swift\n@AllOfCodable\nstruct SocialUser {\n  struct Properties: Codable {\n    let isPublic: Bool\n    let username: String\n  }\n  let user: User\n  let additionalProperties: Properties\n}\n```\n\n### @OneOfCodable\n\n`@OneOfCodable` describes [OpenAPI OneOf](https://spec.openapis.org/oas/v3.1.0#fixed-fields-20) relationship\n\n\u003e **Note**\n\u003e Only one associated value is expected in each enum case\n\n```swift\n@OneOfCodable\nenum PaymentMethod {\n  case card(DebitCardPayload)\n  case applePay(payload: ApplePayPayload)\n  case sepa(_ payload: SepaPayload)\n}\n// valid jsons:\n// { \"card\": { ... DebitCardPayload ... }\n// { \"applePay\": { ... ApplePayPayload ... } }\n// { \"sepa\": { ... SepaPayload ... } }\n```\n\n### Annotations\n\n#### @CodingKey\n\nAnnotate a property with `@CodingKey(_ key: String)`, key will be used as CodingKey in decoding and encoding\n```swift\nstruct User {\n  @CodingKey(\"is_verified\")\n  let isVerified: Bool\n}\n```\n\n#### @OmitCoding\n\nSkip coding for a specific property with `@OmitCoding()` annotation\n\n```swift\nstruct User {\n  @OmitCoding()\n  let isVerified: Bool\n}\n```\n\nIt might be useful when you describe an object, where each encoded property is a part of a http request body\n```swift\n@Encodable\nstruct Request {\n  var endpoint: String { \"/user/\\(userID)/follow\" }\n\n  // We don't want to encode userID, since it's not part of the request body\n  @OmitCoding\n  let userID: String\n  \n  let isFollowing: Bool\n}\n```\n\n#### @DefaultValue\n\nUse `@DefaultValue\u003cProvider: DefaultValueProvider\u003e(_ type: Provider.Type)` to provide default value if decoding fails\n\n\u003e **Warning**\n\u003e `@DefaultValue(_:)` doesn't affect encoding\n\n```swift\n@Codable\nstruct User {\n  @DefaultValue(BoolFalse) // property will be `false`, if value is absent or decoding fails\n  let isVerified: Bool\n}\n```\n\nBuild-in presets:\n- **BoolTrue** - true by default, **BoolFalse** - false by default\n- **IntZero** - Int(0) by default\n- **DoubleZero** - Double(0) by default\n\n#### @ValueCodable\n\nUse `@ValueStrategy\u003cStrategy: ValueCodableStrategy\u003e(_ strategy: Strategy.Type)` to provide custom mapping\n\n```swift\n@Encodable\nstruct Upload {\n    @ValueStrategy(Base64Data)\n    let document: Data\n}\n```\n\nCan be combined with [DefaultValue](#DefaultValue)\n\n```swift\n@Decodable\nstruct Example {\n    @ValueStrategy(SomeStringStrategy)\n    @DefaultValue(EmptyString)\n    let string: String\n}\n```\n\nBuild-in presets\n- Dates:\n  - ISO8601Default - handles dates in ISO8601 format, for example, `2023-10-03T10:15:30+00:00`\n  - ISO8601WithFullDate - handles dates with the full date in ISO8601 format, for example, `2023-10-03`\n  - ISO8601WithFractionalSeconds - handles dates with fractional seconds in ISO8601 format, for example, `2023-10-03T10:15:30.123+00:00`\n  - YearMonthDayDate - handles dates with full date, example: `2023-10-03`\n  - RFC2822Date - handles dates in \"EEE, d MMM y HH:mm:ss zzz\" (RFC2822), example: `Tue, 3 Oct 2023 10:15:30 GMT`\n  - RFC3339Date - handles dates in \"yyyy-MM-dd'T'HH:mm:ssZ\" (RFC2822), example: `2023-10-03T10:15:30Z`\n  - TimestampedDate - converts timestamp either `String` or `Double` to `Date`, example: `1696291200.0`\n- Misc:\n  - Base64Data - converts base64 string to Data\n\n#### @CustomCoding\n\n`@CustomCoding` annotation allows specifying custom encoding and decoding strategies for properties. Attach it to a property and specify a type that contains the custom encoding/decoding logic. For instance, `@CustomCoding(SafeDecoding)` uses `safeDecoding` functions from `CustomCodingDecoding` and `CustomCodingEncoding` for handling arrays and dictionaries safely during encoding and decoding.\n\n```swift\n@Codable\nstruct TaggedPhotos: Equatable {\n    @CustomCoding(SafeDecoding)\n    var photos: [Photo]\n}\n\n@Codable\nstruct UserProfiles: Equatable {\n    @CustomCoding(SafeDecoding)\n    var profiles: [String: Profile]\n}\n\n// Corresponding JSON for TaggedPhotos with corrupted data\n// {\n//     \"photos\": [\n//         { \"url\": \"https://example.com/photo1.jpg\", \"tag\": \"vacation\" },\n//         { \"url\": \"https://example.com/photo2.jpg\", \"tag\": \"family\" },\n//         \"corruptedData\"\n//     ]\n// }\n\n// Corresponding JSON for UserProfiles with corrupted data\n// {\n//     \"profiles\": {\n//         \"john_doe\": { \"age\": 25, \"location\": \"NYC\" },\n//         \"jane_doe\": { \"age\": 28, \"location\": \"LA\" },\n//         \"corrupted_entry\": \"corruptedData\"\n//     }\n// }\n```\n\nIn the example above, `@CustomCoding(SafeDecoding)` will catch and forward any decoding errors caused by invalid decoding to `CustomCodingDecoding.errorHandler`, allowing the rest of the data to be decoded safely.\n\n## Installation\n\n### Swift Package Manager\n\n## Known Limitations\n\n- [@Codable](#basics---codable), [@AllOfCodable](#allofcodable) are only applicable to `struct` \n\n## Acknowledgments\n\n- **[swift-foundation](https://github.com/apple/swift-foundation)** by **Apple Swift Foundation Team** provided a profound understanding of `Decoder`, `Encoder`, and the handling of `encodeIfPresent` and `decodeIfPresent` functions which logic is required for the library such as **MacroCodableKit**.\n\n- **[BetterCodable](https://github.com/marksands/BetterCodable)** by [Mark Sands](https://github.com/marksands): This project has all the common and widespread use cases for `Codable`, which was adopted in **MacroCodableKit**. Especially [key conversion case](https://github.com/marksands/BetterCodable/pull/51) which turned out to be vital for `SafeDecoding` on dictionaries\n","funding_links":[],"categories":["Swift"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikhailmaslo%2Fmacro-codable-kit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmikhailmaslo%2Fmacro-codable-kit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikhailmaslo%2Fmacro-codable-kit/lists"}