Voici mon JSON
{
"id": 1,
"user": {
"user_name": "Tester",
"real_info": {
"full_name":"Jon Doe"
}
},
"reviews_count": [
{
"count": 4
}
]
}
Voici la structure dans laquelle je veux l'enregistrer (incomplète)
struct ServerResponse: Decodable {
var id: String
var username: String
var fullName: String
var reviewCount: Int
enum CodingKeys: String, CodingKey {
case id,
// How do i get nested values?
}
}
J'ai regardé la documentation d'Apple sur le décodage des structures imbriquées, mais je ne comprends toujours pas comment faire correctement les différents niveaux du JSON. Toute aide sera très appréciée.
Encodable
pour laServerResponse
structure en suivant la même approche. Est-ce même possible?ServerResponse
a moins de données queRawServerResponse
. Vous pouvez capturer l'RawServerResponse
instance, la mettre à jour avec les propriétés deServerResponse
, puis générer le JSON à partir de cela. Vous pouvez obtenir une meilleure aide en publiant une nouvelle question avec le problème spécifique auquel vous êtes confronté.Afin de résoudre votre problème, vous pouvez diviser votre
RawServerResponse
implémentation en plusieurs parties logiques (en utilisant Swift 5).#1. Implémenter les propriétés et les clés de codage requises
import Foundation struct RawServerResponse { enum RootKeys: String, CodingKey { case id, user, reviewCount = "reviews_count" } enum UserKeys: String, CodingKey { case userName = "user_name", realInfo = "real_info" } enum RealInfoKeys: String, CodingKey { case fullName = "full_name" } enum ReviewCountKeys: String, CodingKey { case count } let id: Int let userName: String let fullName: String let reviewCount: Int }
# 2. Définir la stratégie de décodage pour la
id
propriétéextension RawServerResponse: Decodable { init(from decoder: Decoder) throws { // id let container = try decoder.container(keyedBy: RootKeys.self) id = try container.decode(Int.self, forKey: .id) /* ... */ } }
# 3. Définir la stratégie de décodage pour la
userName
propriétéextension RawServerResponse: Decodable { init(from decoder: Decoder) throws { /* ... */ // userName let userContainer = try container.nestedContainer(keyedBy: UserKeys.self, forKey: .user) userName = try userContainer.decode(String.self, forKey: .userName) /* ... */ } }
# 4. Définir la stratégie de décodage pour la
fullName
propriétéextension RawServerResponse: Decodable { init(from decoder: Decoder) throws { /* ... */ // fullName let realInfoKeysContainer = try userContainer.nestedContainer(keyedBy: RealInfoKeys.self, forKey: .realInfo) fullName = try realInfoKeysContainer.decode(String.self, forKey: .fullName) /* ... */ } }
# 5. Définir la stratégie de décodage pour la
reviewCount
propriétéextension RawServerResponse: Decodable { init(from decoder: Decoder) throws { /* ...*/ // reviewCount var reviewUnkeyedContainer = try container.nestedUnkeyedContainer(forKey: .reviewCount) var reviewCountArray = [Int]() while !reviewUnkeyedContainer.isAtEnd { let reviewCountContainer = try reviewUnkeyedContainer.nestedContainer(keyedBy: ReviewCountKeys.self) reviewCountArray.append(try reviewCountContainer.decode(Int.self, forKey: .count)) } guard let reviewCount = reviewCountArray.first else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath + [RootKeys.reviewCount], debugDescription: "reviews_count cannot be empty")) } self.reviewCount = reviewCount } }
Mise en œuvre complète
import Foundation struct RawServerResponse { enum RootKeys: String, CodingKey { case id, user, reviewCount = "reviews_count" } enum UserKeys: String, CodingKey { case userName = "user_name", realInfo = "real_info" } enum RealInfoKeys: String, CodingKey { case fullName = "full_name" } enum ReviewCountKeys: String, CodingKey { case count } let id: Int let userName: String let fullName: String let reviewCount: Int }
extension RawServerResponse: Decodable { init(from decoder: Decoder) throws { // id let container = try decoder.container(keyedBy: RootKeys.self) id = try container.decode(Int.self, forKey: .id) // userName let userContainer = try container.nestedContainer(keyedBy: UserKeys.self, forKey: .user) userName = try userContainer.decode(String.self, forKey: .userName) // fullName let realInfoKeysContainer = try userContainer.nestedContainer(keyedBy: RealInfoKeys.self, forKey: .realInfo) fullName = try realInfoKeysContainer.decode(String.self, forKey: .fullName) // reviewCount var reviewUnkeyedContainer = try container.nestedUnkeyedContainer(forKey: .reviewCount) var reviewCountArray = [Int]() while !reviewUnkeyedContainer.isAtEnd { let reviewCountContainer = try reviewUnkeyedContainer.nestedContainer(keyedBy: ReviewCountKeys.self) reviewCountArray.append(try reviewCountContainer.decode(Int.self, forKey: .count)) } guard let reviewCount = reviewCountArray.first else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath + [RootKeys.reviewCount], debugDescription: "reviews_count cannot be empty")) } self.reviewCount = reviewCount } }
Usage
let jsonString = """ { "id": 1, "user": { "user_name": "Tester", "real_info": { "full_name":"Jon Doe" } }, "reviews_count": [ { "count": 4 } ] } """ let jsonData = jsonString.data(using: .utf8)! let decoder = JSONDecoder() let serverResponse = try! decoder.decode(RawServerResponse.self, from: jsonData) dump(serverResponse) /* prints: ▿ RawServerResponse #1 in __lldb_expr_389 - id: 1 - user: "Tester" - fullName: "Jon Doe" - reviewCount: 4 */
la source
struct
vous utiliséenum
avec des clés. ce qui est beaucoup plus élégant 👍Plutôt que d'avoir une grande
CodingKeys
énumération avec toutes les clés dont vous aurez besoin pour décoder le JSON, je vous conseillerais de diviser les clés pour chacun de vos objets JSON imbriqués, en utilisant des énumérations imbriquées pour préserver la hiérarchie:// top-level JSON object keys private enum CodingKeys : String, CodingKey { // using camelCase case names, with snake_case raw values where necessary. // the raw values are what's used as the actual keys for the JSON object, // and default to the case name unless otherwise specified. case id, user, reviewsCount = "reviews_count" // "user" JSON object keys enum User : String, CodingKey { case username = "user_name", realInfo = "real_info" // "real_info" JSON object keys enum RealInfo : String, CodingKey { case fullName = "full_name" } } // nested JSON objects in "reviews" keys enum ReviewsCount : String, CodingKey { case count } }
Cela facilitera le suivi des clés à chaque niveau de votre JSON.
Maintenant, en gardant à l'esprit que:
Un conteneur à clé est utilisé pour décoder un objet JSON et est décodé avec un
CodingKey
type conforme (tel que ceux que nous avons définis ci-dessus).Un conteneur sans clé est utilisé pour décoder un tableau JSON et est décodé séquentiellement (c'est-à-dire que chaque fois que vous appelez une méthode de décodage ou de conteneur imbriqué dessus, il passe à l'élément suivant du tableau). Voir la deuxième partie de la réponse pour savoir comment vous pouvez en parcourir une.
Après avoir obtenu votre conteneur à clé de niveau supérieur du décodeur avec
container(keyedBy:)
(comme vous avez un objet JSON au niveau supérieur), vous pouvez utiliser à plusieurs reprises les méthodes:nestedContainer(keyedBy:forKey:)
pour obtenir un objet imbriqué à partir d'un objet pour une clé donnéenestedUnkeyedContainer(forKey:)
pour obtenir un tableau imbriqué à partir d'un objet pour une clé donnéenestedContainer(keyedBy:)
pour obtenir le prochain objet imbriqué à partir d'un tableaunestedUnkeyedContainer()
pour obtenir le prochain tableau imbriqué à partir d'un tableauPar exemple:
struct ServerResponse : Decodable { var id: Int, username: String, fullName: String, reviewCount: Int private enum CodingKeys : String, CodingKey { /* see above definition in answer */ } init(from decoder: Decoder) throws { // top-level container let container = try decoder.container(keyedBy: CodingKeys.self) self.id = try container.decode(Int.self, forKey: .id) // container for { "user_name": "Tester", "real_info": { "full_name": "Jon Doe" } } let userContainer = try container.nestedContainer(keyedBy: CodingKeys.User.self, forKey: .user) self.username = try userContainer.decode(String.self, forKey: .username) // container for { "full_name": "Jon Doe" } let realInfoContainer = try userContainer.nestedContainer(keyedBy: CodingKeys.User.RealInfo.self, forKey: .realInfo) self.fullName = try realInfoContainer.decode(String.self, forKey: .fullName) // container for [{ "count": 4 }] – must be a var, as calling a nested container // method on it advances it to the next element. var reviewCountContainer = try container.nestedUnkeyedContainer(forKey: .reviewsCount) // container for { "count" : 4 } // (note that we're only considering the first element of the array) let firstReviewCountContainer = try reviewCountContainer.nestedContainer(keyedBy: CodingKeys.ReviewsCount.self) self.reviewCount = try firstReviewCountContainer.decode(Int.self, forKey: .count) } }
Exemple de décodage:
let jsonData = """ { "id": 1, "user": { "user_name": "Tester", "real_info": { "full_name":"Jon Doe" } }, "reviews_count": [ { "count": 4 } ] } """.data(using: .utf8)! do { let response = try JSONDecoder().decode(ServerResponse.self, from: jsonData) print(response) } catch { print(error) } // ServerResponse(id: 1, username: "Tester", fullName: "Jon Doe", reviewCount: 4)
Itérer dans un conteneur sans clé
Considérant le cas où vous voulez
reviewCount
être an[Int]
, où chaque élément représente la valeur de la"count"
clé dans le JSON imbriqué:"reviews_count": [ { "count": 4 }, { "count": 5 } ]
Vous devrez parcourir le conteneur imbriqué sans clé, obtenir le conteneur à clé imbriqué à chaque itération et décoder la valeur de la
"count"
clé. Vous pouvez utiliser lacount
propriété du conteneur sans clé afin de pré-allouer le tableau résultant, puis laisAtEnd
propriété pour l'itérer.Par exemple:
struct ServerResponse : Decodable { var id: Int var username: String var fullName: String var reviewCounts = [Int]() // ... init(from decoder: Decoder) throws { // ... // container for [{ "count": 4 }, { "count": 5 }] var reviewCountContainer = try container.nestedUnkeyedContainer(forKey: .reviewsCount) // pre-allocate the reviewCounts array if we can if let count = reviewCountContainer.count { self.reviewCounts.reserveCapacity(count) } // iterate through each of the nested keyed containers, getting the // value for the "count" key, and appending to the array. while !reviewCountContainer.isAtEnd { // container for a single nested object in the array, e.g { "count": 4 } let nestedReviewCountContainer = try reviewCountContainer.nestedContainer( keyedBy: CodingKeys.ReviewsCount.self) self.reviewCounts.append( try nestedReviewCountContainer.decode(Int.self, forKey: .count) ) } } }
la source
I would advise splitting the keys for each of your nested JSON objects up into multiple nested enumerations, thereby making it easier to keep track of the keys at each level in your JSON
?CodingKeys
énumération avec toutes les clés dont vous aurez besoin pour décoder votre objet JSON, vous devriez les diviser en plusieurs énumérations pour chaque objet JSON - par exemple, dans le code ci-dessus que nous avonsCodingKeys.User
avec les clés pour décoder l'objet JSON utilisateur ({ "user_name": "Tester", "real_info": { "full_name": "Jon Doe" } }
), donc juste les clés pour"user_name"
&"real_info"
.reviews_count
qui est un tableau de dictionnaire. Actuellement, le code fonctionne comme prévu. My reviewsCount n'a jamais qu'une seule valeur dans le tableau. Mais que faire si je voulais réellement un tableau de review_count, alors je devrais simplement le déclarervar reviewCount: Int
comme un tableau, n'est-ce pas? ->var reviewCount: [Int]
. Et puis j'aurais besoin de modifier également l'ReviewsCount
énumération, n'est-ce pas?Int
, mais un tableau d'objets JSON qui ont chacun uneInt
valeur pour une clé donnée - donc ce que vous devez faire est de parcourir le conteneur sans clé et obtenez tous les conteneurs à clé imbriqués, en décodant unInt
pour chacun (puis en les ajoutant à votre tableau), par exemple gist.github.com/hamishknight/9b5c202fe6d8289ee2cb9403876a1b41De nombreuses bonnes réponses ont déjà été postées, mais il existe une méthode plus simple non encore décrite à l'OMI.
Lorsque les noms de champ JSON sont écrits à l'aide de,
snake_case_notation
vous pouvez toujours utiliser lecamelCaseNotation
dans votre fichier Swift.Vous avez juste besoin de définir
Après cette ligne ☝️, Swift fera automatiquement correspondre tous les
snake_case
champs du JSON auxcamelCase
champs du modèle Swift.Par exemple
Voici le code complet
1. Rédaction du modèle
struct Response: Codable { let id: Int let user: User let reviewsCount: [ReviewCount] struct User: Codable { let userName: String struct RealInfo: Codable { let fullName: String } } struct ReviewCount: Codable { let count: Int } }
2. Réglage du décodeur
let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase
3. Décodage
do { let response = try? decoder.decode(Response.self, from: data) print(response) } catch { debugPrint(error) }
la source
let file = "data.json" guard let url = Bundle.main.url(forResource: "data", withExtension: "json") else{ fatalError("Failed to locate \(file) in bundle.") } guard let data = try? Data(contentsOf: url) else{ fatalError("Failed to locate \(file) in bundle.") } let yourObject = try? JSONDecoder().decode(YourModel.self, from: data)
la source
jsonStr
, vous pouvez l'utiliser à la place des deuxguard let
s ci-dessus:guard let jsonStrData: Data? = jsonStr.data(using: .utf8)! else { print("error") }
puis convertissez-jsonStrData
vous en votre structure comme décrit ci-dessus sur lalet yourObject
ligneVous pouvez également utiliser la bibliothèque KeyedCodable que j'ai préparée. Cela nécessitera moins de code. Dis moi ce que tu penses de ça.
struct ServerResponse: Decodable, Keyedable { var id: String! var username: String! var fullName: String! var reviewCount: Int! private struct ReviewsCount: Codable { var count: Int } mutating func map(map: KeyMap) throws { var id: Int! try id <<- map["id"] self.id = String(id) try username <<- map["user.user_name"] try fullName <<- map["user.real_info.full_name"] var reviewCount: [ReviewsCount]! try reviewCount <<- map["reviews_count"] self.reviewCount = reviewCount[0].count } init(from decoder: Decoder) throws { try KeyedDecoder(with: decoder).decode(to: &self) } }
la source