使用 JSONEncoder 对类型为 Codable 的变量进行编码

Posted

技术标签:

【中文标题】使用 JSONEncoder 对类型为 Codable 的变量进行编码【英文标题】:Using JSONEncoder to encode a variable with Codable as type 【发布时间】:2017-12-16 14:36:54 【问题描述】:

我设法让 JSON 和 plist 编码和解码工作,但只能通过直接调用特定对象的编码/解码函数。

例如:

struct Test: Codable 
    var someString: String?


let testItem = Test()
testItem.someString = "abc"

let result = try JSONEncoder().encode(testItem)

这很好用,没有问题。

但是,我试图获得一个函数,它只接受 Codable 协议一致性作为类型并保存该对象。

func saveObject(_ object: Encodable, at location: String) 
    // Some code

    let data = try JSONEncoder().encode(object)

    // Some more code

这会导致以下错误:

无法使用“(Encodable)”类型的参数列表调用“encode”

看encode函数的定义,好像应该可以接受Encodable,除非Value是我不知道的奇怪类型。

open func encode<Value>(_ value: Value) throws -> Data where Value : Encodable

【问题讨论】:

Protocols don't conform to themselves,因此您不能用Encodable 替换通用占位符Value,因为Encodable 不是符合Encodable 的类型。正如 vadian 所说,只需使用通用占位符即可。 【参考方案1】:

使用限制为Encodable的泛型类型

func saveObject<T : Encodable>(_ object: T, at location: String) 
    //Some code

    let data = try JSONEncoder().encode(object)

    //Some more code

【讨论】:

当然,谢谢,我还是 Swift 新手,完全忘记了它是如何完成的。 什么?有人可以向我解释这种行为吗?这对我没有任何意义 Codable 需要能够确定其对象类型。使用Any 作为类型会混淆它,因为它不知道要调用哪个类型的init(from decoder:) 初始化程序。这个函数本质上以泛型的形式提供了缺失的信息。代码可以通过类型推断计算出使用哪种类型。 它需要知道(或推断)你想要编码的类型,因为如果你有var x: Encodable = myXStruct(); var y: Encodable = myYStruct(); 。编码功能不会知道那是什么。是 x 还是 y。因此您应该查找具有关联类型 (PAT) 的泛型函​​数和协议 在属性 getter 而不是函数中执行此操作的语法是什么?【参考方案2】:

我会使用一种不同的方法来扩展 Encodable 协议,使用您可能需要的所有实例方法。在此基础上,您可以向您的方法添加一个参数以传递自定义编码器并为所有方法提供默认编码器:

extension DataProtocol 
    var string: String?  String(bytes: self, encoding: .utf8) 


extension Encodable 
    func data(using encoder: JSONEncoder = JSONEncoder()) throws -> Data  try encoder.encode(self) 
    func string(using encoder: JSONEncoder = JSONEncoder()) throws -> String  try data(using: encoder).string ?? "" 


用法

let message = ["key":["a","b","c"]]

let jsonData = try! message.data() // 21 bytes [123, 34, 107, 101, 121, 34, 58, 91, 34, 97, 34, 44, 34, 98, 34, 44, 34, 99, 34, 93, 125]
let jsonString = try! message.string()  // ""key":["a","b","c"]"

使用默认编码器传递日期时的示例。请注意,使用的 dateEncodingStrategy 是默认的(表示 timeIntervalSinceReferenceDate 的 Double):

let message = ["createdAt": Date()]

let jsonData = try! message.data() // 33 bytes -> [123, 34, 99, 114, 101, 97, 116, 101, 97, 100, 65, 116, 34, 58, 53, 55, 49, 54, 49, 55, 56, 52, 49, 46, 52, 53, 48, 55, 52, 52, 48, 51, 125]
let jsonString = try! message.string()  // "createdAt":571617841.45074403"

现在您可以将自定义编码器传递给您的方法,以将您的日期格式化为人类可读的格式:

let message = ["createdAt": Date()]
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let jsonString = try! message.string(using: encoder)  // ""createdAt":"2019-02-11T22:48:19Z""

现在使用自定义消息结构

struct Message: Codable 
    let id: Int
    let createdAt: Date
    let sender, title, body: String


extension Encodable 
    func sendDataToServer(using encoder: JSONEncoder = JSONEncoder()) throws 
        print(self, terminator: "\n\n")
        // Don't handle the error here. Propagate the error.
        let data = try self.data(using: encoder)
        print(data.string!)
        // following the code to upload the data to the server
        print("Message was successfully sent")
    


let message = Message(id: 1, createdAt: Date(), sender: "user@company.com", title: "Lorem Ipsum", body: """
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
""")

let iso8601 = JSONEncoder()
iso8601.dateEncodingStrategy = .iso8601
iso8601.outputFormatting = .prettyPrinted
do 
    try message.sendDataToServer(using: iso8601)
 catch 
    // handle all errors
    print(error)


这将打印出来

Message(id: 1, createdAt: 2019-02-11 23:57:31 +0000, sender: "user@company.com", title: "Lorem Ipsum", body: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.")


  "body" : "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
  "id" : 1,
  "sender" : "user@company.com",
  "title" : "Lorem Ipsum",
  "createdAt" : "2019-02-11T23:57:31Z"

now just add the code to send the json data to the server

【讨论】:

【参考方案3】:

您需要使用泛型类型为Encodable的泛型函数

你不能

func toData(object: Encodable) throws -> Data 
  let encoder = JSONEncoder()
  return try encoder.encode(object) // Cannot invoke 'encode' with an argument list of type '(Encodable)'

你可以

func toData<T: Encodable>(object: T) throws -> Data 
  let encoder = JSONEncoder()
  return try encoder.encode(object)

【讨论】:

我对此寄予厚望,但同样的错误:` let bufferReq: Encodable func toData(object: T) throws -> Data let encoder = JSONEncoder() return try encoder. encode(object) do let foo = toData(object: bufferReq) // 还是一样的错误 `

以上是关于使用 JSONEncoder 对类型为 Codable 的变量进行编码的主要内容,如果未能解决你的问题,请参考以下文章

使用 JSONEncoder 编码/解码符合协议的类型数组

swift JSONEncoder() 如何编码 swift NSDate/ 数据类型?

使用 JSONEncoder 将 nil 值编码为 null

text JSONEncoder对数据进行编码/解码

为啥我的自定义 JSONEncoder.default() 忽略布尔值?

python字典转化成json格式。JSONEncoder和JSONDecoder两个类来实现Json字符串和dict类型数据的互相转换