{"id":13535185,"url":"https://github.com/MPLew-is/deep-codable","last_synced_at":"2025-04-02T00:32:45.511Z","repository":{"id":44468669,"uuid":"512399125","full_name":"MPLew-is/deep-codable","owner":"MPLew-is","description":"Encode and decode deeply-nested data into flat Swift objects","archived":false,"fork":false,"pushed_at":"2022-07-26T08:28:30.000Z","size":51,"stargazers_count":103,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-29T03:14:56.174Z","etag":null,"topics":["codable","decoding","json","propertywrapper","result-builder","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MPLew-is.png","metadata":{"files":{"readme":"ReadMe.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-07-10T09:53:45.000Z","updated_at":"2024-11-15T23:36:42.000Z","dependencies_parsed_at":"2022-08-17T14:50:53.573Z","dependency_job_id":null,"html_url":"https://github.com/MPLew-is/deep-codable","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MPLew-is%2Fdeep-codable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MPLew-is%2Fdeep-codable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MPLew-is%2Fdeep-codable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MPLew-is%2Fdeep-codable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MPLew-is","download_url":"https://codeload.github.com/MPLew-is/deep-codable/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246735354,"owners_count":20825221,"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","decoding","json","propertywrapper","result-builder","swift"],"created_at":"2024-08-01T08:00:50.941Z","updated_at":"2025-04-02T00:32:45.235Z","avatar_url":"https://github.com/MPLew-is.png","language":"Swift","readme":"# `DeepCodable`: Encode and decode deeply-nested data into flat Swift objects #\n\nHave you ever gotten a response from an API that looked like this and wanted to pull out and flatten the values you care about?\n(This is a real response from the GitHub GraphQL API, with only the actual values changed)\n\n```json\n{\n\t\"data\": {\n\t\t\"node\": {\n\t\t\t\"content\": {\n\t\t\t\t\"__typename\": \"Example type\",\n\t\t\t\t\"title\": \"Example title\"\n\t\t\t},\n\t\t\t\"fieldValues\": {\n\t\t\t\t\"nodes\": [\n\t\t\t\t\t{},\n\t\t\t\t\t{},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"name\": \"Example node name\",\n\t\t\t\t\t\t\"field\": {\n\t\t\t\t\t\t\t\"name\": \"Example field name\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\n`DeepCodable` lets you easily do so in Swift while maintaining type-safety, with the magic of result builders, key paths, and property wrappers:\n\n```swift\nimport DeepCodable\n\nstruct GithubGraphqlResponse: DeepDecodable {\n\tstatic let codingTree = CodingTree {\n\t\tKey(\"data\") {\n\t\t\tKey(\"node\") {\n\t\t\t\tKey(\"content\") {\n\t\t\t\t\tKey(\"__typename\", containing: \\._type)\n\t\t\t\t\tKey(\"title\", containing: \\._title)\n\t\t\t\t}\n\n\t\t\t\tKey(\"fieldValues\") {\n\t\t\t\t\tKey(\"nodes\", containing: \\._nodes)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\n\tstruct Node: DeepDecodable {\n\t\tstatic let codingTree = CodingTree {\n\t\t\tKey(\"name\", containing: \\._name)\n\n\t\t\tKey(\"field\", \"name\", containing: \\._fieldName)\n\t\t\t/*\n\t\t\tThe above is a \"flattened\" shortcut for:\n\t\t\tKey(\"field\") {\n\t\t\t\tKey(\"name\", containing: \\._fieldName)\n\t\t\t}\n\t\t\t*/\n\t\t}\n\n\n\t\t@Value var name: String?\n\t\t@Value var fieldName: String?\n\t}\n\n\tenum TypeName: String, Decodable {\n\t\tcase example = \"Example type\"\n\t}\n\n\t@Value var title: String\n\t@Value var nodes: [Node]\n\t@Value var type: TypeName\n}\n\ndump(try JSONDecoder().decode(GithubGraphqlResponse.self, from: jsonData))\n```\n\n\n## Quick start ##\n\nAdd to your `Package.Swift`:\n```swift\n...\n\tdependencies: [\n\t\t...\n\t\t.package(url: \"https://github.com/MPLew-is/deep-codable\", branch: \"main\"),\n\t],\n\ttargets: [\n\t\t...\n\t\t.target(\n\t\t\t...\n\t\t\tdependencies: [\n\t\t\t\t...\n\t\t\t\t.product(name: \"DeepCodable\", package: \"deep-codable\"),\n\t\t\t]\n\t\t),\n\t\t...\n\t]\n]\n```\n\nConform a type you want to decode to `DeepDecodable` by defining a coding tree representing which nodes are bound to which values:\n```swift\nstruct DeeplyNestedResponse: DeepDecodable {\n\tstatic let codingTree = CodingTree {\n\t\tKey(\"topLevel\") {\n\t\t\tKey(\"secondLevel\") {\n\t\t\t\tKey(\"thirdLevel\", containing: \\._property)\n\t\t\t}\n\t\t}\n\t}\n\t/*\n\tAlso valid is the flattened form:\n\tstatic let codingTree = CodingTree {\n\t\tKey(\"topLevel\", \"secondLevel\", \"thirdLevel\", containing: \\._property)\n\t}\n\t*/\n\n\t@Value var property: String\n}\n/*\nCorresponding JSON would look like:\n{\n\t\"topLevel\": {\n\t\t\"secondLevel\": {\n\t\t\t\"thirdLevel: \"{some value}\"\n\t\t}\n\t}\n}\n*/\n```\n\nNodes in your `codingTree` are made of `Key`s initialized one of the following ways:\n\n- `Key(\"name\") { /* More Keys */ }`: node that don't capture values directly, but contain other nodes\n\t- This maps to a serialized representation like `{\"name\": { ... } }`\n\n- `Key(\"name\", containing: \\._value)`: node that should be decoded into the `value` property\n\nAll values to decode must be wrapped with the `@Value` property wrapper, and the `\\._{name}` syntax refers directly to the wrapping instance (`\\.{name}` without the underscore refers to the actual underlying value).\n\n\nDecode a value into an instance of your type:\n```swift\nlet instance = try JSONDecoder().decode(Response.self, from: jsonData)\n```\n\n`DeepCodable` is built on top of normal `Codable`, so any decoder (like [the property list decoder in `Foundation`](https://developer.apple.com/documentation/foundation/propertylistdecoder) or [the excellent third-party YAML decoder, Yams](https://github.com/jpsim/Yams)) can be used to decode values.\n\n\n## Encoding ##\n\nWhile decoding is probably the most common use-case for this type of nested decoding, this package also supports encoding a flat Swift struct into a deeply nested one with the same pattern:\n```swift\nstruct DeeplyNestedRequest: DeepEncodable {\n\tstatic let codingTree = CodingTree {\n\t\tKey(\"topLevel\") {\n\t\t\tKey(\"secondLevel\") {\n\t\t\t\tKey(\"thirdLevel\", containing: \\.bareProperty)\n\t\t\t}\n\n\t\t\tKey(\"otherSecondLevel\", containing: \\._wrappedProperty)\n\t\t}\n\t}\n\t/*\n\tAlso valid is the flattened form:\n\tstatic let codingTree = CodingTree {\n\t\tKey(\"topLevel\") {\n\t\t\tKey(\"secondLevel\", \"thirdLevel\", containing: \\.bareProperty)\n\n\t\t\tKey(\"otherSecondLevel\", containing: \\._wrappedProperty)\n\t\t}\n\t}\n\t*/\n\n\tlet bareProperty: String\n\t@Value var wrappedProperty: String\n}\n/*\nCorresponding JSON would look like:\n{\n\t\"topLevel\": {\n\t\t\"secondLevel\": {\n\t\t\t\"thirdLevel: \"{bareProperty}\"\n\t\t},\n\t\t\"otherSecondLevel\": \"{wrappedProperty}\"\n\t}\n}\n*/\n\nlet instance: DeeplyNestedRequest = ...\nlet jsonData = try JSONEncoder().encode(instance)\n```\n\nWith encoding, you don't have to use the `@Value` wrappers, though you can if you'd like to support decoding and encoding on the same type (in which case you can conform to `DeepCodable` as an alias for the two).\n\n\n## Key features ##\n\n- Encoding and decoding a Swift object to/from an arbitrarily complex deeply nested serialized representation without manually writing `Codable` implementations\n\n- Preservation of existing `Codable` behavior on the values being encoded/decoded, including custom types\n\t- Since `DeepCodable` is just a custom implementation of the `Codable` requirements, this also means you can nest `DeepCodable` objects like in the `GithubGraphqlResponse` example\n\n- When conforming to `DeepEncodable` or `DeepDecodable`, don't interfere with the opposite normal `Codable` implementation (`Decodable`/`Encodable`, respectively)\n\t- You can declare something like `struct Response: DeepDecodable, Encodable { ... }` and decode from a deeply nested tree, and then re-encode back to a flat structure like normal `Encodable` objects\n\n- No requirement for `@Value` property wrapper for types only conforming to `DeepEncodable`\n\n- Omission of the corresponding tree sections when all values at the leaves are `nil`\n\t- This makes it so trying to encode an object with a `nil` value doesn't result in something like `{\"top\": {\"second\": {\"third\": null} } }`\n\n- Flattened shortcuts using variadic parameters for long paths with no branching:\n\t- `Key(\"topLevel\", \"secondLevel\", containing: \\._property)` instead of `Key(\"topLevel\") { Key(\"secondLevel\", containing: \\._property) }`\n","funding_links":[],"categories":["Parsing"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMPLew-is%2Fdeep-codable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FMPLew-is%2Fdeep-codable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMPLew-is%2Fdeep-codable/lists"}