Quantcast
Channel: dictionaryタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 99

[Swift] あなたのDictionaryは本当にCodableなの?

$
0
0

概要

言いたいこと: 「思った通りにDictionaryをencode/decodeできないことがあるよ!」

前提

この記事では分かりやすさのためJSONへの変換を考えます。特に断りがなければencoderは次のものとします。また、printJSONという関数も定義しておきます。

JSONEncoder.swift
letencoder=JSONEncoder()encoder.outputFormatting=[.prettyPrinted,.sortedKeys,.withoutEscapingSlashes,]funcprintJSON<T>(_object:T)throwswhereT:Encodable{print(String(data:tryencoder.encode(object),encoding:.utf8)!)}

そもそもDictionaryCodableになるの?

"Dictionary | Apple Developer Documentation"のConforms Toの項目には、

Decodable
- Conforms when Key conforms to Decodable and Value conforms to Decodable.
Encodable
- Conforms when Key conforms to Encodable and Value conforms to Encodable.

ということが書かれています。
CodableEncodable & Decodableのことなので、KeyValue両方がCodableに適合していれば、Dictionary<Key, Value>Codableになるということになります。

例: KeyValueStringの場合

StringはもともとCodableなので、Dictionary<String, String>Codableになるはずです。
実際のコードをみてみましょう:

EncodeStringDictionary.swift
letdictionary:Dictionary<String,String>=["key0":"value0","key1":"value1",]tryprintJSON(dictionary)

とても簡単。これを実行すると次のようにJSONが表示されるはずです。

{"key0":"value0","key1":"value1"}

期待通りですね。

独自のKeyにしてみよう!

KeyCodableなら(ValueCodableである限り)DictionaryCodableになるとのことでした。そこで、Codableに適合する独自の型をKeyにしてみましょう。

MyKey.swift
enumMyKey:String,Codable{casekey0casekey1}

すごく単純なenumMyKeyです1

では、このMyKeyKeyとするDictionaryをencodeしてみましょう!
コードは次のようになります:

EncodeMyKeyDictionary.swift
enumMyKey:String,Codable{casekey0casekey1}letdictionary:Dictionary<MyKey,String>=[.key0:"value0",.key1:"value1",]tryprintJSON(dictionary)

結果は…

["key0","value0","key1","value1"]

!?

KeyValueがフラットに並んだ配列になっているんですけど!
Dictionary<String, String>のときと同じ結果になることを期待したんですけど!

なぜフラットな配列になるのか?

答えは公式ドキュメントに書かれています:

If the dictionary uses String or Int keys, the contents are encoded in a keyed container. Otherwise, the contents are encoded as alternating key-value pairs in an unkeyed container.

(YOCKOW拙訳) StringIntをキーとして用いている場合、ディクショナリの中身はキーで紐づけられたコンテナにエンコードされます。それ以外の場合、キーで紐づけられないコンテナにキーと値のペアが交互にエンコードされます。

すなわち、上記の"EncodeMyKeyDictionary.swift"は、KeyStringでもIntでもないため、キーと値が交互に並ぶ配列としてエンコードされてしまうのです2

フラットな配列はJSONDecoderを使えばDictionary<MyKey, String>にデコードできるのですが、Dictionaryなのにエンコードされると配列になるというのは気持ち悪いですね。Foundation以外のライブラリやSwift以外の言語と連携するときは気をつけなければいけません。

やっぱりDictionaryはオブジェクト(連想配列)にしたい

世界中にそう思っている人がいるはずで、Swift JIRAにも該当の項目があります: SR-7788
そこで私めもコメントさせていただいたわけなのですが、これは設計段階でのミスに思えます。DictionaryCodableにしたかったらKeyが適合すべきはCodableではなくCodingKeyのはずなのです。しかし、今から変えようとするとAPIが破壊的変更となってしまうため、実現可能性はかなり低いでしょう。

いくつか選択肢はありますが、SwiftCodableDictionaryを利用するという手があります。Dictionaryの代わりにこのモジュールのCodableDictionaryを利用することで、期待通りのエンコード/デコードができるはずです。

WithSwiftCodableDictionary.swift
importFoundationimportCodableDictionaryenumMyKey:String,CodableDictionaryKey{casekey0casekey1}letdictionary:CodableDictionary<MyKey,String>=[.key0:"value0",.key1:"value1",]tryprintJSON(dictionary)// -> 期待通り

まとめ

以上、SwiftCodableDictionaryの宣伝でした(え?。


  1. StringRawValueとするRawRepresentableとすることで、Codableの実装を自分でせずに済みます。 

  2. 実際の実装はGitHubで見ることができます: https://github.com/apple/swift/blob/4dab4c235b975f9b092dac504f0546bf3a5d54e1/stdlib/public/core/Codable.swift#L5523-L5560 


Viewing all articles
Browse latest Browse all 99

Trending Articles