Xcode warning: Immutable property will not be decoded because it is declared with an initial value which cannot be overwritten - swift

Running Xcode 12, my Swift 5 Xcode project now has warnings whenever a Decodable or Codable type declares a let constant with an initial value.
struct ExampleItem: Decodable {
let number: Int = 42 // warning
}
Immutable property will not be decoded because it is declared with an initial value which cannot be overwritten
Xcode suggests changing the let to a var:
Fix: Make the property mutable instead
var number: Int = 42
It also suggests the fix:
Fix: Set the initial value via the initializer or explicitly define a CodingKeys enum including a 'title' case to silence this warning
What is the purpose of this new warning? Should it be heeded, or ignored? Can this type of warning be silenced?
Should Xcode's fix be implemented? Or is there a better solution?

Noah's explanation is correct. It’s a common source of bugs and it's not immediately obvious what’s happening due to the “magical” behaviour of Codable synthesis, which is why I added this warning to the compiler, since it brings your attention to the fact that the property won't be decoded and makes you explicitly call it out if that's the expected behaviour.
As the fix-it explains, you have a couple of options if you want to silence this warning - which one you choose depends on the exact behaviour you want:
Pass the initial value via an init:
struct ExampleItem: Decodable {
let number: Int
init(number: Int = 42) {
self.number = number
}
}
This will allow number to be decoded, but you can also pass around instances of ExampleItem where the default value is used.
You can also use it directly inside init instead, during decoding:
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
}
}
This will allow number to be decoded, but use 42 as the default value if the decoding fails.
Make the property a var, although you can also make it a private(set) var:
struct ExampleItem: Decodable {
var number: Int = 42
}
Making it a var will allow number to be decoded, but it will also allow callers to modify it. By marking it as private(set) var instead, you can disallow this if you want.
Define an explicit CodingKeys enum:
struct ExampleItem: Decodable {
let number: Int = 42
private enum CodingKeys: CodingKey {}
}
This will prevent number from being decoded. Since the enum has no cases, this makes it clear to the compiler that there are no properties that you want to decode.

This warning appears because immutable properties with initial values don't participate in decoding - after all, they're immutable and they have an initial value, which means that initial value will never be changed.
For example, consider this code:
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)
This will actually print Model(value: "1"), even though the json we gave it had value as "2".
In fact, you don't even need to provide the value in the data you're decoding, since it has an initial value anyway!
let json = """
{}
"""
let decoder = JSONDecoder()
let model = try! decoder.decode(Model.self, from: json.data(using: .utf8)!)
print(model) // prints "Model(value: "1")"
Changing the value to a var means it will decode correctly:
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")"
If you're seeing this error, it means your code has never correctly parsed the property in question when decoding. If you change it to a var, the property will be parsed correctly, which might be what you want - however, you should make sure that the data you're decoding always has that key set. For example, this will throw an exception (and crash since we're using try!):
let json = """
{}
"""
let decoder = JSONDecoder()
struct VarModel: Decodable {
var value: String = "1"
}
let varModel = try! decoder.decode(VarModel.self, from: json.data(using: .utf8)!)
In conclusion, Xcode's suggestion is probably viable in many cases, but you should evaluate on a case by case basis whether changing the property to a var will break your app's functionality.
If you want the property to always return the hard-coded initial value (which is what's happening right now), consider making it a computed property or a lazy var.

Solution: define an explicit CodingKeys enum to prevent id from decoded.
For example,
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"
}
}

The suggested workarounds by #SuyashSrijan suppresses the warning but may also lead to further developer errors.
I've written an alternative work around here:
public struct IdentifierWrapper<T>: Identifiable {
public let id = UUID()
public let value: T
}
Usage:
struct Model: Codable, Identifiable {
public let name: String
}
let wrapper = IdentifierWrapper(value: Model(name: "ptrkstr"))

Related

Is there a shorter way of declaring CodingKeys?

