JSONDecoder で型がごちゃ混ぜの JSON 配列をデコードする
ルートが配列で、最初の要素はその後の要素の個数を表す、という JSON があったときに、どういう Decodable
を書けばいいのか?で少し悩んだので。
[ {"count" : 5 }, {"name" : "itemA", "value" : "valueA" }, {"name" : "itemB", "value" : "valueB" }, {"name" : "itemC", "value" : "valueC" }, {"name" : "itemD", "value" : "valueD" }, {"name" : "itemE", "value" : "valueE" } ]
それぞれの要素が、以下のいずれかの型に decode されるというところまでは共通。
struct Count: Codable { let count: Int } struct Item: Codable { let name: String let value: String }
方法1. 取り得る値の両方でパースする
Stack Overflow の回答で見つけたのが、singleValueContainer()
でそれぞれの型に対して decode()
できるかチェックする方法。
enum Response: Decodable { case count(Count) case item(Item) init(from decoder: Decoder) throws { if let count = try? decoder.singleValueContainer().decode(Count.self) { self = .count(count) return } if let item = try? decoder.singleValueContainer().decode(Item.self) { self = .item(item) return } fatalError() } }
singleValueContainer()
のリファレンスを見ると、
Returns the data stored in this decoder as represented in a container appropriate for holding a single primitive value.
「単一のプリミティブな値を持つ」というような説明があって、SO の回答でも Int
や String
を decode していたので、ES の仕様でいうところの Primitive Data Types しか decode できないのだと勝手に勘違いしていたけど、Decodable
でも普通にいけた。
方法2. 配列を直接全部走査する
このケースだと、「先頭の要素は全件のカウントが入っている」ということが自明なので、unkeyedContainer()
で配列のコンテナを取得して、要素一つ一つに対して decode() していくという方法の方が使い勝手が良かった。
struct Response: Decodable { let count: Int let items: [Item] init(from decoder: Decoder) throws { var unkeyedContainer = try decoder.unkeyedContainer() count = try unkeyedContainer.decode(Count.self).count var result: [Item] = [] result.reserveCapacity(count) while !unkeyedContainer.isAtEnd { result.append(try unkeyedContainer.decode(Item.self)) } items = result } }
JSON の構造が固定ならこちらの方がよさそう。
逆の操作をすれば encode() もできるので、Codable
にすることもできる。
extension Response: Encodable { func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() try container.encode(Count(count: count)) try items.forEach { try container.encode($0) } } }