<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Decoder &#8211; 隨心所欲</title>
	<atom:link href="https://doeverythingiwant.com/tag/decoder/feed/" rel="self" type="application/rss+xml" />
	<link>https://doeverythingiwant.com</link>
	<description>iOS Developer 的隨筆記錄</description>
	<lastBuildDate>Sun, 08 Dec 2024 08:11:48 +0000</lastBuildDate>
	<language>zh-TW</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://doeverythingiwant.com/wp-content/uploads/2025/08/cropped-0802.png</url>
	<title>Decoder &#8211; 隨心所欲</title>
	<link>https://doeverythingiwant.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>[iOS] Codable 有關的那些小事情</title>
		<link>https://doeverythingiwant.com/codable-decoder/</link>
					<comments>https://doeverythingiwant.com/codable-decoder/#respond</comments>
		
		<dc:creator><![CDATA[艾普利]]></dc:creator>
		<pubDate>Sun, 08 Dec 2024 08:11:47 +0000</pubDate>
				<category><![CDATA[寫程式]]></category>
		<category><![CDATA[codable]]></category>
		<category><![CDATA[Decoder]]></category>
		<category><![CDATA[iOS]]></category>
		<guid isPermaLink="false">https://doeverythingiwant.com/?p=2372</guid>

					<description><![CDATA[說到 Codable 已經推出很久了，也用了好幾年了，但是對它還是不算是很了解，該怎麼說呢，因為Codable 最常運用在解析 API Response，基本上寫完之後，除非API 回傳值要改變，你大概到離職的那一刻都不會再去修改它了，所以即使它推出了很多年了，實際上接觸的時間意外地少。
最近正好有機會再次寫不少 Codable 的部分，發現了一些過去不知道的東西，把它們記錄在這裡。]]></description>
										<content:encoded><![CDATA[
<p class="has-text-align-center wp-block-paragraph">Photo by <a href="https://unsplash.com/@markusspiske?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank" rel="noopener">Markus Spiske</a> on <a href="https://unsplash.com/photos/matrix-movie-still-iar-afB0QQw?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank" rel="noopener">Unsplash</a></p>



<p class="wp-block-paragraph">說到 Codable 已經推出很久了，也用了好幾年了，但是對它還是不算是很了解，該怎麼說呢，因為Codable 最常運用在解析 API Response，基本上寫完之後，除非API 回傳值要改變，大概到離職的那一刻都不會再去修改它了，所以即使它推出了很多年了，實際上接觸的時間意外地少。</p>



<p class="wp-block-paragraph">最近正好有機會再次寫不少 Codable 的部分，發現了一些過去不知道的東西，把它們記錄在這裡。</p>



<span id="more-2372"></span>



<h2 class="wp-block-heading">CodingKey</h2>



<p class="wp-block-paragraph">最常使用在API 回傳值的Key 風格 不是 Swift 習慣的風格，做為對應解析之用</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>enum CodingKeys: String, CodingKey {
    case id = &quot;product_id&quot;
    case name
    case description
    case expiryDate = &quot;expiry_date&quot;
}</code></pre></div>



<p class="wp-block-paragraph">在API 回傳值裡的 <code>product_id</code> 就會被放到 <code>id</code>裡，這是非常好用的地方。</p>



<p class="wp-block-paragraph">另一個好用的地方在於也可以讓decoder只解析寫在 CodingKeys 裡的那些參數，舉個例</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>struct SubItem: Decodable,Identifiable {
   var id = UUID().string
   var name: String
   var description: String
   var isActive: Bool
}</code></pre></div>



<p class="wp-block-paragraph"><code>SubItem</code> conform Identifiable protocol ，因此有一個參數是<code>id</code> 且還是直接使用UUID，並不希望它被拿來當解析用的參數時，就可以使用 CodingKey來避開 <code>id</code> ，就不需要再寫 <code>init(from decoder: any Decoder)</code></p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>struct SubItem: Decodable,Identifiable {
   var id = UUID().string
   var name: String
   var description: String
   var isActive: Bool

   enum CodingKeys: String, CodingKey {
    case name
    case description
    case isActive 
  }
}</code></pre></div>



<h2 class="wp-block-heading">Try? &amp; decodeIfPresent()</h2>



<p class="wp-block-paragraph">基於因為不知道API 會回傳什麼，是否真的會依照文件回傳，為了讓App 不會因為回傳值問題需導至 Crash，一般而言，除非100%保証一定會有值的情況下，大多數參數都會被設定為 Optional，在解析的時候就會用上  <code>decodeIfPresent</code> ，當回傳值完全沒有這個參數，或是參數是nil(null)的時候，它會回傳 nil。</p>



<p class="wp-block-paragraph">一般情況下，這樣就幾乎不會有什麼問題，但是，就是這個但是，當回傳值是有值的時候，但偏偏回傳的資料型態是錯誤的，這樣的寫法就還是會解析失敗，導致整個物件都會是nil。</p>



<p class="wp-block-paragraph">如果你是覺得「為什麼會回傳資料型態不同的資料來啊? 這不合理啊」，沒錯，理解上不應該出現回傳值的資料有誤的情況，多數情況下都會要求修正。因為App 端對於資料型態相對來說是嚴格的，解析失敗最後就可能造成 Crash ，所以最好是大家都遵守好訂好的資料型態，不應該回傳不正確的型態。</p>



<p class="wp-block-paragraph">在有點歷史的產品裡或是一開始就沒有 App 端產品就可能會發生，回傳值會變成不預期的資料型態問題，且可能也不是馬上可以修正的情況，這種時候就需要先確認有問題的資料是不是需要用的，若是需要用的就必須要再另外處理，若是不需要用的資料，就是<code>try?</code> 出馬的時候了!</p>



<p class="wp-block-paragraph"><code>try?</code> 本來就是在解析失敗的時候會回傳nil ，本來比較常會和 <code>decode</code>() 一起使用，如果和 <code>decodeIfPresent</code> 一起使用，所有可以會造成問題的情況都會包含到，這樣就比較不用怕因為資料型態解析失敗問題，當然，前提是有問題的資料並不是會使用到的資料</p>



<p class="wp-block-paragraph">以下就是合體技的寫法</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>init(from decoder: any Decoder) throws {
   let container = try decoder.container(keyedBy: CodingKeys.self)
   id = try? container.decodeIfPresent(String.self, forKey: .id)
   name = try? container.decodeIfPresent(String.self, forKey: .name)
}</code></pre></div>



<h2 class="wp-block-heading">假如運氣特好遇到…</h2>



<p class="wp-block-paragraph">萬一真的運氣特好遇到同一支API 回傳值在不同情況下某一個參數會回傳完全不同的型態，偏偏你又必須要使用這些參數，那只好自定義解析方式了，方法上網Google 一下就會找得到了，不然也可以詢問 Claude AI. </p>



<p class="wp-block-paragraph">其方式是 <code>extenstion KeyedDecodingContainer</code> 這個Struct ，寫一個通用的 <code>decode</code>，之後就可以利用這個 <code>decode</code> 為它加上一些自定義的解析方式</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>func decodeAny&lt;T&gt;(_: T.Type, forKey key: K, transform: ((Any) -&gt; T?)? = nil) throws -&gt; T? {
        guard contains(key) else { return nil }
        
        //預設轉換邏輯
        let defaultTransform: (Any) -&gt; T? = { value in
            if let typedValue = value as? T {
                return typedValue
            }
            
            if let stringConvertible = T.self as? LosslessStringConvertible.Type {
                let stringValue = String(describing: value)
                return stringConvertible.init(stringValue) as? T
            }
            
            return nil
        }
        
        let transformer = transform ?? defaultTransform
        
        //不同型別的解法
        let attempts: [(Any.Type, (K) throws -&gt; Any)] = [
            (String.self, {try decode(String.self, forKey: $0) as Any}),
            (Int.self, {try decode(Int.self, forKey: $0) as Any}),
            (Double.self, {try decode(Double.self, forKey: $0) as Any}),
            (Bool.self, {try decode(Bool.self, forKey: $0) as Any})
        ]
        
        // value: 取得API 資料的型態,transformed: 利用transformer 解析成需要的型態
        for(_, decoder) in attempts {
            if let value = try? decoder(key),
               let transformed = transformer(value) {
                return transformed
            }
        }
    
        return nil
    }</code></pre></div>



<p class="wp-block-paragraph">例如 <code>Bool</code> 是最常遇到擁有各種不同型態的表示法的，就可以為它來寫一個方便的解析方式</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>func decodeBool(forKey key: K) throws -&gt; Bool? {
        try decodeAny(Bool.self, forKey: key) { value in
            switch value {
            case let boolValue as Bool:
                return boolValue
            case let intValue as Int:
                return intValue != 0
            case let doubleValue as Double:
                return doubleValue != 0
            case let stringValue as String:
                let lowercased = stringValue.lowercased()
                if [&quot;true&quot;, &quot;t&quot;, &quot;yes&quot;, &quot;1&quot;].contains(lowercased) { return true }
                if [&quot;false&quot;, &quot;f&quot;, &quot;no&quot;, &quot;0&quot;].contains(lowercased) { return false }
                return nil
            default nil
            }
        }
    }</code></pre></div>



<p class="wp-block-paragraph">說真的，最好是不要遇到需要這樣寫的情況，為什麼呢? 主要的原因是萬一真的是 API 的錯誤，但因為App 端有自定義解析的方式，所以就解析成功了，那麼就可能完全不會有人知道可能真的是資料錯誤的問題，當這樣資料數量多了之後，也許就會造成後續資料分析有誤，或有些神奇的情況。</p>



<p class="wp-block-paragraph">最後，祝大家 Coding 愉快!!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://doeverythingiwant.com/codable-decoder/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
