3

現在、次のような JSON をデコードしようとしています。

{
  "result": {
    "success": true,
    "items": [
      {
        "timeEntryID": "1",
        "start": "1519558200",
        "end": "1519563600",
        "customerName": "Test-Customer",
        "projectName": "Test-Project",
        "description": "Entry 1",
      },
      {
        "timeEntryID": "2",
        "start": "1519558200",
        "end": "1519563600",
        "customerName": "Test-Customer",
        "projectName": "Test-Project",
        "description": "Entry 2",
      }
    ],
    "total": "2"
  },
  "id": "1"
}

この特定のタイプの JSON のデコード プロセスは非常に単純です。私はちょうどこのようなものが必要です:

struct ResponseKeys: Decodable {
    let result: ResultKeys

    struct ResultKeys: Decodable {
        let success: Bool
        let items: [Item]
    }
}

今私が直面している問題は、サーバーのすべての応答が上記の JSON と同じ構造を持っているが、アイテムの種類が異なることです。そのため、そうである場合もありますが、ユーザー エンドポイントを呼び出した場合let items: [Item]もそうなる可能性があります。let items: [User]

アイテム配列を変更するだけですべてのエンドポイントに対して上記の迅速なコードを記述すると、コードの不要な複製になるため、カスタム デコーダーを作成しました。

enum KimaiAPIResponseKeys: String, CodingKey {
    case result

    enum KimaiResultKeys: String, CodingKey {
        case success
        case items
    }
}

struct Activity: Codable {
    let id: Int
    let description: String?
    let customerName: String
    let projectName: String
    let startDateTime: Date
    let endDateTime: Date

    enum CodingKeys: String, CodingKey {
        case id = "timeEntryID"
        case description
        case customerName
        case projectName
        case startDateTime = "start"
        case endDateTime = "end"
    }
}

extension Activity {

    init(from decoder: Decoder) throws {
        let resultContainer = try decoder.container(keyedBy: KimaiAPIResponseKeys.self)
        let itemsContainer = try resultContainer.nestedContainer(keyedBy: KimaiAPIResponseKeys.KimaiResultKeys.self, forKey: .result)
        let activityContainer = try itemsContainer.nestedContainer(keyedBy: Activity.CodingKeys.self, forKey: .items)

        id = Int(try activityContainer.decode(String.self, forKey: .id))!
        description = try activityContainer.decodeIfPresent(String.self, forKey: .description)
        customerName = try activityContainer.decode(String.self, forKey: .customerName)
        projectName = try activityContainer.decode(String.self, forKey: .projectName)
        startDateTime = Date(timeIntervalSince1970: Double(try activityContainer.decode(String.self, forKey: .startDateTime))!)
        endDateTime = Date(timeIntervalSince1970: Double(try activityContainer.decode(String.self, forKey: .endDateTime))!)
    }

}

"items"配列ではなく単一のオブジェクトのみが含まれている場合、デコーダーは完全に機能します。

{
  "result": {
    "success": true,
    "items":
      {
        "timeEntryID": "2",
        "start": "1519558200",
        "end": "1519563600",
        "customerName": "Test-Customer",
        "projectName": "Test-Project",
        "description": "Entry 2",
      },
    "total": "2"
  },
  "id": "1"
}

が配列の場合items、次のエラーが発生します。

typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [__lldb_expr_151.KimaiAPIResponseKeys.result], debugDescription: "Dictionary をデコードすると予想されますが、代わりに配列が見つかりました。", 基本エラー: nil))

デコーダーを変更してアイテムの配列を操作する方法がわかりません。機能するバージョンと機能しないバージョンの JSON を使用して Playground ファイルを作成しました。見て、試してみてください: Decodable.playground

ご協力ありがとうございました!

4

2 に答える 2