Использование JSONEncoder для кодирования переменной с типом Codable

Мне удалось заставить работать кодирование и декодирование 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, если только Value является каким-то странным типом, о котором я не знаю.

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

Ответ 1

Используйте общий тип, ограниченный Encodable

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

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

    //Some more code
}

Ответ 2

Я бы использовал другой подход к расширению протокола Encodable всеми методами, которые вам могут понадобиться. Более того, вы можете добавить параметр в ваши методы для передачи пользовательского кодировщика и предоставить кодировщик по умолчанию для всех них:

extension Encodable {
    func data(using encoder: JSONEncoder = JSONEncoder()) throws -> Data {
        return try encoder.encode(self)
    }
    func string(using encoder: JSONEncoder = JSONEncoder()) throws -> String {
        return try String(data: encoder.encode(self), encoding: .utf8)!
    }
}

использование

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 (Double, представляющий timeIntervalSinceReferenceDate):

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(String(data: data, encoding: .utf8)!)
        // following the code to upload the data to the server
        print("Message was successfully sent")
    }
}

let message = Message(id: 1, createdAt: Date(), sender: "[email protected]", title: "Lorem Ipsum", body: """
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry 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: "[email protected]", title: "Lorem Ipsum", body: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\ 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 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" : "[email protected]",
  "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)
}