Home > Enterprise >  How swift implement the default Decodable for struct?
How swift implement the default Decodable for struct?

Time:12-14

struct Person: Decodable {
    let firstName: String
}
var data = """
{"firstName": "Fai"}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let parsed = try decoder.decode(Person.self, from: data)

JSONDecoder will decode the data, which is comfirmed to Decodable protocol.

So I want to know how swift implement this. But I can not get any idea in the source code: https://github.com/apple/swift/blob/main/stdlib/public/core/Codable.swift

The Decodable protocol only need to implement an init(from decoder: Decoder) function. If I am going to do it, I will make an extension for struct:

extension struct: Decodable {
  init(from decoder: Decoder) {...}
}

But when I delete the Decodable on my example, the compiler give errors:

Instance method 'decode(_:from:)' requires that 'Person' conform to 'Decodable'

So this is not the swift way to implement this. How's swift way? And where's the source code?

CodePudding user response:

You can see what the compiler writes for you using -print-ast:

echo 'struct Person: Decodable {
    let firstName: String
}' | swiftc -print-ast -

This will output most of the auto-generated code (it should include all of the Codable conformances, but there are a few other kinds of auto-generated code that won't include their implementation):

internal struct Person : Decodable {
  internal let firstName: String
  private enum CodingKeys : CodingKey {
    case firstName
    @_implements(Equatable, ==(_:_:)) fileprivate static func __derived_enum_equals(_ a: Person.CodingKeys, _ b: Person.CodingKeys) -> Bool {
      private var index_a: Int
      switch a {
      case .firstName:

        index_a = 0
      }
      private var index_b: Int
      switch b {
      case .firstName:

        index_b = 0
      }
      return index_a == index_b
    }
    fileprivate func hash(into hasher: inout Hasher) {
      private var discriminator: Int
      switch self {
      case .firstName:

        discriminator = 0
      }
      hasher.combine(discriminator)
    }
    private init?(stringValue: String) {
      switch stringValue {
      case "firstName":

        self = Person.CodingKeys.firstName
        default:

        return nil
      }

    }
    private init?(intValue: Int) {
      return nil
    }
    fileprivate var hashValue: Int {
      get {
        return _hashValue(for: self)
      }
    }
    fileprivate var intValue: Int? {
      get {
        return nil
      }
    }
    fileprivate var stringValue: String {
      get {
        switch self {
        case .firstName:

          return "firstName"
        }
      }
    }
  }
  internal init(firstName: String)
  internal init(from decoder: Decoder) throws {
    @_hasInitialValue private let container: KeyedDecodingContainer<Person.CodingKeys> = try decoder.container(keyedBy: Person.CodingKeys.self)

    self.firstName = try container.decode(String.self, forKey: Person.CodingKeys.firstName)

  }
}

For the full implementation details, see DerivedConformanceCodable.cpp. Probably of most interest to your question is deriveBodyDecodable_init.

CodePudding user response:

Swift automatically synthesizes the Decodable protocol for structs that have no custom implementation of the init(from:) initializer. This means that you can use a struct as a Decodable type without implementing the init(from:) initializer yourself, as long as the struct's properties match the keys in the encoded data.

For example, suppose you have the following struct:

struct User: Decodable {
    let id: Int
    let name: String
    let email: String
}

This struct conforms to the Decodable protocol because it has no custom implementation of the init(from:) initializer. When you try to decode data that contains the keys id, name, and email, Swift will automatically use the property names and types of the User struct to decode the data and create a new User instance.

You can decode data using the decode(_:from:) method of the JSONDecoder class, like this:

let json = """
{
    "id": 123,
    "name": "John Doe",
    "email": "[email protected]"
}
"""

let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: json.data(using: .utf8)!)
  • Related