web-dev-qa-db-fra.com

Clé optionnelle décodable rapide

(Ceci fait suite à cette question: Utilisation du protocole Decodable avec des clés multiples .)

J'ai le code Swift suivant:

let additionalInfo = try values.nestedContainer(keyedBy: UserInfoKeys.self, forKey: .age)
age = try additionalInfo.decodeIfPresent(Int.self, forKey: .realage)

Je sais que si j'utilise decodeIfPresent et que je n'ai pas la propriété, il la gérera toujours correctement s'il s'agit d'une variable facultative.

Par exemple, le JSON suivant fonctionne pour l’analyser à l’aide du code ci-dessus.

{
    "firstname": "Test",
    "lastname": "User",
    "age": {"realage": 29}
}

Et le JSON suivant fonctionne également.

{
    "firstname": "Test",
    "lastname": "User",
    "age": {"notrealage": 30}
}

Mais ce qui suit ne fonctionne pas.

{
    "firstname": "Test",
    "lastname": "User"
}

Comment puis-je faire fonctionner les 3 exemples? Existe-t-il quelque chose de similaire à decodeIfPresent pour nestedContainer?

7
Charlie Fish

Vous pouvez utiliser la fonction KeyedDecodingContainer suivante:

func contains(_ key: KeyedDecodingContainer.Key) -> Bool

Retourne une valeur Bool indiquant si le décodeur contient une valeur associée à la clé donnée. La valeur associée à la clé donnée peut être une valeur nulle en fonction du format de données.

Par exemple, pour vérifier si la clé "age" existe avant demandant le conteneur imbriqué correspondant:

struct Person: Decodable {
    let firstName, lastName: String
    let age: Int?

    enum CodingKeys: String, CodingKey {
        case firstName = "firstname"
        case lastName = "lastname"
        case age
    }

    enum AgeKeys: String, CodingKey {
        case realAge = "realage"
        case fakeAge = "fakeage"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        self.firstName = try values.decode(String.self, forKey: .firstName)
        self.lastName = try values.decode(String.self, forKey: .lastName)

        if values.contains(.age) {
            let age = try values.nestedContainer(keyedBy: AgeKeys.self, forKey: .age)
            self.age = try age.decodeIfPresent(Int.self, forKey: .realAge)
        } else {
            self.age = nil
        }
    }
}
28
Paulo Mattos

J'avais ce problème et j'ai trouvé cette solution, au cas où cela serait utile à quelqu'un d'autre:

let ageContainer = try? values.nestedContainer(keyedBy: AgeKeys.self, forKey: .age)
self.age = try ageContainer?.decodeIfPresent(Int.self, forKey: .realAge)

La clé est dans essayez? values.nestedContainer... si vous avez un conteneur optionnel. C’est que vous n’avez pas besoin de vérifier si contains est la clé.

1
TomCobo

Pouvez-vous essayer de coller votre échantillon JSON dans quicktype pour voir quels types il en déduit? Sur la base de votre question, j'ai collé vos échantillons et obtenu:

struct UserInfo: Codable {
    let firstname: String
    let age: Age?
    let lastname: String
}

struct Age: Codable {
    let realage: Int?
}

Faire en sorte que les options UserInfo.age et Age.realage fonctionnent, si c'est ce que vous essayez d'accomplir.

0
David Siegel