81 votes

Encode la valeur nil comme nulle avec JSONEncoder

Je suis en utilisant Swift 4 JSONEncoder. J'ai un Codable struct avec une option de propriété, et j'aimerais que cette propriété à apparaître comme null de la valeur du produit de données JSON lorsque la valeur est nil. Toutefois, JSONEncoder les rejets de la propriété et de ne pas ajouter à la sortie JSON. Est-il un moyen de configurer JSONEncoder , de sorte qu'il conserve la clé et la définit null dans ce cas?

Exemple

L'extrait de code ci-dessous génère {"number":1}, mais je préfère comme ça pour me donner des {"string":null,"number":1}:

struct Foo: Codable {
  var string: String? = nil
  var number: Int = 1
}

let encoder = JSONEncoder()
let data = try! encoder.encode(Foo())
print(String(data: data, encoding: .utf8)!)

65voto

Rob Napier Points 92148

Oui, mais vous devrez écrire votre propre encode(to:) mise en œuvre, vous ne pouvez pas utiliser la fonction d'auto-généré.

struct Foo: Codable {
    var string: String? = nil
    var number: Int = 1

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(number, forKey: .number)
        try container.encode(string, forKey: .string)
    }
}

Codage d'une option directement encoder une valeur nulle, comme vous êtes à la recherche pour.

Si c'est une utilisation importante de cas pour vous, vous pouvez envisager l'ouverture d'un défaut à bugs.swift.org pour demander un nouveau OptionalEncodingStrategy drapeau pour être ajouté sur JSONEncoder fonction des DateEncodingStrategy, etc. (Voir ci-dessous pourquoi c'est probablement impossible à mettre en oeuvre rapide aujourd'hui, mais entrer dans le système de suivi est toujours utile Swift évolue.)


Edit: Pour Paulo questions ci-dessous, ce dépêches pour le générique de l' encode<T: Encodable> version Optional conforme Encodable. Ceci est mis en œuvre dans Codable.swift de cette façon:

extension Optional : Encodable /* where Wrapped : Encodable */ {
    @_inlineable // FIXME(sil-serialize-all)
    public func encode(to encoder: Encoder) throws {
        assertTypeIsEncodable(Wrapped.self, in: type(of: self))

        var container = encoder.singleValueContainer()
        switch self {
        case .none: try container.encodeNil()
        case .some(let wrapped): try (wrapped as! Encodable).__encode(to: &container)
        }
    }
}

Cette encapsule l'appel à l' encodeNil, et je pense que laisser stdlib poignée de Options comme juste un autre Encodable est mieux que de les traiter comme un cas particulier dans notre propre encodeur et en appelant encodeNil - mêmes.

Une autre question évidente est pourquoi il fonctionne de cette façon en premier lieu. Depuis Facultatif est Encodable, et les Encodable conformité code pour toutes les propriétés, pourquoi ne "coder toutes les propriétés à la main" travailler différemment? La réponse est que la conformité générateur comprend un cas spécial pour les Options:

// Now need to generate `try container.encode(x, forKey: .x)` for all
// existing properties. Optional properties get `encodeIfPresent`.
...

if (varType->getAnyNominal() == C.getOptionalDecl() ||
    varType->getAnyNominal() == C.getImplicitlyUnwrappedOptionalDecl()) {
  methodName = C.Id_encodeIfPresent;
}

Cela signifie que la modification de ce comportement nécessiterait de modifier la fonction d'auto-généré de conformité, pas JSONEncoder (ce qui signifie aussi qu'il est probablement très difficile à faire configurable aujourd'hui Swift....)

0voto

guido Points 1699

J'ai rencontré le même problème. Résolu par la création d'un dictionnaire à partir de la structure sans l'aide de JSONEncoder. Vous pouvez le faire en un temps relativement de façon universelle. Voici mon code:

struct MyStruct: Codable {
    let id: String
    let regionsID: Int?
    let created: Int
    let modified: Int
    let removed: Int?


    enum CodingKeys: String, CodingKey, CaseIterable {
        case id = "id"
        case regionsID = "regions_id"
        case created = "created"
        case modified = "modified"
        case removed = "removed"
    }

    var jsonDictionary: [String : Any] {
        let mirror = Mirror(reflecting: self)
        var dic = [String: Any]()
        var counter = 0
        for (name, value) in mirror.children {
            let key = CodingKeys.allCases[counter]
            dic[key.stringValue] = value
            counter += 1
        }
        return dic
    }
}

extension Array where Element == MyStruct {
    func jsonArray() -> [[String: Any]] {
        var array = [[String:Any]]()
        for element in self {
            array.append(element.jsonDictionary)
        }
        return array
    }
}

Vous pouvez le faire sans le CodingKeys (si la table des noms d'attributs sur le côté serveur sont égales à votre struct noms de propriété). Dans ce cas il suffit d'utiliser le nom de miroir.les enfants.

Si vous avez besoin d'CodingKeys n'oubliez pas d'ajouter le CaseIterable protocole. Qui rend possible l'utilisation de la allCases variable.

Soyez prudent avec des structures imbriquées: E. g. si vous avez une propriété avec une coutume struct type, vous avez besoin de convertir un dictionnaire de trop. Vous pouvez le faire dans la boucle for.

Le Tableau de rallonge est nécessaire si vous souhaitez créer un tableau de MyStruct dictionnaires.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X