Codable creating Array with nil instead of default value

When I am declaring the Array variable as let it works just fine, but when I change the declaration from let to var, instead of taking the default value that is provided(an empty array), it initialises it as nil

import Foundation

struct Model: Codable {
    let name: String? = "John"
    let data: [String]? = []
}

let data = """
{
    "name": "John"
}
""".data(using: .utf8)!

if let myObject = try? JSONDecoder().decode(Model.self, from: data) {
    dump(myObject)
}

Output

▿ __lldb_expr_130.Model

▿ name: Optional("John")
some: "John"

▿ data: Optional([ ])
some: 0 elements

But when I change

let data: [String]? = [ ]

to

var data: [String]? = [ ]

the output changes to

▿ __lldb_expr_132.Model

▿ name: Optional("John")
some: "John"

data: nil

Hello,

This is because Codable does not decode initialized let properties:

import Foundation

struct Model: Codable {
    let name: String? = "John"
    let data: [String]? = []
}

let data = """
{
    "name": "Michelle"
}
""".data(using: .utf8)!

if let myObject = try? JSONDecoder().decode(Model.self, from: data) {
    dump(myObject)
}

Output (see how "Michelle" was discarded):

▿ __lldb_expr_3.Model
  ▿ name: Optional("John")
    - some: "John"
  ▿ data: Optional([])
    - some: 0 elements

When the let properties are not initialized, Codable does what you expect:

struct Model: Codable {
    let name: String?
    let data: [String]?
}

let data = """
{
    "name": "Michelle"
}
""".data(using: .utf8)!

if let myObject = try? JSONDecoder().decode(Model.self, from: data) {
    dump(myObject)
}

Output:

▿ __lldb_expr_6.Model
  ▿ name: Optional("Michelle")
    - some: "Michelle"
  - data: nil
2 Likes

More on initialized let properties: compare our code above with:

import Foundation

struct Model: Codable {
    let name: String? = "John"
    let data: [String]? = []
    
    // Compiler error:
    // Immutable value 'self.name' may only be initialized once
    // Immutable value 'self.data' may only be initialized once
    init(name: String?, data: [String]?) {
        self.name = name
        self.data = data
    }
}

Some will advise you to generally prefer var declarations of struct properties, and to rely on variable declarations in order to be guaranteed with immutability:

struct Model {
    var name: String? = "John"
    var data: [String]? = []
}

// Immutable name and data
let model: Model = ...

You also have private(set) and other language niceties to control mutability.

So... let properties have their use cases, but they can come in the way, as you can see. Maybe consider using them only as a last resort. YMMV.

1 Like