A problem encountered when using Codable with generics and optional

PersonT<Int?> and Person should be equivalent classes, but why do they produce different results when decoding?

import Foundation

struct PersonT<T: Codable>: Codable {
    let name: String
    let age: Int
    let extra: T
}

struct Person: Codable {
    let name: String
    let age: Int
    let extra: Int?
}

let testData = """
{
    "name": "test",
    "age": 18
}
"""

let data = try? JSONDecoder().decode(PersonT<Int?>.self, from: testData.data(using: .utf8)!)
print(data ?? "<nil>")
// got <nil>


let data1 = try? JSONDecoder().decode(Person.self, from: testData.data(using: .utf8)!)
print(data1 ?? "<nil>")
// got Person(name: "test", age: 18, extra: nil)

You can encode a PersonT<Int?> value to see its output, which is the expected input for decoding. My experiment shows it's {"age":18,"name":"test","extra":null}. Not sure why the difference.

Swift generics is not C++ style substitution. When the compiler synthesizes PersonT.init(decoder:), the only constraint it sees is T: Codable, there's just not enough local information to treat extra: T as optional.

You can use this example to understand the local and static aspects in Swift generics.

// this will always return false
func isOptional<T>(_ t: T) -> Bool { t == nil }

let a: Int = 3
let b: Int? = 4

isOptional(a)
isOptional(b)
3 Likes

Get it.
Thanks a lot~