如何将 JSON 解码和编码为 Swift 中的通用结构

How do I decode and encode JSON into a generic struct in Swift

假设 JSON 看起来像这样:

{
  "users": [
    {
      "id": 6,
      "email": "123@gmail.com"
    },
    {
      "id": 2,
      "email": "345@gmail.com"
    }
  ],
  "meta": {
    "current_page": 1,
    "next_page": 2,
    "prev_page": null,
    "total_pages": 3,
    "total_count": 12
  }
}

有时它看起来像这样

{
  "messages": [
    {
      "id": 6,
      "text": "hello"
    },
    {
      "id": 2,
      "text": "hi"
    }
  ],
  "meta": {
    "current_page": 1,
    "next_page": 2,
    "prev_page": null,
    "total_pages": 3,
    "total_count": 12
  }
}

如您所见,编码键会根据JSON中的对象而变化。 我如何将此 JSON 解析为我可以阅读的动态内容,例如:

struct GenericListModel<ListObject: Codable>: Codable {
    let list: [ListObject]
    let page: PaginationModel
}

我将在其中单独创建 ListObject,例如:UserModel

然后我将创建模型:

GenericListModel<UserModel>(list: UserModel(id: 6, email: "123@gmail.com"), page: PaginationModel())

您可能想稍微调整一下,但您可以这样做:

struct Metadata: Codable {
    // ...
}

struct User: Codable {
    let id: Int
    let email: String
}

struct Message: Codable {
    // ...
}

protocol ListKeyable: CodingKey {
    static var listKey: Self { get }
}

enum UserKeys: String, ListKeyable {
    case users

    static var listKey: UserKeys { .users }
}

enum MessageKeys: String, ListKeyable {
    case messages

    static var listKey: MessageKeys { .messages }
}

class PagedList<Element: Codable, ListKeys: ListKeyable>: Codable {

    enum MetaKeys: String, CodingKey {
        case meta
    }

    let list: [Element]
    let meta: Metadata

    func encode(to encoder: Encoder) throws {
        var container1 = encoder.container(keyedBy: ListKeys.self)
        var container2 = encoder.container(keyedBy: MetaKeys.self)
        try container1.encode(list, forKey: ListKeys.listKey)
        try container2.encode(meta, forKey: .meta)
    }

    required init(from decoder: Decoder) throws {
        let container1 = try decoder.container(keyedBy: ListKeys.self)
        let container2 = try decoder.container(keyedBy: MetaKeys.self)
        list = try container1.decode([Element].self, forKey: ListKeys.listKey)
        meta = try container2.decode(Metadata.self, forKey: .meta)
    }
}

let decoder = JSONDecoder()
let list = try decoder.decode(PagedList<User, UserKeys>.self, from: users)