Причина, по которой ваш первый пример не компилируется (а второй дает сбой), заключается в том, что протоколы не соответствуют самим себе - Tag
не является типом, который соответствует Codable
, следовательно, и [Tag]
. Следовательно, Article
не получает автоматически сгенерированное соответствие Codable
, поскольку не все его свойства соответствуют Codable
.
Кодирование и декодирование только тех свойств, которые указаны в протоколе.
Если вы просто хотите кодировать и декодировать свойства, перечисленные в протоколе, одним из решений было бы просто использовать ластик типа AnyTag
, который просто хранит эти свойства и затем может обеспечить соответствие Codable
.
Затем вы можете Article
хранить массив этой обертки со стиранием типа, а не Tag
:
struct AnyTag : Tag, Codable {
let type: String
let value: String
init(_ base: Tag) {
self.type = base.type
self.value = base.value
}
}
struct Article: Codable {
let tags: [AnyTag]
let title: String
}
let tags: [Tag] = [
AuthorTag(value: "Author Tag Value"),
GenreTag(value:"Genre Tag Value")
]
let article = Article(tags: tags.map(AnyTag.init), title: "Article Title")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(article)
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
Что выводит следующую строку JSON:
{
"title" : "Article Title",
"tags" : [
{
"type" : "author",
"value" : "Author Tag Value"
},
{
"type" : "genre",
"value" : "Genre Tag Value"
}
]
}
и расшифровывается так:
let decoded = try JSONDecoder().decode(Article.self, from: jsonData)
print(decoded)
// Article(tags: [
// AnyTag(type: "author", value: "Author Tag Value"),
// AnyTag(type: "genre", value: "Genre Tag Value")
// ], title: "Article Title")
Кодирование и декодирование всех свойств соответствующего типа
Однако, если вам нужно кодировать и декодировать каждое свойство данного Tag
соответствующего типа, вы, вероятно, захотите каким-то образом сохранить информацию о типе в JSON.
Для этого я бы использовал enum
:
enum TagType : String, Codable {
// be careful not to rename these – the encoding/decoding relies on the string
// values of the cases. If you want the decoding to be reliant on case
// position rather than name, then you can change to enum TagType : Int.
// (the advantage of the String rawValue is that the JSON is more readable)
case author, genre
var metatype: Tag.Type {
switch self {
case .author:
return AuthorTag.self
case .genre:
return GenreTag.self
}
}
}
Это лучше, чем просто использовать простые строки для представления типов, поскольку компилятор может проверить, что мы предоставили метатип для каждого случая.
Затем вам просто нужно изменить протокол Tag
так, чтобы он требовал соответствующих типов для реализации свойства static
, которое описывает их тип:
protocol Tag : Codable {
static var type: TagType { get }
var value: String { get }
}
struct AuthorTag : Tag {
static var type = TagType.author
let value: String
var foo: Float
}
struct GenreTag : Tag {
static var type = TagType.genre
let value: String
var baz: String
}
Затем нам нужно адаптировать реализацию обертки со стиранием типа, чтобы кодировать и декодировать TagType
вместе с базовым Tag
:
struct AnyTag : Codable {
var base: Tag
init(_ base: Tag) {
self.base = base
}
private enum CodingKeys : CodingKey {
case type, base
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(TagType.self, forKey: .type)
self.base = try type.metatype.init(from: container.superDecoder(forKey: .base))
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type(of: base).type, forKey: .type)
try base.encode(to: container.superEncoder(forKey: .base))
}
}
Мы используем суперкодер / декодер, чтобы гарантировать, что ключи свойств для данного соответствующего типа не конфликтуют с ключом, используемым для кодирования типа. Например, закодированный JSON будет выглядеть так:
{
"type" : "author",
"base" : {
"value" : "Author Tag Value",
"foo" : 56.7
}
}
Однако если вы знаете, что конфликта не будет, и хотите, чтобы свойства кодировались / декодировались на том же уровне, что и ключ "type", чтобы JSON выглядел следующим образом:
{
"type" : "author",
"value" : "Author Tag Value",
"foo" : 56.7
}
Вы можете передать decoder
вместо container.superDecoder(forKey: .base)
& encoder
вместо container.superEncoder(forKey: .base)
в приведенном выше коде.
В качестве необязательного шага мы могли бы затем настроить Codable
реализацию Article
так, чтобы вместо того, чтобы полагаться на автоматически сгенерированное соответствие со свойством tags
, имеющим тип [AnyTag]
, мы могли бы предоставить нашу собственную реализацию, которая объединяет [Tag]
в [AnyTag]
перед кодированием, а затем распаковать для декодирования:
struct Article {
let tags: [Tag]
let title: String
init(tags: [Tag], title: String) {
self.tags = tags
self.title = title
}
}
extension Article : Codable {
private enum CodingKeys : CodingKey {
case tags, title
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.tags = try container.decode([AnyTag].self, forKey: .tags).map { $0.base }
self.title = try container.decode(String.self, forKey: .title)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(tags.map(AnyTag.init), forKey: .tags)
try container.encode(title, forKey: .title)
}
}
Это позволяет нам иметь свойство tags
типа [Tag]
, а не [AnyTag]
.
Теперь мы можем кодировать и декодировать любой Tag
соответствующий тип, указанный в нашем TagType
перечислении:
let tags: [Tag] = [
AuthorTag(value: "Author Tag Value", foo: 56.7),
GenreTag(value:"Genre Tag Value", baz: "hello world")
]
let article = Article(tags: tags, title: "Article Title")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(article)
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
Что выводит строку JSON:
{
"title" : "Article Title",
"tags" : [
{
"type" : "author",
"base" : {
"value" : "Author Tag Value",
"foo" : 56.7
}
},
{
"type" : "genre",
"base" : {
"value" : "Genre Tag Value",
"baz" : "hello world"
}
}
]
}
и затем можно расшифровать так:
let decoded = try JSONDecoder().decode(Article.self, from: jsonData)
print(decoded)
// Article(tags: [
// AuthorTag(value: "Author Tag Value", foo: 56.7000008),
// GenreTag(value: "Genre Tag Value", baz: "hello world")
// ],
// title: "Article Title")
person
Hamish
schedule
10.06.2017
AuthorTag
иGenreTag
отдельные типы? У них обоих одинаковый интерфейс, и вы, кажется, просто используете свойствоtype
, чтобы различать их (хотя на самом деле это, вероятно, должно бытьenum
). - person Hamish   schedule 08.06.2017Type 'Article' does not conform to protocol 'Decodable'
и'Encodable'
- person Code Different   schedule 10.06.2017Tag
не соответствуетCodable
(и, следовательно, тоже[Tag]
), потому что протоколы не подчиняются себе. Подумайте, соответствует лиTag
Codable
- что должно произойти при попытке декодера декодировать в произвольныйTag
? Какой конкретный тип нужно создать? - person Hamish   schedule 10.06.2017Decodable
иEncodable
. Ошибку можно легко выбросить .. gist.github .com / anonymous / 74d2723e18444344f3635d403e8bf6b8 и это похоже на ошибку - person Anish Parajuli 웃   schedule 10.06.2017[Tag]
не соответствуетCodable
. - person Hamish   schedule 10.06.2017