14
votes

Avertissement Xcode: la propriété immuable ne sera pas décodée car elle est déclarée avec une valeur initiale qui ne peut pas être écrasée

En exécutant Xcode 12, mon projet Swift 5 Xcode a maintenant des avertissements chaque fois qu'un type Decodable ou Codable déclare une constante let avec une valeur initiale.

    var number: Int = 42

La propriété immuable ne sera pas décodée car elle est déclarée avec une valeur initiale qui ne peut pas être écrasée

Xcode suggère de changer le let en var :

Correction: rendre la propriété mutable à la place

struct ExampleItem: Decodable {
    let number: Int = 42 // warning
}

Il suggère également le correctif:

Correction: définissez la valeur initiale via l'initialiseur ou définissez explicitement une énumération CodingKeys comprenant une casse `` titre '' pour faire taire cet avertissement

Quel est le but de ce nouvel avertissement? Doit-il être pris en compte ou ignoré? Ce type d'avertissement peut-il être réduit au silence?

Le correctif de Xcode doit-il être implémenté? Ou bien y a-t-il une meilleure solution?


0 commentaires

3 Réponses :


4
votes

Cet avertissement apparaît parce que les propriétés immuables avec des valeurs initiales ne participent pas au décodage - après tout, elles sont immuables et ont une valeur initiale, ce qui signifie que la valeur initiale ne sera jamais modifiée.

Par exemple, considérez ce code:

let json = """
{}
"""
let decoder = JSONDecoder()

struct VarModel: Decodable {
    var value: String = "1"
}

let varModel = try! decoder.decode(VarModel.self, from: json.data(using: .utf8)!)

Cela affichera en fait le Model(value: "1") , même si le json que nous lui avons donné avait la value "2" .

En fait, vous n'avez même pas besoin de fournir la valeur des données que vous décodez, car elles ont de toute façon une valeur initiale!

struct VarModel: Decodable {
    var value: String = "1"
}
let json = """
{"value": "2"}
"""
let varModel = try! decoder.decode(VarModel.self, from: json.data(using: .utf8)!)
print(varModel) // "VarModel(value: "2")"

Changer la valeur en var signifie qu'il décodera correctement:

let json = """
{}
"""
let decoder = JSONDecoder()
let model = try! decoder.decode(Model.self, from: json.data(using: .utf8)!)
print(model) // prints "Model(value: "1")"

Si vous voyez cette erreur, cela signifie que votre code n'a jamais correctement analysé la propriété en question lors du décodage. Si vous le changez en var, la propriété sera analysée correctement, ce qui peut être ce que vous voulez - cependant, vous devez vous assurer que les données que vous décodez ont toujours ce jeu de clés. Par exemple, cela lèvera une exception (et plantera puisque nous utilisons try! ):

struct Model: Decodable {
    let value: String = "1"
}

let json = """
{"value": "2"}
"""
let decoder = JSONDecoder()
let model = try! decoder.decode(Model.self, from: json.data(using: .utf8)!)
print(model)

En conclusion, la suggestion de Xcode est probablement viable dans de nombreux cas, mais vous devez évaluer au cas par cas si le changement de la propriété en var cassera la fonctionnalité de votre application.

Si vous souhaitez que la propriété renvoie toujours la valeur initiale codée en dur (ce qui se passe actuellement), envisagez d'en faire une propriété calculée ou une variable différée.


0 commentaires

29
votes

L'explication de Noé est correcte. C'est une source courante de bogues et ce qui se passe n'est pas immédiatement évident en raison du comportement «magique» de la synthèse Codable, c'est pourquoi j'ai ajouté cet avertissement au compilateur, car il attire votre attention sur le fait que la propriété ne sera pas décodée et vous oblige à l'appeler explicitement si c'est le comportement attendu.

Comme l'explique le correctif, vous avez quelques options si vous souhaitez désactiver cet avertissement - celui que vous choisissez dépend du comportement exact que vous souhaitez:

  1. Passez la valeur initiale via un init :
struct ExampleItem: Decodable {
  let number: Int = 42
  
  private enum CodingKeys: CodingKey {}
}

Cela permettra au number d'être décodé, mais vous pouvez également transmettre des instances de ExampleItem où la valeur par défaut est utilisée.

Vous pouvez également l'utiliser directement dans init place, lors du décodage:

struct ExampleItem: Decodable {
  var number: Int = 42
}

Cela permettra au number d'être décodé, mais utilisez 42 comme valeur par défaut si le décodage échoue.

  1. Faites de la propriété une var , bien que vous puissiez également en faire une private(set) var :
