Swift JSONDecode 配列のデコードは、単一要素のデコードが失敗すると失敗します 質問する

Swift JSONDecode 配列のデコードは、単一要素のデコードが失敗すると失敗します 質問する

Swift4 と Codable プロトコルを使用しているときに、次の問題が発生しました。配列内の要素をスキップする方法がないようですJSONDecoder。たとえば、次の JSON があります。

[
    {
        "name": "Banana",
        "points": 200,
        "description": "A banana grown in Ecuador."
    },
    {
        "name": "Orange"
    }
]

そしてコーディング可能構造体:

struct GroceryProduct: Codable {
    var name: String
    var points: Int
    var description: String?
}

このJSONをデコードすると

let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)

結果は空です。JSON の 2 番目のオブジェクトにはキーがなく、構造体ではオプションではないproductsため、これは予想どおりの結果です。"points"pointsGroceryProduct

JSONDecoder質問は、無効なオブジェクトを「スキップ」できるようにするにはどうすればよいかということです。

ベストアンサー1

1 つのオプションは、指定された値のデコードを試みるラッパー型を使用することです。nil失敗した場合は保存します。

struct FailableDecodable<Base : Decodable> : Decodable {

    let base: Base?

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        self.base = try? container.decode(Base.self)
    }
}

GroceryProduct次に、プレースホルダーに入力した内容に基づいて、これらの配列をデコードしますBase

import Foundation

let json = """
[
    {
        "name": "Banana",
        "points": 200,
        "description": "A banana grown in Ecuador."
    },
    {
        "name": "Orange"
    }
]
""".data(using: .utf8)!


struct GroceryProduct : Codable {
    var name: String
    var points: Int
    var description: String?
}

let products = try JSONDecoder()
    .decode([FailableDecodable<GroceryProduct>].self, from: json)
    .compactMap { $0.base } // .flatMap in Swift 4.0

print(products)

// [
//    GroceryProduct(
//      name: "Banana", points: 200,
//      description: Optional("A banana grown in Ecuador.")
//    )
// ]

次に、要素 (デコード時にエラーが発生した要素) を.compactMap { $0.base }フィルター処理するために使用します。nil

これにより、 の中間配列が作成されます[FailableDecodable<GroceryProduct>]が、これは問題にはなりません。ただし、これを回避したい場合は、キーのないコンテナーから各要素をデコードしてラップ解除する別のラッパー タイプを作成することもできます。

struct FailableCodableArray<Element : Codable> : Codable {

    var elements: [Element]

    init(from decoder: Decoder) throws {

        var container = try decoder.unkeyedContainer()

        var elements = [Element]()
        if let count = container.count {
            elements.reserveCapacity(count)
        }

        while !container.isAtEnd {
            if let element = try container
                .decode(FailableDecodable<Element>.self).base {

                elements.append(element)
            }
        }

        self.elements = elements
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(elements)
    }
}

次のようにデコードします。

let products = try JSONDecoder()
    .decode(FailableCodableArray<GroceryProduct>.self, from: json)
    .elements

print(products)

// [
//    GroceryProduct(
//      name: "Banana", points: 200,
//      description: Optional("A banana grown in Ecuador.")
//    )
// ]

おすすめ記事