使用字典/数组初始化符合 Codable 的对象

Posted

技术标签:

【中文标题】使用字典/数组初始化符合 Codable 的对象【英文标题】:Init an object conforming to Codable with a dictionary/array 【发布时间】:2018-03-01 19:47:28 【问题描述】:

我的主要用例是使用字典创建一个对象:例如

struct Person: Codable  let name: String     
let dictionary = ["name": "Bob"]
let person = Person(from: dictionary)    

我想避免编写自定义实现,并希望尽可能高效。

【问题讨论】:

【参考方案1】:

目前我最好的解决方案是这个,但它有编码/解码的开销。

extension Decodable 
  init(from: Any) throws 
    let data = try JSONSerialization.data(withJSONObject: from, options: .prettyPrinted)
    let decoder = JSONDecoder()
    self = try decoder.decode(Self.self, from: data)
  

根据问题中的示例,结果将是

let person = Person(from: dictionary)

如果您有兴趣采取另一种方式,那么这可能会有所帮助https://***.com/a/46329055/1453346

【讨论】:

什么是 DateFormatter 部分...? @Marty:使用 Codable,您可以在 Decorder 中定义自己的日期格式,以便更正设置对象的日期属性。 @smukamuka 是的,但在这种特殊情况下,它与问题有什么关系......? :) 没什么!只是我的特殊问题有一个日期,而 json 序列化自动编码日期和可解码的事实一开始并没有让我感到困惑,所以我把它留了下来 这是一个很好的答案。如果您来自***.com/a/46329055/1453346,则应删除日期格式化程序行,它们会破坏该用例中的解码【参考方案2】:

基于Chris Mitchelmore answer

详情

Xcode 版本 10.3 (10G8),Swift 5

解决方案

import Foundation

extension Decodable 

    init(from value: Any,
         options: JSONSerialization.WritingOptions = [],
         decoder: JSONDecoder) throws 
        let data = try JSONSerialization.data(withJSONObject: value, options: options)
        self = try decoder.decode(Self.self, from: data)
    

    init(from value: Any,
         options: JSONSerialization.WritingOptions = [],
         decoderSetupClosure: ((JSONDecoder) -> Void)? = nil) throws 
        let decoder = JSONDecoder()
        decoderSetupClosure?(decoder)
        try self.init(from: value, options: options, decoder: decoder)
    

    init?(discardingAnErrorFrom value: Any,
          printError: Bool = false,
          options: JSONSerialization.WritingOptions = [],
          decoderSetupClosure: ((JSONDecoder) -> Void)? = nil) 
        do 
            try self.init(from: value, options: options, decoderSetupClosure: decoderSetupClosure)
         catch 
            if printError  print("\(Self.self) decoding ERROR:\n\(error)") 
            return nil
        
    

用法

struct Item: Decodable 
    let id: Int
    let name: String
    let isActive: Bool
    var date: Date


let dictionary = ["id": 1, "name": "Item", "is_active": false,
                  "date": "2019-08-06T06:55:00.000-04:00"] as [String : Any]
do 
    let item1 = try Item(from: dictionary)  decoder in
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
        decoder.dateDecodingStrategy = .formatted(dateFormatter)
    
    print(item1)
 catch 
    print("Error: \(error)")


print("\n========================")
let item2 = Item(discardingAnErrorFrom: dictionary)
print(String(describing: item2))

print("\n========================")
let item3 = Item(discardingAnErrorFrom: dictionary, printError: true)
print(String(describing: item3))

print("\n========================")
let item4 = Item(discardingAnErrorFrom: dictionary) decoder in
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
    decoder.dateDecodingStrategy = .formatted(dateFormatter)

print(String(describing: item4))

使用记录

Item(id: 1, name: "Item", isActive: false, date: 2019-08-06 10:55:00 +0000)

========================
nil

========================
Item decoding ERROR:
keyNotFound(CodingKeys(stringValue: "isActive", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"isActive\", intValue: nil) (\"isActive\").", underlyingError: nil))
nil

========================
Optional(__lldb_expr_5.Item(id: 1, name: "Item", isActive: false, date: 2019-08-06 10:55:00 +0000))

【讨论】:

【参考方案3】:

我改编了Chris Mitchelmore 的答案,使其成为一个可失败的初始化程序,而不是抛出代码。在某些情况下让它更方便一些。

extension Decodable 
    init?(from: Any) 
        guard let data = try? JSONSerialization.data(withJSONObject: from, options: .prettyPrinted) else  return nil 
        let decoder = JSONDecoder()
        guard let decoded = try? decoder.decode(Self.self, from: data) else  return nil 
        self = decoded
    

【讨论】:

以上是关于使用字典/数组初始化符合 Codable 的对象的主要内容,如果未能解决你的问题,请参考以下文章

在 swift 中对动态键和动态对象使用 Codable

如何使用 Codable 从字典中解码 .key

如果某些东西符合 Codable ,它会永远无法被编码或解码吗?

Codable和CodingKeys

忽略 Codable 对象中的非 Codable 可选属性

Swift Init 不符合预期的“解码器”类型