{"id":9735,"date":"2025-06-30T16:35:00","date_gmt":"2025-06-30T13:35:00","guid":{"rendered":"https:\/\/handoli.com\/index.php\/2025\/06\/30\/decoding-swift-types-that-require-additional-data\/"},"modified":"2025-06-30T16:35:00","modified_gmt":"2025-06-30T13:35:00","slug":"decoding-swift-types-that-require-additional-data","status":"publish","type":"post","link":"https:\/\/handoli.com\/index.php\/2025\/06\/30\/decoding-swift-types-that-require-additional-data\/","title":{"rendered":"Decoding Swift types that require additional data"},"content":{"rendered":"<p>Swift\u2019s <code>Codable<\/code> API \u2014 which consists of the <code>Encodable<\/code> protocol for encoding, and <code>Decodable<\/code> for decoding \u2014 offers a powerful, built-in mechanism for converting native Swift types to and from a serialized format, such as JSON. Thanks to its integration with the Swift compiler, we often don\u2019t have to do any additional work to enable one of our types to become <code>Codable<\/code>, such as this <code>Movie<\/code> type:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">struct<\/span> Movie: <span class=\"s-type\">Identifiable<\/span>, <span class=\"s-type\">Codable<\/span> {\n    <span class=\"s-keyword\">let<\/span> id: <span class=\"s-type\">UUID<\/span>\n    <span class=\"s-keyword\">var<\/span> title: <span class=\"s-type\">String<\/span>\n    <span class=\"s-keyword\">var<\/span> releaseDate: <span class=\"s-type\">Date<\/span>\n    <span class=\"s-keyword\">var<\/span> genre: <span class=\"s-type\">Genre<\/span>\n    <span class=\"s-keyword\">var<\/span> directorName: <span class=\"s-type\">String<\/span>\n}<\/code><\/pre>\n<blockquote>\n<p>Just by adding that <code>Codable<\/code> conformance (which is a type alias for both <code>Encodable<\/code> and <code>Decodable<\/code>), our above <code>Movie<\/code> type can now be serialized and deserialized automatically, as long as the data format (such as JSON) that we\u2019re working with follows the same structure as our Swift type declaration.<\/p>\n<\/blockquote>\n<p>However, sometimes we might be working with a type that requires some <em>additional<\/em> data that\u2019s not present in the JSON (or whichever data format we\u2019re decoding from) in order to be initialized. For example, the following <code>User<\/code> type includes a <code>favorites<\/code> property \u2014 which is a <code>Favorites<\/code> value that contains the user\u2019s favorites, such as their favorite director and movie genre:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">struct<\/span> User: <span class=\"s-type\">Identifiable<\/span> {\n    <span class=\"s-keyword\">let<\/span> id: <span class=\"s-type\">UUID<\/span>\n    <span class=\"s-keyword\">var<\/span> name: <span class=\"s-type\">String<\/span>\n    <span class=\"s-keyword\">var<\/span> membershipPoints: <span class=\"s-type\">Int<\/span>\n    <span class=\"s-keyword\">var<\/span> favorites: <span class=\"s-type\">Favorites<\/span>\n}\n\n<span class=\"s-keyword\">struct<\/span> Favorites: <span class=\"s-type\">Codable<\/span> {\n    <span class=\"s-keyword\">var<\/span> genre: <span class=\"s-type\">Genre<\/span>\n    <span class=\"s-keyword\">var<\/span> directorName: <span class=\"s-type\">String<\/span>\n    <span class=\"s-keyword\">var<\/span> movieIDs: [<span class=\"s-type\">Movie<\/span>.<span class=\"s-type\">ID<\/span>]\n}<\/code><\/pre>\n<p>However, the JSON response that our app receives from the server when loading the data for a user doesn\u2019t include the <code>Favorites<\/code> data, which instead need to be loaded from a separate server endpoint:<\/p>\n<pre><code class=\"no-highlight\">\/\/ User server response:\n{\n    \"id\": \"7CBE0CC1-7779-42E9-AAF1-C4B145F3CAE9\",\n    \"name\": \"John Appleseed\",\n    \"membershipPoints\": 192\n}\n\n\/\/ Favorites server response:\n{\n    \"genre\": \"action\",\n    \"directorName\": \"Christopher Nolan\",\n    \"movieIDs\": [\n        \"F028CAB5-74D7-4B86-8450-D0046C32DFA0\",\n        \"D2657C95-1A35-446C-97D4-FAAA4783F2AA\",\n        \"5159AF60-DF61-4A0C-A6BA-AE0E027E2BC2\"\n    ]\n}<\/code><\/pre>\n<p>Now the question is, how do we make <code>User<\/code> conform to <code>Codable<\/code> (or more specifically, <code>Decodable<\/code>) without being able to decode the required <code>Favorites<\/code> data from the server\u2019s JSON response?<\/p>\n<p>One option would be to simply make the <code>favorites<\/code> property optional \u2014 but that would have several downsides. First, it would make our data model more fragile, as we could easily miss to populate that property when loading <code>User<\/code> values within various contexts (and the compiler wouldn\u2019t be able to warn us about it). Second, and arguably more important, is that we\u2019d constantly have to unwrap that optional <code>favorites<\/code> value every time we access it, leading to either extra boilerplate code (and potentially ambiguous states), or dangerous force unwrapping.<\/p>\n<p>Another, more robust option would be to use a secondary, <em>partial<\/em> model when decoding our <code>User<\/code> data, which we would then combine with a <code>Favorites<\/code> value in order to form our final model \u2014 like this:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">extension<\/span> <span class=\"s-type\">User<\/span> {\n    <span class=\"s-keyword\">struct<\/span> Partial: <span class=\"s-type\">Decodable<\/span> {\n        <span class=\"s-keyword\">let<\/span> id: <span class=\"s-type\">UUID<\/span>\n        <span class=\"s-keyword\">var<\/span> name: <span class=\"s-type\">String<\/span>\n        <span class=\"s-keyword\">var<\/span> membershipPoints: <span class=\"s-type\">Int<\/span>\n    }\n}\n\n<span class=\"s-keyword\">struct<\/span> Networking {\n    <span class=\"s-keyword\">var<\/span> session = <span class=\"s-type\">URLSession<\/span>.<span class=\"s-property\">shared<\/span>\n    ...\n\n    <span class=\"s-keyword\">func<\/span> loadUser(withID id: <span class=\"s-type\">User<\/span>.<span class=\"s-type\">ID<\/span>) <span class=\"s-keyword\">async throws<\/span> -&gt; <span class=\"s-type\">User<\/span> {\n        <span class=\"s-keyword\">let<\/span> favoritesURL = <span class=\"s-call\">favoritesURLForUser<\/span>(withID: id)\n        <span class=\"s-keyword\">let<\/span> userURL = <span class=\"s-call\">urlForUser<\/span>(withID: id)\n\n        <span class=\"s-comment\">\/\/ Load the user's favorites and the partial user data\n        \/\/ that our server responds with:<\/span>\n        <span class=\"s-keyword\">async let<\/span> favorites = <span class=\"s-call\">request<\/span>(favoritesURL) <span class=\"s-keyword\">as<\/span> <span class=\"s-type\">Favorites<\/span>\n        <span class=\"s-keyword\">async let<\/span> partialUser = <span class=\"s-call\">request<\/span>(userURL) <span class=\"s-keyword\">as<\/span> <span class=\"s-type\">User<\/span>.<span class=\"s-type\">Partial<\/span>\n\n        <span class=\"s-comment\">\/\/ Form our final user model by combining the partial\n        \/\/ model with the favorites that were loaded:<\/span>\n        <span class=\"s-keyword\">return try await<\/span> <span class=\"s-type\">User<\/span>(\n            id: partialUser.<span class=\"s-property\">id<\/span>,\n            name: partialUser.<span class=\"s-property\">name<\/span>,\n            membershipPoints: partialUser.<span class=\"s-property\">membershipPoints<\/span>,\n            favorites: favorites\n        )\n    }\n    \n    ...\n\n    <span class=\"s-keyword\">private func<\/span> request&lt;T: <span class=\"s-type\">Decodable<\/span>&gt;(<span class=\"s-keyword\">_<\/span> url: <span class=\"s-type\">URL<\/span>) <span class=\"s-keyword\">async throws<\/span> -&gt; <span class=\"s-type\">T<\/span> {\n        <span class=\"s-keyword\">let<\/span> (data, <span class=\"s-keyword\">_<\/span>) = <span class=\"s-keyword\">try await<\/span> session.<span class=\"s-call\">data<\/span>(from: url)\n        <span class=\"s-keyword\">return try<\/span> <span class=\"s-type\">JSONDecoder<\/span>().<span class=\"s-call\">decode<\/span>(<span class=\"s-type\">T<\/span>.<span class=\"s-keyword\">self<\/span>, from: data)\n    }\n}<\/code><\/pre>\n<p>While the above works perfectly fine, it would be really nice to find a solution that doesn\u2019t require us to duplicate all of our <code>User<\/code> model\u2019s properties by declaring a separate, decoding-specific <code>Partial<\/code> model. Thankfully, the Swift Codable system* does actually include such a solution \u2014 the somewhat lesser known <code>CodableWithConfiguration<\/code> API.<\/p>\n<blockquote>\n<p>* CodableWithConfiguration is not technically a direct part of Codable, which is defined within Swift\u2019s standard library, but is instead an extension defined within Foundation. That doesn\u2019t make much of a difference when targeting any of Apple\u2019s platforms, though.<\/p>\n<\/blockquote>\n<p>When a type conforms to either <code>EncodableWithConfiguration<\/code> or <code>DecodableWithConfiguration<\/code>, it requires an additional configuration value to be passed when either encoding or decoding it (and the compiler will enforce that requirement). That\u2019s incredibly useful in situations such as when decoding our <code>User<\/code> type, since we can define that <code>Favorites<\/code> is the required <code>DecodingConfiguration<\/code> for our type \u2014 meaning that we can ensure that such a value will always be present during decoding, without having to declare any additional partial types.<\/p>\n<p>So let\u2019s go ahead and update our <code>User<\/code> type to conform to <code>DecodableWithConfiguration<\/code>, which does require a manual decoding implementation, unfortunately:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">extension<\/span> <span class=\"s-type\">User<\/span>: <span class=\"s-type\">Encodable<\/span>, <span class=\"s-type\">DecodableWithConfiguration<\/span> {\n    <span class=\"s-keyword\">enum<\/span> CodingKeys: <span class=\"s-type\">CodingKey<\/span> {\n        <span class=\"s-keyword\">case<\/span> id\n        <span class=\"s-keyword\">case<\/span> name\n        <span class=\"s-keyword\">case<\/span> membershipPoints\n    }\n\n    <span class=\"s-keyword\">init<\/span>(from decoder: <span class=\"s-type\">Decoder<\/span>, configuration: <span class=\"s-type\">Favorites<\/span>) <span class=\"s-keyword\">throws<\/span> {\n        <span class=\"s-keyword\">let<\/span> container = <span class=\"s-keyword\">try<\/span> decoder.<span class=\"s-call\">container<\/span>(keyedBy: <span class=\"s-type\">CodingKeys<\/span>.<span class=\"s-keyword\">self<\/span>)\n\n        id = <span class=\"s-keyword\">try<\/span> container.<span class=\"s-call\">decode<\/span>(<span class=\"s-type\">UUID<\/span>.<span class=\"s-keyword\">self<\/span>, forKey: .<span class=\"s-dotAccess\">id<\/span>)\n        name = <span class=\"s-keyword\">try<\/span> container.<span class=\"s-call\">decode<\/span>(<span class=\"s-type\">String<\/span>.<span class=\"s-keyword\">self<\/span>, forKey: .<span class=\"s-dotAccess\">name<\/span>)\n        membershipPoints = <span class=\"s-keyword\">try<\/span> container.<span class=\"s-call\">decode<\/span>(\n            <span class=\"s-type\">Int<\/span>.<span class=\"s-keyword\">self<\/span>,\n            forKey: .<span class=\"s-dotAccess\">membershipPoints<\/span>\n        )\n        favorites = configuration\n    }\n}<\/code><\/pre>\n<p>So we still have to write a bit of boilerplate in order to enable our new decoding setup, but the advantage is that we can now make our networking code a lot simpler \u2014 all that we need is another overload of our private <code>request<\/code> method, which works with types conforming to <code>DecodableWithConfiguration<\/code>, and we\u2019ll now be able to leverage type inference to make our decoding call site a lot simpler:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">struct<\/span> Networking {\n    ...\n\n    <span class=\"s-keyword\">func<\/span> loadUser(withID id: <span class=\"s-type\">User<\/span>.<span class=\"s-type\">ID<\/span>) <span class=\"s-keyword\">async throws<\/span> -&gt; <span class=\"s-type\">User<\/span> {\n        <span class=\"s-keyword\">let<\/span> favoritesURL = <span class=\"s-call\">favoritesURLForUser<\/span>(withID: id)\n        <span class=\"s-keyword\">let<\/span> userURL = <span class=\"s-call\">urlForUser<\/span>(withID: id)\n\n        <span class=\"s-keyword\">return try await<\/span> <span class=\"s-call\">request<\/span>(userURL, with: <span class=\"s-call\">request<\/span>(favoritesURL))\n    }\n    \n    ...\n\n    <span class=\"s-keyword\">private func<\/span> request&lt;T: <span class=\"s-type\">Decodable<\/span>&gt;(<span class=\"s-keyword\">_<\/span> url: <span class=\"s-type\">URL<\/span>) <span class=\"s-keyword\">async throws<\/span> -&gt; <span class=\"s-type\">T<\/span> {\n        ...\n    }\n\n    <span class=\"s-keyword\">private func<\/span> request&lt;T: <span class=\"s-type\">DecodableWithConfiguration<\/span>&gt;(\n        <span class=\"s-keyword\">_<\/span> url: <span class=\"s-type\">URL<\/span>,\n        with config: <span class=\"s-type\">T<\/span>.<span class=\"s-type\">DecodingConfiguration<\/span>\n    ) <span class=\"s-keyword\">async throws<\/span> -&gt; <span class=\"s-type\">T<\/span> {\n        <span class=\"s-keyword\">let<\/span> (data, <span class=\"s-keyword\">_<\/span>) = <span class=\"s-keyword\">try await<\/span> session.<span class=\"s-call\">data<\/span>(from: url)\n\n        <span class=\"s-keyword\">return try<\/span> <span class=\"s-type\">JSONDecoder<\/span>().<span class=\"s-call\">decode<\/span>(\n            <span class=\"s-type\">T<\/span>.<span class=\"s-keyword\">self<\/span>,\n            from: data,\n            configuration: config\n        )\n    }\n}<\/code><\/pre>\n<p>However, one thing that\u2019s a bit puzzling about the <code>Codable WithConfiguration<\/code> API is that even though the protocol itself, as well as the <code>KeyedCodingContainer<\/code> methods that enable us to perform nested decoding of such types, are all available from iOS 15, the top-level configuration-compatible <code>JSONDecoder<\/code> API wasn\u2019t added until iOS 17.<\/p>\n<p>Thankfully, that\u2019s something that we can quite easily work around if working on a project that needs to support iOS 16 and earlier \u2014 by introducing our own implementation of that API, which uses Codable\u2019s <code>userInfo<\/code> mechanism to store the configuration of the value that we\u2019re currently decoding:<\/p>\n<pre class=\"splash\"><code><span class=\"s-keyword\">extension<\/span> <span class=\"s-type\">JSONDecoder<\/span> {\n    <span class=\"s-comment\">\/\/ First, we define a wrapper type which we'll use to decode\n    \/\/ values that require a configuration type:<\/span>\n    <span class=\"s-keyword\">private struct<\/span> ConfigurationDecodingWrapper&lt;\n        Wrapped: <span class=\"s-type\">DecodableWithConfiguration<\/span>\n    &gt;: <span class=\"s-type\">Decodable<\/span> {\n        <span class=\"s-keyword\">var<\/span> wrapped: <span class=\"s-type\">Wrapped<\/span>\n\n        <span class=\"s-keyword\">init<\/span>(from decoder: <span class=\"s-type\">Decoder<\/span>) <span class=\"s-keyword\">throws<\/span> {\n            <span class=\"s-keyword\">let<\/span> configuration = decoder.<span class=\"s-property\">userInfo<\/span>[configurationUserInfoKey]\n\n            wrapped = <span class=\"s-keyword\">try<\/span> <span class=\"s-type\">Wrapped<\/span>(\n                from: decoder,\n                configuration: configuration <span class=\"s-keyword\">as<\/span>! <span class=\"s-type\">Wrapped<\/span>.<span class=\"s-type\">DecodingConfiguration<\/span>\n            )\n        }\n    }\n\n    <span class=\"s-keyword\">private static let<\/span> configurationUserInfoKey = <span class=\"s-type\">CodingUserInfoKey<\/span>(\n        rawValue: <span class=\"s-string\">\"configuration\"<\/span>\n    )!\n\n    <span class=\"s-comment\">\/\/ Then, we declare our own decode method (which omits the\n    \/\/ type parameter in order to not conflict with the built-in\n    \/\/ API), which will work on iOS 15 and above:<\/span>\n    <span class=\"s-keyword\">func<\/span> decode&lt;T: <span class=\"s-type\">DecodableWithConfiguration<\/span>&gt;(\n        from data: <span class=\"s-type\">Data<\/span>,\n        configuration: <span class=\"s-type\">T<\/span>.<span class=\"s-type\">DecodingConfiguration<\/span>\n    ) <span class=\"s-keyword\">throws<\/span> -&gt; <span class=\"s-type\">T<\/span> {\n        <span class=\"s-keyword\">let<\/span> decoder = <span class=\"s-type\">JSONDecoder<\/span>()\n        decoder.<span class=\"s-property\">userInfo<\/span>[<span class=\"s-type\">Self<\/span>.<span class=\"s-property\">configurationUserInfoKey<\/span>] = configuration\n\n        <span class=\"s-keyword\">let<\/span> wrapper = <span class=\"s-keyword\">try<\/span> decoder.<span class=\"s-call\">decode<\/span>(\n            <span class=\"s-type\">ConfigurationDecodingWrapper<\/span>&lt;<span class=\"s-type\">T<\/span>&gt;.<span class=\"s-keyword\">self<\/span>,\n            from: data\n        )\n\n        <span class=\"s-keyword\">return<\/span> wrapper.<span class=\"s-property\">wrapped<\/span>\n    }\n}<\/code><\/pre>\n<p><code>CodableWithConfiguration<\/code> is really quite useful when using Swift\u2019s built-in serialization API to encode and decode types that require additional data in order to be initialized, without having to resort to modeling required data as optional, or having to define additional types that are only ever used for decoding purposes.<\/p>\n<p>I hope that you found this article useful. Feel free to reach out via either either <a href=\"https:\/\/mastodon.social\/@johnsundell\">Mastodon<\/a> or <a href=\"https:\/\/bsky.app\/profile\/johnsundell.bsky.social\">Bluesky<\/a> if you have any questions or feedback.<\/p>\n<p>Thanks for reading!<\/p>","protected":false},"excerpt":{"rendered":"<p>Swift\u2019s Codable API \u2014 which consists of the Encodable protocol for encoding, and Decodable for decoding \u2014 offers a powerful, built-in mechanism for converting native Swift types to and from a serialized format, such as JSON. Thanks to its integration with the Swift compiler, we often don\u2019t have to do any additional work to enable [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rop_custom_images_group":[],"rop_custom_messages_group":[],"rop_publish_now":"initial","rop_publish_now_accounts":[],"rop_publish_now_history":[],"rop_publish_now_status":"pending","footnotes":""},"categories":[1,15],"tags":[],"class_list":["post-9735","post","type-post","status-publish","format-standard","hentry","category-explore","category-world"],"_links":{"self":[{"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/posts\/9735","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/comments?post=9735"}],"version-history":[{"count":0,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/posts\/9735\/revisions"}],"wp:attachment":[{"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/media?parent=9735"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/categories?post=9735"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/handoli.com\/index.php\/wp-json\/wp\/v2\/tags?post=9735"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}