Say you have a struct for a model of your API response. Let's say it has 50 members. However, 5-7 members are non-standard casing, you could have AUsernAme or _BTmember, but the rest are all snake case credit_score or status_code.
Rather than writing all members like this:
struct MyStruct {
let aUserName: String
// +50 more...
private enum CodingKeys: String, CodingKey {
case aUserName = "AUsernAme"
// +50 more...
}
}
Is there a way that we can write it like this?
struct MyStruct {
#CodingKey("AUsernAme") let aUserName: String
let creditScore: Int
// +50 more ...
}
Edit: I guess this is not possible with the current Swift version, but does anyone know if this would somehow be included in the future versions of Swift?
The solution which Sweeper provided is a great solution to your problem, but IMO it may display great complexity to your problem and to the next developers who will read this code.
If I were you, I would just stick to writing all the CodingKeys for simplicity. If your worry is writing a lot of lines of cases, you can write all the cases that doesn't need custom keys in one line and just add the keys with unusual/non-standard casing on new lines:
case property1, property2, property3, property4, property5...
case property50 = "_property50"
And since you mentioned that the rest are in snake case, not sure if you know yet, but we have JSONDecoder.KeyDecodingStrategy.convertFromSnakeCase.
Hope this helps `tol! :)
How about setting a custom keyDecodingStrategy just before you decode instead?
struct AnyCodingKey: CodingKey, Hashable {
var stringValue: String
init(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init(intValue: Int) {
self.intValue = intValue
self.stringValue = "\(intValue)"
}
}
let mapping = [
"AUsernAme": "aUserName",
// other mappings...
]
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ codingPath in
let key = codingPath[0].stringValue
guard let mapped = mapping[key] else { return codingPath.last! }
return AnyCodingKey(stringValue: mapped)
})
This assumes your JSON has a single level flat structure. You can make this into an extension:
extension JSONDecoder.KeyDecodingStrategy {
static func mappingRootKeys(_ dict: [String: String]) -> JSONDecoder.KeyDecodingStrategy {
return .custom { codingPath in
let key = codingPath[0].stringValue
guard let mapped = dict[key] else { return codingPath.last! }
return AnyCodingKey(stringValue: mapped)
}
}
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .mappingRootKeys(mapping)
If your JSON has more levels, you can change the type of the dictionary to [JSONPath: String], where JSONPath is a type that you can create that represents a key in a nested JSON. Then add a bit of code that converts the coding path, which is just an array of coding keys, to JSONPath. This should not be hard to write on your own.
A simple way is to just use [AnyCodingKey] as JSONPath, but there are many other ways too, and I encourage you to experiment and find the one you like the best.
typealias JSONPath = [AnyCodingKey]
extension AnyCodingKey {
init(codingKey: CodingKey) {
if let int = codingKey.intValue {
self.init(intValue: int)
} else {
self.init(stringValue: codingKey.stringValue)
}
}
}
extension JSONDecoder.KeyDecodingStrategy {
static func mappingRootKeys(_ dict: [JSONPath: String]) -> JSONDecoder.KeyDecodingStrategy {
return .custom { codingPath in
guard let mapped = dict[codingPath.map(AnyCodingKey.init(codingKey:))] else { return codingPath.last! }
return AnyCodingKey(stringValue: mapped)
}
}
}
let mapping = [
[AnyCodingKey(stringValue: "AUsernAme")]: "aUserName"
]
It is not possible to use a property wrapper for this. Your property wrapper #CodingKey("AUsernAme") let aUserName: String will be compiled to something like this (as per here):
private var _aUserName: CodingKey<String> = CodingKey("AUsernAme")
var aUserName: String {
get { _aUserName.wrappedValue }
set { _aUserName.wrappedValue = newValue }
}
There are two main problems with this:
Assuming you don't want to write init(from:) for all the 50+ properties in MyStruct, code will be synthesised to decode it, assigning to its _aUserName property. You only have control over the init(from:) initialiser of the CodingKey property wrapper, and you cannot do anything about how MyStruct is decoded in there. If MyStruct is contained in another struct:
struct AnotherStruct: Decodable {
let myStruct: MyStruct
}
Then you can indeed control the coding keys used to decode myStruct by marking it with a property wrapper. You can do whatever you want in the decoding process by implementing the property wrapper's init(from:), which brings us to the second problem:
The coding key you pass to the CodingKey property wrapper is passed via an initialiser of the form init(_ key: String). But you control the decoding via the initialiser init(from decoder: Decoder) because that is what will be called when the struct is decoded. In other words, there is no way for you to send the key mappings to the property wrapper.

Trying to make a class codable in Swift but it is not the correct format?

I am creating a class that conforms to codable.
I have this:
import Foundation
class Attribute : Decodable {
var number: Int16
var label: String?
var comments: String?
init(number:Int16, label:String?, comments:String?) {
self.number = number
self.label = label
self.comments = comments
}
// Everything from here on is generated for you by the compiler
required init(from decoder: Decoder) throws {
let keyedContainer = try decoder.container(keyedBy: CodingKeys.self)
number = try keyedContainer.decode(Int16.self, forKey: .number)
label = try keyedContainer.decode(String.self, forKey: .label)
comments = try keyedContainer.decode(String.self, forKey: .comments)
}
enum CodingKeys: String, CodingKey {
case number
case label
case comments
}
}
extension Attribute: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(number, forKey: .number)
try container.encode(label, forKey: .label)
try container.encode(comments, forKey: .comments)
}
}
This is apparently fine.
I create an instance of Attribute and encode it using:
let newAttribute = Attribute.init(number:value.number, label:value.label, comments:value.shortcut)
Then I create an array of these attributes and encode that array using
let array = try JSONEncoder().encode(array)
This will encode the array of Attribute to Data.
Then I try to convert the Data object back to the array of Attribute using this:
let array = try JSONDecoder().decode(Attribute.self, from: data) as! Array<Attribute>
First error I get is this:
Cast from 'Attribute' to unrelated type 'Array< Attribute>' always fails
If I remove the cast part I catch this error when the decode tries...
Optional("The data isn’t in the correct format.")
Any ideas?
You need to pass in the array to decode, don't pass in the array element type, then try to force-cast that to an array, that doesn't make any sense. YourType and Array<YourType> are two different and completely unrelated types, so you cannot cast one to the other and you need to use the specific type when calling JSONDecoder.decode(_:from:).
let array = try JSONDecoder().decode([Attribute].self, from: data)
Btw as already pointed out in your previous question, there is no need to manually write the init(from:) and encode(to:) methods or the CodingKeys enum since for your simple type, the compiler can auto-synthesise all of those for you. Also, if you used a struct instead of class, you'd also get the member wise initialiser for free.

Decode a Swift type that is a wrapped Codable type with an extra Codable property

I've got a Codable type, let's say Car, that is defined as:
struct Car: Codable {
let age: Int
let color: String
}
I can encode/decode this just fine.
With my persistence system, when an object is stored it gets assigned an _id property, which is a String, e.g. 5cae04b533376609456d40ed.
As such, when I read the Data from the persistent store and then try to decode it there are extra bytes in there that represent the _id property and its associated String value.
I'm not in control of the various types that can be encoded and stored in the store. The only restriction on them is that they are Codable.
What I want to be able to do is decode the Data that I get when reading from the store (with the _id stuff included) into a type that is something like Wrapped<T: Codable>, which would be defined as something like (in the simplest form):
struct Wrapped<T: Codable> {
let _id: String
let value: T
}
However, I'm not sure to go about this.
One attempt I made was to to define a custom decode function but that didn't get very far as I can't seem to access the T type's CodingKeys, which makes things, as far as I can tell, impossible with that approach.
Maybe there's another approach that would make things work as I'd like?
You can write a custom decode function for your Wrapped type that parses out the _id and then passes the decoder along to the wrapped type so it can decode it's own properties:
struct Wrapped<T: Codable>: Decodable {
let _id: String
let value: T
private enum CodingKeys: String, CodingKey {
case _id
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
_id = try container.decode(String.self, forKey: ._id)
value = try T(from: decoder)
}
}
You can simply declare that the _id property shouldn't decoded by defining your custom CodingKeys and omitting _id from there. You also need to assign a default value to the non-decoded properties (_id in your case) if you want to use the automatically synthetised initializer.
For a concrete type:
struct Car: Codable {
let age: Int
let color: String
let _id:Int = 0
enum CodingKeys: String, CodingKey {
case age, color
}
}
You can achieve this for all your persisted types.
If you don't want to create the CodingKeys enum for all persisted types, you could follow the generic wrapper type approach you started, but you'll need to create custom init(from:) and encode(to:) methods.
struct Persisted<T: Codable>: Codable {
let _id:Int = 0
let value:T
init(from decoder:Decoder) throws {
value = try decoder.singleValueContainer().decode(T.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
}

How can I decode when I don't know the type, with class inheritance?

I have a base class Action, which is an Operation. It has a bunch of crufty Operation stuff in it (KVO and all that). The base class itself doesn't actually need to encode/decode anything.
class Action : Operation, Codable {
var _executing = false
...
}
I have a bunch of Action sub-classes, like DropboxUploadAction, which are directly instantiated with an Input struct they define:
let actionInput = DropboxUploadAction.Input.init(...)
ActionManager.shared.run(DropboxUploadAction.init(actionInput, data: binaryData), completionBlock: nil)
Here's what the subclasses look like:
class DropboxUploadAction : Action {
struct Input : Codable {
var guid: String
var eventName: String
var fileURL: URL?
var filenameOnDropbox: String
var share: Bool
}
struct Output : Codable {
var sharedFileLink: String?
var dropboxPath: String?
}
var input: Input
var output: Output
...
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
input = try values.decode(Input.self, forKey: .input)
output = try values.decode(Output.self, forKey: .output)
let superDecoder = try values.superDecoder()
try super.init(from: superDecoder)
}
fileprivate enum CodingKeys: String, CodingKey {
case input
case output
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(input, forKey: .input)
try container.encode(output, forKey: .output)
try super.encode(to: container.superEncoder())
}
}
When some situations occur such as a loss of internet connectivity, these classes need to be serialized to disk for later. That's fine, because at the time I have references to them and can encode them with JSONEncoder().encode(action), no problem.
But later when I want to deserialize them, I need to specify the type of the class and I don't know what it is. I have some data and I know it can be decoded to a class that inherits from Action, but I don't know which subclass it is. I'm loathe to encode that in the filename. Is there some way to decode it as the base class Action, then in the decode() method of Action, somehow detect the proper class and redirect?
In the past I've used NSKeyedUnarchiver.setClass() to handle this. But I don't know how to do that with Swift 4's Codable, and I understand that NSCoding is deprecated now so I shouldn't use NSKeyedUnarchiver anymore...
If it helps: I have a struct Types : OptionSet, Codable which each subclass returns, so I don't have to use the name of the class as its identity.
Thanks for any help!
Uhhh NSCoding isn't deprecated. We still use it when instantiating UIViewControllers from storyboard via init(coder:).
Also, if you still don't want to use NSCoding, you can just store the Input, Output and Types to a struct and serialize that to disk instead.
struct SerializedAction {
let input: Input
let output: Output
let type: Type
}
When needed, you can decode that and decide the correct Action to initialize with your input/output via the type property.
class DropboxAction: Action {
...
init(input: Input, output: Output) {
...
}
}
You don't necessarily need to encode the entire Action object.

What is difference between optional and decodeIfPresent when using Decodable for JSON Parsing?

I am using Codable protocol from Swift 4 first time, I am not able to understand use of decodeIfPresent from Decodable.
/// Decodes a value of the given type for the given key, if present.
///
/// This method returns `nil` if the container does not have a value associated with `key`, or if the value is null. The difference between these states can be distinguished with a `contains(_:)` call.
///
/// - parameter type: The type of value to decode.
/// - parameter key: The key that the decoded value is associated with.
/// - returns: A decoded value of the requested type, or `nil` if the `Decoder` does not have an entry associated with the given key, or if the value is a null value.
/// - throws: `DecodingError.typeMismatch` if the encountered encoded value is not convertible to the requested type.
public func decodeIfPresent(_ type: String.Type, forKey key: KeyedDecodingContainer.Key) throws -> String?
Here it suggest that it returns nil, if value not present with associated key. If this is the only reason , then how it differ from optional property, as optional variable also set to nil if value is not present in response.
There's a subtle, but important difference between these two lines of code:
// Exhibit 1
foo = try container.decode(Int?.self, forKey: .foo)
// Exhibit 2
foo = try container.decodeIfPresent(Int.self, forKey: .foo)
Exhibit 1 will parse:
{
"foo": null,
"bar": "something"
}
but not:
{
"bar": "something"
}
while exhibit 2 will happily parse both. So in normal use cases for JSON parsers you'll want decodeIfPresent for every optional in your model.
Yes, #Sweeper's comment makes a sense.
I will try to explain it according to my understanding.
public class User : Decodable{
public var firstName:String
public var lastName:String
public var middleName:String?
public var address:String
public var contactNumber:String
public enum UserResponseKeys: String, CodingKey{
case firstName = "first_name"
case lastName = "last_name"
case middleName = "middle_name"
case address = "address"
case contactNumber = "contact_number"
}
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: UserResponseKeys.self)
self.firstName = try container.decode(String.self, forKey: .firstName)
self.lastName = try container.decode(String.self, forKey: .lastName)
self.middleName = try container.decodeIfPresent(String.self, forKey: .middleName)
self.address = try container.decode(String.self, forKey: .address)
self.contactNumber = try container.decode(String.self, forKey: .contactNumber)
}
}
Above is my User class, in which I marked middleName as optional parameter, because it may possible that JSON response may not provide middleName key-value pair in response, so we can use decodeIfPresent.
self.middleName = try container.decodeIfPresent(String.self, forKey: .middleName)
While for others variables which are mandatory fields so we are sure that no need to use of optional for that. We used only decode for that as that method does not return optional.
public func decode(_ type: String.Type, forKey key: KeyedDecodingContainer.Key) throws -> String
Above decode function returns String while decodeIfPresent returns String?, so we can use optional variable to store that.
So final conclusion is that if you are not sure of service response contract or you may dealing with any third party services where JSON response and parameters may change without your knowledge then you can use decodeIfPresent so it can handle absence of particular parameter in response and set value as nil.
I think it makes sense to use decodeifPresent rather than an optional property if you want to use a default value for a property that could be missing from the JSON.
For example, let's examine 3 situations:
1. All the keys are present in the JSON:
Let's suppose you must decode this JSON:
{
"project_names": ["project1", "project2", "project3"],
"is_pro": true
}
You can use this struct:
struct Program: Codable {
let projectNames: [String]
let isPro: Bool
}
and you will get a Program object with a isPro value equal to true.
(I suppose your decoder keyDecodingStrategy is .convertFromSnakeCase in the rest of this example)
2. Some keys are missing in the JSON and you're ok to have an optional in Swift:
{
"project_names": ["project1", "project2", "project3"]
}
You can now use this struct:
struct Program: Codable {
let projectNames: [String]
var isPro: Bool?
}
and you will get a Program object with a isPro value equal to nil.
If the JSON looked like this:
{
"project_names": ["project1", "project2", "project3"],
"is_pro": true
}
then isPro would be a Bool? with value true.
Maybe that's what you want, but probably you would like to have a Bool with a default value of false. That's where decodeIfPresent could be useful.
3. Some keys are missing in the JSON and you want a non-optional property with a default value in Swift:
If your struct looks like this:
struct Program: Codable {
let projectNames: [String]
var isPro: Bool = false
}
then you will get a parsing error if the "is_pro" attribute is not present in your JSON. Because Codable expects to find a value to parse a Bool property.
In that situation, a good idea would be to have an initializer with decodeIfPresent, like so:
struct Program: Codable {
let projectNames: [String]
let isPro: Bool
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.projectNames = try container.decode([String].self, forKey: .projectNames)
self.isPro = try container.decodeIfPresent(Bool.self, forKey: .isPro) ?? false
}
}
This allows you to have the best of both worlds:
your struct has a Bool, not a Bool? property
you are still able to parse a JSON that does NOT contain the "is_pro" field
you can get a default value of false if the field is not present in the JSON.