swift 如何使用泛型和继承来解析输出?

9jyewag0  于 2023-01-25  发布在  Swift
关注(0)|答案(1)|浏览(119)

我正在NestJs中为一个启用了分页的后端编写一个网络调用,服务的输出具有以下结构:

{
    "items": [{
        "id": "26c26ecb-0763-4813-b489-3c6ad94084a9",
        "body": "Test body",
        "subTitle": "SubTitle Test",
        "author": "Test Author",
        "createAt": "2023-01-12T07:04:58.480Z",
        "title": "Temporary Title",
        "updateAt": "2023-01-12T07:04:58.480Z"
    }],
    "meta": {
        "itemsPerPage": 10,
        "totalPages": 2,
        "currentPage": 1,
        "totalItems": 19,
        "itemCount": 10
    },
    "links": {
        "previous": "",
        "first": "\/post\/public?limit=10",
        "next": "\/post\/public?page=2&limit=10",
        "last": "\/post\/public?page=2&limit=10"
    }
}

meta和链接属性是相当直接的,所以他们没有问题。我的问题是项目列表。对象的数据结构,里面可以是不同的,可以有新闻,帖子,事件。
所有这些对象都有一些共同的属性,因此创建了一个BaseEntity模型,因此所有模型都继承自BaseEntity。
我不想为每个输出创建一个类。我想声明一个泛型类,它接收泛型并相应地解析它。
我的BaseCrudEntity模型和协议是这样设计的:

public protocol BaseCrudEntityParsingProtocol: AnyObject
{
    var id: String { get set }
    var createAt: Date { get set }
    var updateAt: Date { get set }
    var deletedAt: Date? { get set }
    var isActive: Bool { get set }
    
    init()
    init?(info: Any?)
    static func parseList(info: Any?) -> [BaseCrudEntityParsingProtocol]?
}

public extension BaseCrudEntityParsingProtocol
{
    static func parseList(info: Any?) -> [BaseCrudEntityParsingProtocol]? {
        return nil
    }
}

public class BaseCrudEntity: BaseCrudEntityParsingProtocol
{
    public var createAt: Date = Date()
    public var updateAt: Date = Date()
    public var deletedAt: Date?
    public var isActive: Bool = true
    public var id: String = ""
    
    enum CodingKeysBase: String {
        case createAt = "createAt"
        case updateAt = "updateAt"
        case deletedAt = "deletedAt"
        case isActive = "isActive"
        case id = "id"
    }

    required public init() { }
    
    required public init?(info: Any?) {
        guard let anyInfo = info,
              let info = anyInfo as? NSDictionary else { return nil }
    
        self.createAt = info.parseString(forKeyPath: CodingKeysBase.createAt.rawValue).toDate()
        self.updateAt = info.parseString(forKeyPath: CodingKeysBase.updateAt.rawValue).toDate()
        self.deletedAt = info.parseStringOptional(forKeyPath: CodingKeysBase.deletedAt.rawValue)?.toDateOptional()
        self.isActive = info.parseBool(forKeyPath: CodingKeysBase.isActive.rawValue)
        self.id = info.parseString(forKeyPath: CodingKeysBase.id.rawValue)
    }
}

一个具有分页功能的特定模型的示例如下所示:

// MARK: - Post
public class Post: BaseCrudEntity
{
    public var title: String?
    public var subTitle: String?
    public var author: String?
    public var body: String?

    enum CodingKeys: String, CodingKey {
        case title = "title"
        case subTitle = "subTitle"
        case author = "author"
        case body = "body"
    }

    required public init() {
        super.init()
    }
    
    required public init?(info: Any?) {
        super.init(info: info)
        guard let anyInfo = info,
              let info = anyInfo as? NSDictionary else { return nil }
        
        self.title = info.parseStringOptional(forKeyPath: CodingKeys.title.rawValue)
        self.subTitle = info.parseStringOptional(forKeyPath: CodingKeys.subTitle.rawValue)
        self.author = info.parseStringOptional(forKeyPath: CodingKeys.author.rawValue)
        self.body = info.parseStringOptional(forKeyPath: CodingKeys.body.rawValue)
    }
    
