Подтвердить что ты не робот

Использование Decodable в Swift 4 с наследованием

Если использование наследования класса нарушает Декодируемость класса. Например, следующий код

class Server : Codable {
    var id : Int?
}

class Development : Server {
    var name : String?
    var userId : Int?
}

var json = "{\"id\" : 1,\"name\" : \"Large Building Development\"}"
let jsonDecoder = JSONDecoder()
let item = try jsonDecoder.decode(Development.self, from:json.data(using: .utf8)!) as Development

print(item.id ?? "id is nil")
print(item.name ?? "name is nil") here

:

1
name is nil

Теперь, если я отменил это, имя декодирует, но id не делает.

class Server {
    var id : Int?
}

class Development : Server, Codable {
    var name : String?
    var userId : Int?
}

var json = "{\"id\" : 1,\"name\" : \"Large Building Development\"}"
let jsonDecoder = JSONDecoder()
let item = try jsonDecoder.decode(Development.self, from:json.data(using: .utf8)!) as Development

print(item.id ?? "id is nil")
print(item.name ?? "name is nil")

:

id is nil
Large Building Development

И вы не можете выразить Codable в обоих классах.

4b9b3361

Ответ 1

Я считаю, что в случае наследования вы должны внедрить Coding самостоятельно. То есть вы должны указать CodingKeys и реализовать init(from:) и encode(to:) как в суперклассе, так и в подклассе. В соответствии с видео WWDC (около 49:28, на рисунке ниже), вы должны вызывать super с помощью супер-кодера/декодера.

WWDC 2017 Session 212 Screenshot at 49:28 (Source Code)

required init(from decoder: Decoder) throws {

  // Get our container for this subclass' coding keys
  let container = try decoder.container(keyedBy: CodingKeys.self)
  myVar = try container.decode(MyType.self, forKey: .myVar)
  // otherVar = ...

  // Get superDecoder for superclass and call super.init(from:) with it
  let superDecoder = try container.superDecoder()
  try super.init(from: superDecoder)

}

Видео, кажется, остановится, показывающая сторона кодирования (но container.superEncoder() для encode(to:) сторону), но она работает во многом так же, как в вашем encode(to:) реализации. Я могу подтвердить, что это работает в этом простом случае (см. Код игровой площадки ниже).

Я до сих пор борюсь с каким-то странным поведением и с гораздо более сложной моделью, которую я конвертирую из NSCoding, которая имеет много новых вложенных типов (включая struct и enum), которые демонстрируют это неожиданное поведение nil и "не должно быть", Просто знайте, что могут быть крайние случаи, которые включают вложенные типы.

Редактировать: Вложенные типы, кажется, отлично работают на моей тестовой площадке; Теперь я подозреваю, что что-то не так с самоссылающимися классами (например, потомками узлов дерева) с собственной коллекцией, которая также содержит экземпляры различных подклассов этого класса. Тест простого самоссылающегося класса прекрасно декодирует (то есть не имеет подклассов), поэтому я сейчас сосредотачиваю свои усилия на том, почему случай с подклассами терпит неудачу.

Обновление от 25 июня 17 года: я закончил с сообщением об ошибке Apple. rdar://32911973 - К сожалению, цикл кодирования/декодирования массива Superclass который содержит элементы Subclass: Superclass, приведет к тому, что все элементы в массиве будут декодированы как Superclass (init(from:) подкласса init(from:)) никогда не вызывается, что приводит к потеря данных или хуже).

//: Fully-Implemented Inheritance

class FullSuper: Codable {

    var id: UUID?

    init() {}

    private enum CodingKeys: String, CodingKey { case id }

    required init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(UUID.self, forKey: .id)

    }

    func encode(to encoder: Encoder) throws {

        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)

    }

}

class FullSub: FullSuper {

    var string: String?
    private enum CodingKeys: String, CodingKey { case string }

    override init() { super.init() }

    required init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)
        let superdecoder = try container.superDecoder()
        try super.init(from: superdecoder)

        string = try container.decode(String.self, forKey: .string)

    }

    override func encode(to encoder: Encoder) throws {

        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(string, forKey: .string)

        let superdecoder = container.superEncoder()
        try super.encode(to: superdecoder)

    }
}

