La raison pourquoi votre premier exemple ne compile pas (et votre deuxième accidents) c'est parce que les protocoles ne sont pas conformes à eux-mêmes – Tag
n'est pas un type qui est conforme à l' Codable
, donc ni est - [Tag]
. Par conséquent, Article
ne pas obtenir un auto-générés Codable
de conformité, de ne pas toutes ses propriétés sont conformes à l' Codable
.
L'encodage et le décodage uniquement les propriétés énumérées dans le protocole
Si vous voulez juste pour coder et décoder les propriétés énumérées dans le protocole, une solution serait de tout simplement utiliser un AnyTag
type de gomme qui a ces propriétés, et peuvent alors fournir à l' Codable
de conformité.
Vous pouvez alors Article
contenir un tableau de ce type effacé wrapper, plutôt que d' 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)
}
Les résultats suivants chaîne JSON:
{
"title" : "Article Title",
"tags" : [
{
"type" : "author",
"value" : "Author Tag Value"
},
{
"type" : "genre",
"value" : "Genre Tag Value"
}
]
}
et peut être décodé comme suit:
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")
L'encodage et le décodage de tous les biens de la mise en conformité de type
Si toutefois vous avez besoin d'encoder et de le décoder tous les biens de l' Tag
de la conformation de type, vous aurez probablement souhaitez stocker les informations de type dans le JSON en quelque sorte.
Je voudrais utiliser un enum
afin de faire:
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
}
}
}
Ce qui est mieux que la simple utilisation de chaînes de caractères pour représenter les types, comme le compilateur peut vérifier que nous avons fourni un metatype pour chaque cas.
Ensuite vous avez juste à changer l' Tag
protocole, tel qu'il nécessite conforme types à mettre en œuvre un static
de la propriété qui décrit leur type:
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
}
Ensuite, nous devons nous adapter à la mise en œuvre de la effacées wrapper pour encoder et décoder l' TagType
avec la base 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))
}
}
Nous sommes à l'aide d'un super codeur/décodeur afin de s'assurer que la propriété des clés pour la conformation type n'entrent pas en conflit avec la clé utilisée pour coder le type. Par exemple, l'encodage JSON ressemblera à ceci:
{
"type" : "author",
"base" : {
"value" : "Author Tag Value",
"foo" : 56.7
}
}
Si toutefois vous le savez il n'y a pas de conflit, et que vous voulez les propriétés de coder/décoder au même niveau que le "type" clés, tels que le JSON ressemble à ceci:
{
"type" : "author",
"value" : "Author Tag Value",
"foo" : 56.7
}
Vous pouvez transmettre decoder
au lieu de container.superDecoder(forKey: .base)
& encoder
au lieu de container.superEncoder(forKey: .base)
dans le code ci-dessus.
Comme une option étape, on peut ensuite personnaliser l' Codable
de la mise en œuvre de l' Article
plutôt que de compter sur une auto-généré en conformité avec l' tags
propriété de type [AnyTag]
, nous pouvons fournir notre propre mise en œuvre que les boîtes d'un [Tag]
en [AnyTag]
avant l'encodage, puis unbox pour le décodage:
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)
}
}
Ensuite, cela nous permet d'avoir l' tags
bien être de type [Tag]
, plutôt que d' [AnyTag]
.
Maintenant, nous pouvons encoder et de décoder n'importe quel Tag
conformes type qui est répertorié dans notre TagType
enum:
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)
}
Qui sorties de la chaîne 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"
}
}
]
}
et puis peut être décodé comme suit:
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")