struct ExampleItem: Decodable {
  let number: Int
    
  private enum CodingKeys: String, CodingKey {
    case number
  }
    
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    number = try container.decodeIfPresent(Int.self, forKey: .number) ?? 42
  }
}

En faire un var permettra au number d'être décodé, mais cela permettra également aux appelants de le modifier. En le marquant plutôt comme private(set) var , vous pouvez l'interdire si vous le souhaitez.

  1. Définissez une énumération CodingKeys explicite:
struct ExampleItem: Decodable {
  let number: Int
    
  init(number: Int = 42) {
    self.number = number
  }
}

Cela empêchera le décodage du number . Étant donné que l'énumération n'a pas de cas, cela indique clairement au compilateur qu'il n'y a aucune propriété que vous souhaitez décoder.


10 commentaires

Super d'avoir une réponse de la personne qui a implémenté la nouvelle erreur. Puis-je vous demander comment vous avez trouvé cette question si rapidement et avec un nouveau compte SO?!


@pkamb J'ai vu un lien vers cette question sur Twitter. Je n'avais pas encore de compte S / O, alors j'en ai créé un et j'ai laissé une réponse.


Quelle est la manière la plus simple de faire taire l'avertissement dans le cas suivant? (id n'est pas dans le fichier json.) struct JsonCard: Codable {let id = UUID () let a: String let b: String}


@HugoF Vous pouvez déclarer explicitement l'énumération CodingKeys et exclure la propriété id.


Des considérations sont-elles prises pour désactiver cet avertissement? Il y a des cas où cela est souhaitable, mais maintenant nous voyons des avertissements pour un code valide. Nous comprenons le fonctionnement de Codable et aimerions avoir un moyen d'utiliser le bon code SWIFT sans avertissement. (Je peux dessiner des parallèles avec discardableResult qui permet à l'appelant d'ignorer le résultat sans générer d'avertissement) L'écriture de vos propres clés de codage est beaucoup plus sujette aux erreurs que de compter sur le code généré par le compilateur. (En général, moins de lignes de code, c'est toujours mieux)


@Sam Il existe plusieurs façons de faire taire cet avertissement comme décrit ci-dessus et l'écriture explicite des CodingKeys n'est que l'une d'entre elles. Swift n'offre pas de moyen général de faire taire un avertissement particulier.


Je vois que, cependant, le comportement que nous souhaitons est que la valeur soit encodée dans le json, mais ne soit pas décodée. Le fonctionnement de Codable a beaucoup de sens si vous pensez à quoi ressemblera le code généré automatiquement. Mais ce qui me dérange, c'est qu'il ne nous reste aucune option pour que le code généré par le compilateur suive une convention assez courante pour les tableaux json hétérogènes. (J'espère que je n'ai pas l'air trop déchaîné 😅)


tbh écrire les CodingKeys n'est pas si satisfaisant. Habituellement, faire taire un avertissement implique de faire quelque chose d'aussi simple que l'ajout as Any ou l'ajout de crochets autour de quelque chose. L'écriture des CodingKeys est plus complexe et signifie qu'un champ ajouté à l'avenir pourrait être oublié. Si Swift avait un système approprié pour supprimer les avertissements, ce serait très bien. Maintenant, je dois écrire les CodingKeys pour environ 70 structures ...


notre cas d'utilisation étant que nous avons des structures codables, lorsque l'encodage des valeurs doit toujours être une chaîne spécifique, lors du décodage, cela n'a pas d'importance, la valeur doit toujours être la même, nous ne pouvons donc pas les omettre de CodingKeys car alors ils ne seront pas encodés. Je ne pense vraiment pas qu'un avertissement devrait être ici quand il est juste informatif pour le développeur et qu'il n'y a aucun moyen réel de le supprimer


Je suis d'accord. J'ai un cas d'utilisation où je ne veux pas de cet avertissement. let id = UUID() La structure contient beaucoup plus de variables. Il semble étrange que je doive créer explicitement des init / ou des énumérations juste pour faire taire quelque chose dont j'ai besoin ... Peut-être le gérer d'une autre manière?



0
votes

Solution: définissez une énumération CodingKeys explicite pour empêcher le décodage de l' id . Par exemple,

struct Course: Identifiable, Decodable {
  let id = UUID()
  let name: String

  private enum CodingKeys: String, CodingKey {
    case name
  }
  
  init(name: String) { self.name = name }
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    let name = try container.decodeIfPresent(String.self, forKey: .name)
    self.name = name ?? "default-name"
  }
}


0 commentaires