let fullSub = FullSub()
fullSub.id = UUID()
fullSub.string = "FullSub"

let fullEncoder = PropertyListEncoder()
let fullData = try fullEncoder.encode(fullSub)

let fullDecoder = PropertyListDecoder()
let fullSubDecoded: FullSub = try fullDecoder.decode(FullSub.self, from: fullData)

Свойства super- и подкласса восстанавливаются в fullSubDecoded.

Ответ 2

Найти эту ссылку - перейти в раздел наследования

override func encode(to encoder: Encoder) throws {
    try super.encode(to: encoder)
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(employeeID, forKey: .employeeID)
}

Для декодирования я сделал это:

 required init(from decoder: Decoder) throws {

    try super.init(from: decoder)

    let values = try decoder.container(keyedBy: CodingKeys.self)
    total = try values.decode(Int.self, forKey: .total)
  }

private enum CodingKeys: String, CodingKey
{
    case total

}

Ответ 3

Я был в состоянии сделать его работу, сделав свой базовый класс и подклассы соответствуют Decodable вместо Codable. Если бы я использовал Codable он Codable бы странным образом, например, получая EXC_BAD_ACCESS при доступе к полю подкласса, но отладчик мог без проблем отображать все значения подкласса.

Кроме того, передача superDecoder в базовый класс в super.init() не сработала. Я только что передал декодер из подкласса в базовый класс.

Ответ 4

Как насчет использования следующего способа?

protocol Parent: Codable {
    var inheritedProp: Int? {get set}
}

struct Child: Parent {
    var inheritedProp: Int?
    var title: String?

    enum CodingKeys: String, CodingKey {
        case inheritedProp = "inherited_prop"
        case title = "short_title"
    }
}

Дополнительная информация о композиции: http://mikebuss.com/2016/01/10/interfaces-vs-inheritance/

Ответ 5

Вот библиотека TypePreservingCodingAdapter для этого (может быть установлена с Cocoapods или SwiftPackageManager).

Приведенный ниже код компилируется и прекрасно работает с Swift 4.2. К сожалению, для каждого подкласса вам нужно самостоятельно реализовать кодирование и декодирование свойств.

import TypePreservingCodingAdapter
import Foundation

// redeclared your types with initializers
class Server: Codable {
    var id: Int?

    init(id: Int?) {
        self.id = id
    }
}

class Development: Server {
    var name: String?
    var userId: Int?

    private enum CodingKeys: String, CodingKey {
        case name
        case userId
    }

    init(id: Int?, name: String?, userId: Int?) {
        self.name = name
        self.userId = userId
        super.init(id: id)
    }

    required init(from decoder: Decoder) throws {
        try super.init(from: decoder)
        let container = try decoder.container(keyedBy: CodingKeys.self)

        name = try container.decodeIfPresent(String.self, forKey: .name)
        userId = try container.decodeIfPresent(Int.self, forKey: .userId)
    }

    override func encode(to encoder: Encoder) throws {
        try super.encode(to: encoder)
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(name, forKey: .name)
        try container.encode(userId, forKey: .userId)
    }

}

// create and adapter
let adapter = TypePreservingCodingAdapter()
let encoder = JSONEncoder()
let decoder = JSONDecoder()

// inject it into encoder and decoder
encoder.userInfo[.typePreservingAdapter] = adapter
decoder.userInfo[.typePreservingAdapter] = adapter

// register your types with adapter
adapter.register(type: Server.self).register(type: Development.self)


let server = Server(id: 1)
let development = Development(id: 2, name: "dev", userId: 42)

let servers: [Server] = [server, development]

// wrap specific object with Wrap helper object
let data = try! encoder.encode(servers.map { Wrap(wrapped: $0) })

// decode object back and unwrap them force casting to a common ancestor type
let decodedServers = try! decoder.decode([Wrap].self, from: data).map { $0.wrapped as! Server }

// check that decoded object are of correct types
print(decodedServers.first is Server)     // prints true
print(decodedServers.last is Development) // prints true