/ / Swift 4 décodable avec des clés inconnues jusqu'au moment du décodage - json, swift4, décodable

Swift 4 décodable avec des clés inconnues jusqu'au moment du décodage - json, swift4, décodable

Comment le protocole Swift 4 Decodable gère-t-il un dictionnaire contenant une clé dont le nom n'est pas connu avant l'exécution? Par exemple:

  [
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
}
]

Nous avons ici un éventail de dictionnaires; le premier a des clés categoryName et Trending, tandis que le second a des clés categoryName et Comedy. La valeur de la categoryName key me dit le nom de la seconde clé. Comment puis-je exprimer cela en utilisant Decodable?

Réponses:

27 pour la réponse № 1

La clé est dans la façon dont vous définissez la CodingKeys propriété. Bien que ce soit le plus souvent un enum il peut être tout ce qui est conforme à la CodingKey protocole. Et pour créer des clés dynamiques, vous pouvez appeler une fonction statique:

struct Category: Decodable {
struct Detail: Decodable {
var category: String
var trailerPrice: String
var isFavorite: Bool?
var isWatchlist: Bool?
}

var name: String
var detail: Detail

struct CodingKeys: CodingKey {
var intValue: Int?
var stringValue: String

init?(intValue: Int) { self.intValue = intValue; self.stringValue = "(intValue)" }
init?(stringValue: String) { self.stringValue = stringValue }

static let name = CodingKeys(stringValue: "categoryName")!
static func makeKey(name: String) -> CodingKeys {
return CodingKeys(stringValue: name)!
}
}

init(from coder: Decoder) throws {
let container = try coder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.detail = try container.decode([Detail].self, forKey: .makeKey(name: name)).first!
}
}

Usage:

let jsonData = """
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
}
]
""".data(using: .utf8)!

let categories = try! JSONDecoder().decode([Category].self, from: jsonData)

(J'ai changé isFavourit dans le JSON pour isFavourite depuis que je pensais que c'était une mauvaise prononciation. Il est assez facile d’adapter le code si ce n’est pas le cas)


4 pour la réponse № 2

Vous pouvez écrire une structure personnalisée qui fonctionne comme un objet CodingKeys et l'initialiser avec une chaîne telle qu'elle extrait la clé que vous avez spécifiée:

private struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}

Ainsi, une fois que vous savez quelle est la clé souhaitée, vous pouvez dire (dans le init(from:) passer outre:

let key = // whatever the key name turns out to be
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)

Donc ce que j'ai fini par faire, c'est deux des conteneurs du décodeur - l’un utilisant l’énumération standard CodingKeys pour extraire la valeur du "categoryName" key, et un autre utilisant la structure CK pour extraire la valeur de la clé dont nous venons d'apprendre le nom:

init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CodingKeys.self)
self.categoryName = try! con.decode(String.self, forKey:.categoryName)
let key = self.categoryName
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
}

Voici donc toute ma structure Decodable:

struct ResponseData : Codable {
let categoryName : String
let unknown : [Inner]
struct Inner : Codable {
let category : String
let trailerPrice : String
let isFavourit : String?
let isWatchList : String?
}
private enum CodingKeys : String, CodingKey {
case categoryName
}
private struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CodingKeys.self)
self.categoryName = try! con.decode(String.self, forKey:.categoryName)
let key = self.categoryName
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
}
}

Et voici le banc d’essai:

    let json = """
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
}
]
"""
let myjson = try! JSONDecoder().decode(
[ResponseData].self,
from: json.data(using: .utf8)!)
print(myjson)

Et voici la sortie de la déclaration print, prouvant que nous avons correctement rempli nos structures:

[JustPlaying.ResponseData(
categoryName: "Trending",
unknown: [JustPlaying.ResponseData.Inner(
category: "Trending",
trailerPrice: "",
isFavourit: nil,
isWatchList: nil)]),
JustPlaying.ResponseData(
categoryName: "Comedy",
unknown: [JustPlaying.ResponseData.Inner(
category: "Comedy",
trailerPrice: "",
isFavourit: nil,
isWatchList: nil)])
]

Bien sûr, dans la vraie vie, nous aurions un peu de gestion des erreurs, sans aucun doute!


MODIFIER Plus tard, j'ai réalisé (en partie grâce àLa réponse de CodeDifferent) que je n’avais pas besoin de deux conteneurs; Je peux éliminer l'énumération CodingKeys et ma structure CK peut faire tout le travail! C'est un fabricant de clés à usage général:

init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CK.self)
self.categoryName = try! con.decode(String.self, forKey:CK(stringValue:"categoryName")!)
let key = self.categoryName
self.unknown = try! con.decode([Inner].self, forKey: CK(stringValue:key)!)
}

0 pour la réponse № 3

aussi, a posé cette question. Voici ce qui a finalement été trouvé pour ce Json:

let json = """
{
"BTC_BCN":{
"last":"0.00000057",
"percentChange":"0.03636363",
"baseVolume":"47.08463318"
},
"BTC_BELA":{
"last":"0.00001281",
"percentChange":"0.07376362",
"baseVolume":"5.46595029"
}
}
""".data(using: .utf8)!

Nous faisons une telle structure:

struct Pair {
let name: String
let details: Details

struct Details: Codable {
let last, percentChange, baseVolume: String
}
}

Quand décoder:

if let pairsDictionary = try? JSONDecoder().decode([String: Pair.Details].self, from: json) {

var pairs: [Pair] = []
for (name, details) in pairsDictionary {
let pair = Pair(name: name, details: details)
pairs.append(pair)
}

print(pairs)
}

Il est également possible d’appeler non pas pair.details.baseVolume, mais pair.baseVolume:

struct Pair {
......
var baseVolume: String { return details.baseVolume }
......

Ou écrivez init personnalisé:

struct Pair {
.....
let baseVolume: String
init(name: String, details: Details) {
self.baseVolume = details.baseVolume
......