关于可编码:如何在 Swift 4 中使用 JSONEncoder 对字典进行编码 | 珊瑚贝

How to encode Dictionary with JSONEncoder in Swift 4


我想用 JSONEncoder 将 Dictionary 编码为 json。
它看起来像一个请求,接收一个字典作为参数并将其编码为 json 作为 http 正文。
代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let dict = [“name”:”abcde”]

protocol Request {
    var params: [String: Encodable] { get set }
    func encode< T >(_ value: T) throws -> Data where T : Encodable
}

extension Request {
    func encode< T >(_ value: T) throws -> Data where T : Encodable {
        return try JSONEncoder().encode(value)
    }

    var body: Data? {
        if let encoded = try? self.encode(self.params) {
            return encoded
        }
        return nil
    }
}

struct BaseRequest: Request {
    var params: [String : Encodable]
}

let req = BaseRequest(params: dict)
let body = req.body

但是这段代码出现错误

Fatal error: Dictionary does not conform to Encodable because Encodable does not conform to itself. You must use a concrete type to encode or decode.

我怎样才能使它可编码?

  • 为什么不使用 JSONSerializer 呢?你想防止 Any 依赖吗?
  • 是的,我需要参数为 [String: Any]
  • 哪些类型最终可以作为字典中的值?这就像不是真正的 Any 而是几种已知类型之一,对吧?通常,最好的解决方案是使用这些类型作为关联值进行枚举,以确认 Encodable。
  • 最后我在 Request 中添加了 associatetype


你必须按如下方式引入类型擦除:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct AnyEncodable: Encodable {

    let value: Encodable
    init(value: Encodable) {
        self.value = value
    }

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

}

struct Model: Encodable {

    var params: [String: AnyEncodable]

}

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let json = try! encoder.encode(
    Model(
        params: [
           “hello” : AnyEncodable.init(value:”world”)
        ]
    ).params
)
print(String(data: json, encoding: .utf8))

  • 不幸的是,像这样的类型擦除会阻止编码器在编码之前拦截类型。这意味着如果您尝试对可能具有编码策略的类型(例如 Dates 或 Data)进行编码,它将不会被应用。
  • @ItaiFerber AnyEncodable 装饰它收到的 Encodable 的值。它使用 Encodable 本身定义的实现(每个具体实现的定义不同)。我看不出它是如何破坏不同的编码策略的,除非您尝试输入强制类型转换,这是一个非常糟糕的主意。
  • 此外,如果您需要打破封装,您可以键入 AnyEncodable 的 cast value 参数。
  • 当您通过 JSONEncoder 的容器之一 encode(…) 一个值时,它最终会将该值装箱以进行编码。由于所有的编码方法都是通用的,它知道被编码的内容的类型,并且可以拦截它以应用编码策略。您可以在 box_ 的实现中看到这一点:它检查要应用的特定类型。但是,当您执行 value.encode(to: encoder) 时,您会反转关系,并直接调用底层类型实现。
  • 例如,对于 Date,您最终总是将日期编码为 Double,因为默认情况下 Date 就是这样编码的(实际上,代码会询问”日期,请将自己编码到编码器中”,而不是”编码器,请编码这个日期”)。 encoder 从来没有看到有一个 Date 因为 Date.encode 是直接调用的,它只是对时间间隔值进行编码。
  • 您可以在此要点中看到此行为:将日期package在 AnyEncodable 中会丢失类型上下文,因此编码器无法应用 DateEncodingStrategy。如果您不使用策略,那么这当然完全可以。它只是一个需要注意的警告,因此您最终不会在同一编码的有效负载中出现冲突的值格式。
  • @ItaiFerber 感谢您指出这一点!我什至不知道编码器存在这种行为。但是无论如何,变异编码器是一个坏主意。为什么不引入两个概念装饰器,如 StandardDate 和 FormattedDate 以在编码器上以不同的方式打印日期。他们可以在 encode 方法中更改编码器的状态,应用自己并将编码器返回到其原始状态。
  • 通常在正确性(即不破坏封装)和有用性之间进行权衡。 JSON 经常被发送到对日期格式有严格要求的服务器(因为 JSON 没有指定日期必须如何编码,每个服务器都是不同的),而且很多时候,您需要对您不拥有且无法影响的类型进行编码— 如果这些类型编码 Dates 而不是 StandardDate 或 FormattedDate,那么您无能为力。由于这种权衡,我们为非常有限的一组类型(目前只有 Date 和 Data)提供这些策略。
  • @ItaiFerber 我明白你的推理,但是通过引入打破封装的概念,我们创建了一个积极的反馈循环,我们让开发人员偷工减料,而不是尝试设计方便的 OO 概念来解决严格封装设计的限制。


如果你想定义你的结构符合Codable,你可以这样做:

1
2
3
4
5
6
7
8
struct Model: Codable {
    var param1: String
    var param2: Int
}

let model = Model(param1:”test”, param2: 0)
let encoded = try? JSONEncoder().encode(model)
let decoded = try? JSONDecoder().decode(Model.self, from: encoded!)

如果您设置 params: [String: Any],它实际上不会起作用,因为编码器/解码器不知道如何编码/解码 Any,但他们可以为原始类型做到这一点。

如果您需要更多帮助,您应该阅读更多关于新的 Codable 协议的信息。我推荐这个:https://hackernoon.com/everything-about-codable-in-swift-4-97d0e18a2999

  • Timofey Solonins 的回答显示了一种封装 Encodable Any 的好方法,以防您想使用它。但是,您最好准确定义模型使用的类型,这样您就不需要封装它们。


来源:https://www.codenong.com/48544098/

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?