Swift 4 Codable

Swift 4 Codable

CA.swift #3 WWDC17 報告会 (Jun 19, 2017)
https://cyberagent.connpass.com/event/58796/

--------------------------------------------------------------------------------
2017/06/21 Updated:
Q. `Codable` の Stored properties と`CodingKey` cases が1対1対応しない場合は?🤔
(例:MyCodableのあるプロパティを"複数のキーの値"から算出したい、など)

A. `enum CodingKeys`, `init`, `func encode`すべて実装すればOK
(参考: https://twitter.com/kitasuke/status/877404773900722176)

Eac0bf787b5279aca5e699ece096956e?s=128

Yasuhiro Inami

June 19, 2017
Tweet

Transcript

  1. Swift 4 Codable 2017/06/19 CA.swift #3 WWDC17ใࠂձ Yasuhiro Inami /

    @inamiy
  2. None
  3. None
  4. None
  5. None
  6. Session 212 What's New in Foundation https://developer.apple.com/videos/play/ wwdc2017/212/

  7. Swift 4 Codable* Conversion between Swift data structure and archived

    formats * swift-DEVELOPMENT-SNAPSHOT-2017-06-17-a (Xcode 9 Beta 1ʙ) ݱࡏ
  8. [SE-0166] Swift Archival & Serialization [SE-0167] Swift Encoders

  9. Codable ͷྫ // JSON { "int": 0, "float": 1.5, "double":

    3.25, "string": "Swift", "array": [ "Hello", "World" ], "dict": { "WWDC": 2017 }, "date": 1496682000, }
  10. ! [SE-0168] Multi-Line String Literals let json = """ {

    "int": 0, "float": 1.5, "double": 3.25, "string": "Swift", "array": [ "Hello", "World" ], "dict": { "WWDC": 2017 }, "date": 1496682000, } """.data(using: .utf8)!
  11. struct MyData: Codable { let int: Int let float: Float

    let double: Double let string: String let array: [String] let dict: [String: Int] let date: Date } protocol Codable Λ࠾༻͢Δ͚ͩͰɾɾɾ
  12. let decoder = JSONDecoder() decoder.dateDecodingStrategy = .secondsSince1970 let decoded =

    try decoder.decode(MyData.self, from: json) // MyData(int: 0, float: 1.5, double: 3.25, string: "Swift", // array: ["Hello", "World"], dict: ["WWDC": 2017], // date: 2017-06-05 17:00:00 +0000) let encoder = JSONEncoder() encoder.dateEncodingStrategy = .secondsSince1970 let encodedData = try encoder.encode(decoded) let encodedString = String(data: encodedData, encoding: .utf8)! // {"double":3.25,"int":0,"string":"Swift","date":1496682000, // "array":["Hello","World"],"float":1.5,"dict":{"WWDC":2017}} σίʔυɾΤϯίʔυॲཧ͕؆୯ʂ
  13. None
  14. ϏϧτΠϯͷ Decoder/Encoder • JSON(De|En)coder ... JSONSerializationΛ࢖༻ • date(De|En)codingStrategy • data(De|En)codingStrategy

    • nonConformingFloat(De|En)codingStrategy • PropertyList(De|En)coder ... PropertyListSerializationΛ ࢖༻
  15. ϏϧτΠϯͷ Codableܕ • Swift ͷجຊσʔλܕ • Bool, Int, Int(N), UInt(N),

    Float, Double, String, RawRepresentable, Optional, Array, Set, Dictionary • Foundation / CoreGraphics ͷσʔλܕ • AffineTransform, Calendar, CharacterSet, Data, Date, DateComponents, DateInterval, Decimal, IndexPath, IndexSet, Locale, Measurement, NSRange, PersonNameComponents, TimeZone, URL, UUID, CGFloat
  16. Q. Ͳ͏΍ͬͯຐ๏Λ࣮ݱ ͍ͯ͠Δ!ʁ

  17. A. ίϯύΠϥʹΑΔ ࣗಈੜ੒

  18. struct User: Codable { var userName: String var score: Int

    @derived private enum CodingKeys: String, CodingKey { // @derived = ࣗಈੜ੒ case userName case score } @derived init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) userName = try container.decode(String.self, forKey: .userName) score = try container.decode(Int.self, forKey: .score) } @derived func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(userName, forKey: .userName) try container.encode(score, forKey: .score) } }
  19. struct User: Codable { var userName: String // JSON ͸

    "user_name"ΩʔΛ͍࣋ͬͯΔ var score: Int // 0...100 ͷΈʹݶఆ͍ͨ͠ private enum CodingKeys: String, CodingKey { // declare explicitly case userName = "user_name" case score } init(from decoder: Decoder) throws { // declare explicitly let container = try decoder.container(keyedBy: CodingKeys.self) score = try container.decode(Int.self, forKey: .score) guard (0...100).contains(score) else { // validation throw DecodingError.dataCorrupted( codingPath: container.codingPath + [CodingKeys.score], debugDescription: "score is not in range 0...100" ) } userName = try container.decode(String.self, forKey: .userName) } }
  20. Codable ϓϩτίϧ ˍ ܕͷશମ૾

  21. None
  22. public typealias Codable = Encodable & Decodable public protocol Decodable

    { init(from decoder: Decoder) throws } public protocol Encodable { func encode(to encoder: Encoder) throws } public protocol CodingKey { public var stringValue: String { get } public init?(stringValue: String) public var intValue: Int? { get } public init?(intValue: Int) }
  23. public protocol Decoder { var codingPath: [CodingKey?] { get }

    var userInfo: [CodingUserInfoKey : Any] { get } func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> func unkeyedContainer() throws -> UnkeyedDecodingContainer func singleValueContainer() throws -> SingleValueDecodingContainer } public protocol Encoder { var codingPath: [CodingKey?] { get } var userInfo: [CodingUserInfoKey : Any] { get } func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> func unkeyedContainer() -> UnkeyedEncodingContainer func singleValueContainer() -> SingleValueEncodingContainer }
  24. public protocol KeyedDecodingContainerProtocol { associatedtype Key : CodingKey var codingPath:

    [CodingKey?] { get } var allKeys: [Key] { get } func contains(_ key: Key) -> Bool func decode<T : Decodable>(_ type: T.Type, forKey key: Key) throws -> T func decodeIfPresent<T : Decodable> (_ type: T.Type, forKey key: Key) throws -> T? func nestedContainer<NestedKey> (keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer func superDecoder() throws -> Decoder func superDecoder(forKey key: Key) throws -> Decoder ... }
  25. public protocol KeyedEncodingContainerProtocol { associatedtype Key : CodingKey var codingPath:

    [CodingKey?] { get } mutating func encode<T : Encodable>(_ value: T, forKey key: Key) throws mutating func encodeWeak<T : AnyObject & Encodable> (_ object: T, forKey key: Key) throws mutating func encodeIfPresent<T : Encodable> (_ value: T?, forKey key: Key) throws mutating func nestedContainer<NestedKey> (keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer mutating func superEncoder() -> Encoder mutating func superEncoder(forKey key: Key) -> Encoder ... }
  26. public protocol UnkeyedDecodingContainer { var codingPath: [CodingKey?] { get }

    var count: Int? { get } var isAtEnd: Bool { get } mutating func decode<T : Decodable>(_ type: T.Type) throws -> T mutating func decodeIfPresent<T : Decodable> (_ type: T.Type) throws -> T? mutating func nestedContainer<NestedKey> (keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer mutating func superDecoder() throws -> Decoder ... }
  27. public protocol UnkeyedEncodingContainer { var codingPath: [CodingKey?] { get }

    mutating func encode<T : Encodable>(_ value: T) throws mutating func encodeWeak<T : AnyObject & Encodable> (_ object: T) throws mutating func encode<T : Sequence>(contentsOf sequence: T) throws where T.Iterator.Element : Encodable mutating func nestedContainer<NestedKey> (keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer mutating func superEncoder() -> Encoder ... }
  28. public protocol SingleValueDecodingContainer { public func decodeNil() -> Bool public

    func decode<T>(_ type: T.Type) throws -> T where T : Decodable ... } public protocol SingleValueEncodingContainer { public mutating func encodeNil() throws public mutating func encode<T>(_ value: T) throws where T : Encodable ... }
  29. public enum DecodingError : Error { public struct Context {

    public let codingPath: [CodingKey?] public let debugDescription: String } case typeMismatch(Any.Type, DecodingError.Context) case valueNotFound(Any.Type, DecodingError.Context) case keyNotFound(CodingKey, DecodingError.Context) case dataCorrupted(DecodingError.Context) } public enum EncodingError : Error { public struct Context { public let codingPath: [CodingKey?] public let debugDescription: String } case invalidValue(Any, EncodingError.Context) } public struct CodingUserInfoKey : RawRepresentable, Equatable, Hashable { public typealias RawValue = String }
  30. ϓϩτίϧ ˍ ܕͷશମ૾ (1) • CodingKey • ܕ҆શͳStringΩʔʢIntΩʔ΋Մʣ • superDecoder/superEncoder

    • εʔύʔΫϥεʹ౉͢༻ʢͪΌΜͱಈ͍ͯΔʁʣ • Τϥʔॲཧ (DecodingError/EncodingError) • codingPathΛه࿥ͯ͠ɺΤϥʔՕॴͷৄࡉΛग़ྗ
  31. ϓϩτίϧ ˍ ܕͷશମ૾ (2) • (Keyed|Unkeyed|SingleValue)(De|En)codingContainer • (De|En)coder ͕௚઀ (de|en)code

    ॲཧ͢Δ୅ΘΓʹɺ֤ Container͕୲౰ • (JSONSerialization౳ʹΑΔ) தؒදݱ͕ [String: Any] ౳ͷͨΊɺunbox/box-ingͰܕ҆શʹॲཧ͢ΔͨΊʹ Container͕ඞཁ • [SE-0143] ConditionalConformancesʹΑΔվળͷ༨஍
  32. Q. ίϯύΠϥ͸ Ͳ͏΍ͬͯ ॲཧ͍ͯ͠Δʁ!

  33. // include/swift/AST/KnownProtocols.(h|def) enum class KnownProtocolKind : uint8_t { ... CodingKey,

    Encodable, Decodable, }; // include/swift/AST/(ASTContext.h|KnownIdentifiers.def) class ASTContext { ... Identifier Id_CodingKeys, Identifier Id_Decodable, Identifier Id_decode, Identifier Id_decodeIfPresent, Identifier Id_Decoder, Identifier Id_decoder, Identifier Id_superDecoder, }
  34. static EnumDecl *synthesizeCodingKeysEnum(TypeChecker &tc, NominalTypeDecl *target, ProtocolDecl *proto) { auto

    &C = tc.Context; auto *codingKeyProto = C.getProtocol(KnownProtocolKind::CodingKey); auto *codingKeyType = codingKeyProto->getDeclaredType(); TypeLoc protoTypeLoc[1] = {TypeLoc::withoutLoc(codingKeyType)}; MutableArrayRef<TypeLoc> inherited = C.AllocateCopy(protoTypeLoc); auto *enumDecl = new (C) EnumDecl(SourceLoc(), C.Id_CodingKeys, SourceLoc(), inherited, nullptr, target); enumDecl->setImplicit(); enumDecl->setAccessibility(Accessibility::Private); // `private enum CodingKeys: CodingKey` Λੜ੒ ... return enumDecl }
  35. // lib/Sema/DerivedConformanceCodable.cpp // lib/Sema/DerivedConformanceCodingKey.cpp /// `init(from decoder: Decoder) throws`͕ແ͍৔߹ɺࣗಈੜ੒ static

    void deriveBodyDecodable_init(...) /// `func encode(to encoder: Encoder) throws`͕ແ͍৔߹ɺࣗಈੜ੒ static void deriveBodyEncodable_encode(...) /// `private enum CodingKeys: CodingKey`͕ແ͍৔߹ɺҰ͔Β࡞Δ static EnumDecl *synthesizeCodingKeysEnum(...) /// `enum CodingKey: CodingKey`͕͋Δ৔߹ɺ /// `var stringValue: String`౳Λࣗಈੜ੒ ValueDecl *DerivedConformance::deriveCodingKey(...)
  36. CodableͰؾʹͳͬͨ఺

  37. ܕม਺͕ඞཁ let myData: MyData let decoder = JSONDecoder() myData =

    try decoder.decode(MyData.self, from: json) ฦΓ஋Λܕਪ࿦ͤͨ͞ํ͕γϯϓϧˍԠ༻͕ޮ͘ͷͰ͸ʁ myData = try decoder.decode(from: json)
  38. https://twitter.com/dgregor79/status/873960672060514304

  39. static bool validateCodingKeysEnum(TypeChecker &tc, EnumDecl *codingKeysDecl, NominalTypeDecl *target, ProtocolDecl *proto)

    { llvm::SmallDenseMap<Identifier, VarDecl *, 8> properties; for (auto *varDecl : target->getStoredProperties(/*skipInaccessible=*/true)) { properties[varDecl->getName()] = varDecl; } // Codable ͷ stored properties ͱ CodingKey ͷ cases Λൺֱͯ͠ɺ1ର1ରԠ͔Ͳ͏͔ΛνΣοΫ bool propertiesAreValid = true; for (auto elt : codingKeysDecl->getAllElements()) { auto it = properties.find(elt->getName()); if (it == properties.end()) { tc.diagnose(elt->getLoc(), diag::codable_extraneous_codingkey_case_here, elt->getName()); propertiesAreValid = false; continue; } ... } if (!propertiesAreValid) return false; ... }
  40. Q. Codable ͷ Stored properties ͱ CodingKey cases ͕ 1ର1ରԠ͠ͳ͍৔߹͸ʁ!

    (ྫɿMyCodableͷ͋ΔϓϩύςΟΛ"ෳ਺ͷΩʔͷ஋"͔Βࢉग़͍ͨ͠ɺͳͲ)
  41. ʢ஌͍ͬͯΔਓ͍ͨΒڭ͍͑ͯͩ͘͞ʣ !

  42. ·ͱΊ • Codable ͸Appleެࣜͷຐ๏ • 3೥ʹ౉ΔSwift JSONϥΠϒϥϦઓ૪ʹऴࢭූ • ͱ͸͍͑ɺContainer +

    CodingKey ͷखಈ࣮૷͸໘౗ • ॴײ • Container͸ɺܕ҆શͰͳ͍چ࣌୅ͷͨΊͷworkaround • CodingKeyͷ1:1ରԠ͸ɺएׯ΍Γ͗͢ʁ
  43. ͱ͸͍͑ɺAPI͸៉ྷͩ͠ɺ CodableʹରԠ͢Ε͹ ৭ʑͳ(De|En)coderʹ ରԠͰ͖Δ!

  44. Thanks! Yasuhiro Inami @inamiy