首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >符合Swift中可编码性的具有泛型性质的结构

符合Swift中可编码性的具有泛型性质的结构
EN

Stack Overflow用户
提问于 2020-02-06 13:48:42
回答 1查看 3.6K关注 0票数 5

在struct中,我正在寻找一种具有泛型属性的方法,其中在运行时定义类型如下:

代码语言:javascript
复制
struct Dog {
    let id: String
    let value: ??
}

在构建json对象时,这是一个有用的简单用例。node可以是intstringbool、数组等,但除了可以更改的类型之外,对象node保持不变。

在使用protocols (得到了通常的protocol 'X' can only be used as a generic constraint because it has Self or associated type requirements错误)之后,我想出了两个不同的解决方案,#0使用type erasure,#1使用type-erasuregenerics

#0 (类型擦除)

代码语言:javascript
复制
struct AnyDog: Encodable {

    enum ValueType: Encodable {
        case int(Int)
        case string(String)

        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            switch self {
            case .int(let value):
                try container.encode(value)
            case .string(let value):
                try container.encode(value)
            }
        }
    }

    let id: String
    let value: ValueType

    init(_ dog: DogString) {
        self.id = dog.id
        self.value = .string(dog.value)
    }

    init(_ dog: DogInt) {
        self.id = dog.id
        self.value = .int(dog.value)
    }
}

struct DogString: Encodable{
    let id: String
    let value: String

    var toAny: AnyDog {
        return AnyDog(self)
    }
}

struct DogInt: Encodable {
    let id: String
    let value: Int

    var toAny: AnyDog {
        return AnyDog(self)
    }
}

let dogs: [AnyDog] = [
    DogString(id: "123", value: "pop").toAny,
    DogInt(id: "123", value: 123).toAny,
]

do {
    let data = try JSONEncoder().encode(dogs)
    print(String(data: data, encoding: .utf8)!)
} catch {
    print(error)
} 

#1 (类型擦除+泛型)

代码语言:javascript
复制
struct AnyDog: Encodable {

    enum ValueType: Encodable {
        case int(Int)
        case string(String)

        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            switch self {
            case .int(let value):
                try container.encode(value)
            case .string(let value):
                try container.encode(value)
            }
        }
    }

    let id: String
    let value: ValueType
}

struct Dog<T: Encodable>: Encodable{
    let id: String
    let value: T

    var toAny: AnyDog {
        switch T.self {
        case is String.Type:
            return AnyDog(id: id, value: .string(value as! String))
        case is Int.Type:
            return AnyDog(id: id, value: .int(value as! Int))
        default:
            preconditionFailure("Invalid Type")
        }
    }
}
let dogs: [AnyDog] = [
    Dog<String>(id: "123", value: "pop").toAny ,
    Dog<Int>(id: "123", value: 123).toAny,
]

do {
    let data = try JSONEncoder().encode(dogs)
    print(String(data: data, encoding: .utf8)!)
} catch {
    print(error)
}

这两种方法都产生了适当的结果:

代码语言:javascript
复制
[{"id":"123","value":"pop"},{"id":"123","value":123}]

即使结果是相同的,我坚信,如果考虑到更多的类型,那么方法#1就是更多的scalable,但是对于添加的每个类型,仍然需要在两个不同的区域进行更改。

我相信有一个更好的方法来实现这一点,但未能找到它。会很高兴听到任何关于它的想法或建议。

编辑#0 2020/02/08:可选值

使用Rob的好答案,我现在尝试允许value是可选的,如下所示:

代码语言:javascript
复制
struct Dog: Encodable {
    // This is the key to the solution: bury the type of value inside a closure
    let valueEncoder: (Encoder) throws -> Void

    init<T: Encodable>(id: String, value: T?) {
        self.valueEncoder = {
            var container = $0.container(keyedBy: CodingKeys.self)
            try container.encode(id, forKey: .id)
            try container.encode(value, forKey: .value)
        }
    }

    enum CodingKeys: String, CodingKey {
        case id, value
    }

    func encode(to encoder: Encoder) throws {
        try valueEncoder(encoder)
    }
}

let dogs = [
    Dog(id: "123", value: 123),
    Dog(id: "456", value: nil),
]

do {
    let data = try JSONEncoder().encode(dogs)
    print(String(data: data, encoding: .utf8)!)
} catch {
    print(error)
}

此时,不能再推断T,并引发以下错误:

代码语言:javascript
复制
generic parameter 'T' could not be inferred

我正在寻找使用Rob的答案的可能性,如果Optional类型给出了value,则给出以下结果

代码语言:javascript
复制
[{"id":"123","value":123},{"id":"456","value":null}]

编辑#1 2020/02/08:解决方案

好的,我是如此专注于给valuenil,以至于我没有意识到nil没有任何类型,从而导致了推理错误。

提供一个可选类型使其工作:

代码语言:javascript
复制
let optString: String? = nil
let dogs = [
    Dog(id: "123", value: 123),
    Dog(id: "456", value: optString),
]
EN

回答 1

Stack Overflow用户

发布于 2020-02-08 07:18:41

以下是另一种可能有帮助的解决方案:

代码语言:javascript
复制
struct Dog<V: Codable>: Codable {
   let id: String
   let value: V
}
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60096473

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档