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) }
}
}