    public static func parseList(info: Any?) -> [Post]? {
        guard let anyInfo = info,
              let rawList = anyInfo as? [NSDictionary] else { return nil }
        return rawList.compactMap(Post.init)
    }
}

我创建了下面的类,这样我就可以传递一个泛型值并相应地解析它,我遇到的问题是项目列表返回nil,就好像解析不能成功完成或者有问题一样。这个任务可以使用Codable协议完成,但是由于代码库设计,它现在不能完成。

public class PaginatedList<Body: BaseCrudEntity> where Body: BaseCrudEntity
{
    public var items: [Body]?
    public var meta: PaginatedListMeta?
    public var links: PaginatedListLinks?

    enum CodingKeys: String, CodingKey {
        case meta = "meta"
        case links = "links"
        case items = "items" 
    }

    public init() {
    }

    public init(items: [Body]?, meta: PaginatedListMeta?, links: PaginatedListLinks?) {
        self.meta = meta
        self.links = links
    }

    public init?(info: Any?) {
        guard let anyInfo = info,
              let info = anyInfo as? NSDictionary else { return nil }

        self.meta = PaginatedListMeta(info: info[CodingKeys.meta.rawValue])
        self.links = PaginatedListLinks(info: info[CodingKeys.links.rawValue])
        self.items = Body.parseList(info: info[CodingKeys.items.rawValue]) as? [Body]
    }
}

我试过几种解决办法,但似乎都不管用。

62lalag4

62lalag41#

您可以尝试不同的方法,如本示例(SwiftUI)代码所示(UIKit也是如此)。
它使用[String:String]来存储不同的items类型,而不是将所有Item强制到一些结构模型中。

struct ServerResponse: Decodable {
    let items: [Item]
    let meta: Meta
    let links: Links
}

struct Item: Hashable, Decodable {
    var data: [String:String]
    
    init(from decoder: Decoder) throws {
        self.data = [:]
        do {
            let container = try decoder.singleValueContainer()
            self.data = try container.decode([String:String].self)
        } catch {
            throw error
        }
    }
}

struct Links: Decodable {
    let previous, first, next, last: String
}

struct Meta: Decodable {
    let itemsPerPage, totalPages, currentPage, totalItems: Int
    let itemCount: Int
}

struct ContentView: View {
    @State var apiResponse: ServerResponse?
    
    var body: some View {
        VStack {
            if let response = apiResponse {
                ForEach(response.items, id: \.self) { item in
                    Text(item.data["id"] ?? "no id").foregroundColor(.blue)
                    Text(item.data["createAt"] ?? "no create date").foregroundColor(.red)
                }
            }
        }
        .onAppear {
            let json = """
            {
                "items": [{
                    "id": "26c26ecb-0763-4813-b489-3c6ad94084a9",
                    "body": "Test body",
                    "subTitle": "SubTitle Test",
                    "author": "Test Author",
                    "createAt": "2023-01-12T07:04:58.480Z",
                    "title": "Temporary Title",
                    "updateAt": "2023-01-12T07:04:58.480Z"
                },
                {
                    "id": "123456",
                    "prop1": "property-1",
                    "prop2": "property-2",
                    "prop3": "property-3",
                    "createAt": "2023-01-12T07:04:58.480Z",
                    "updateAt": "2023-01-12T07:04:58.480Z"
                }],
                "meta": {
                    "itemsPerPage": 10,
                    "totalPages": 2,
                    "currentPage": 1,
                    "totalItems": 19,
                    "itemCount": 10
                },
                "links": {
                    "previous": "hhhh",
                    "first": "xxxxxx",
                    "next": "wwwww",
                    "last": "yyyyy"
                }
            }
            """
            // simulated API data from the server
            let data = json.data(using: .utf8)!
            do {
                let results = try JSONDecoder().decode(ServerResponse.self, from: data)
                apiResponse = results
                print("\n---> results: \(results) \n")
            } catch {
                print("\n---> decoding error: \n \(error)\n")
            }
        }
    }
}

相关问题