Congrats on getting this out! A question from the field: https://twitter.com/mdiep/status/842178457115230210 Why does the Swift Serialization API proposal use abstract base classes?
-Joe > On Mar 15, 2017, at 3:40 PM, Itai Ferber via swift-evolution > <[email protected]> wrote: > > Hi everyone, > > The following introduces a new Swift-focused archival and serialization API > as part of the Foundation framework. We’re interested in improving the > experience and safety of performing archival and serialization, and are happy > to receive community feedback on this work. > Because of the length of this proposal, the Appendix and Alternatives > Considered sections have been omitted here, but are available in the full > proposal on the swift-evolution repo. The full proposal also includes an > Unabridged API for further consideration. > > Without further ado, inlined below. > > — Itai > > Swift Archival & Serialization > • Proposal: SE-NNNN > • Author(s): Itai Ferber, Michael LeHew, Tony Parker > • Review Manager: TBD > • Status: Awaiting review > • Associated PRs: > • #8124 > • #8125 > Introduction > Foundation's current archival and serialization APIs (NSCoding, > NSJSONSerialization, NSPropertyListSerialization, etc.), while fitting for > the dynamism of Objective-C, do not always map optimally into Swift. This > document lays out the design of an updated API that improves the developer > experience of performing archival and serialization in Swift. > > Specifically: > > • It aims to provide a solution for the archival of Swift struct and > enum types > • It aims to provide a more type-safe solution for serializing to > external formats, such as JSON and plist > Motivation > The primary motivation for this proposal is the inclusion of native Swift > enum and struct types in archival and serialization. Currently, developers > targeting Swift cannot participate in NSCoding without being willing to > abandon enum and structtypes — NSCoding is an @objc protocol, conformance to > which excludes non-class types. This is can be limiting in Swift because > small enums and structs can be an idiomatic approach to model representation; > developers who wish to perform archival have to either forgo the Swift > niceties that constructs like enumsprovide, or provide an additional > compatibility layer between their "real" types and their archivable types. > > Secondarily, we would like to refine Foundation's existing serialization APIs > (NSJSONSerialization and NSPropertyListSerialization) to better match Swift's > strong type safety. From experience, we find that the conversion from the > unstructured, untyped data of these formats into strongly-typed data > structures is a good fit for archival mechanisms, rather than taking the less > safe approach that 3rd-party JSON conversion approaches have taken (described > further in an appendix below). > > We would like to offer a solution to these problems without sacrificing ease > of use or type safety. > > Agenda > This proposal is the first stage of three that introduce different facets of > a whole Swift archival and serialization API: > > • This proposal describes the basis for this API, focusing on the > protocols that users adopt and interface with > • The next stage will propose specific API for new encoders > • The final stage will discuss how this new API will interop with > NSCoding as it is today > SE-NNNN provides stages 2 and 3. > > Proposed solution > We will be introducing the following new types: > > • protocol Codable: Adopted by types to opt into archival. Conformance > may be automatically derived in cases where all properties are also Codable. > • protocol CodingKey: Adopted by types used as keys for keyed > containers, replacing String keys with semantic types. Conformance may be > automatically derived in most cases. > • protocol Encoder: Adopted by types which can take Codable values and > encode them into a native format. > • class KeyedEncodingContainer<Key : CodingKey>: Subclasses of > this type provide a concrete way to store encoded values by CodingKey. Types > adopting Encoder should provide subclasses of KeyedEncodingContainer to vend. > • protocol SingleValueEncodingContainer: Adopted by types which > provide a concrete way to store a single encoded value. Types adopting > Encoder should provide types conforming to SingleValueEncodingContainer to > vend (but in many cases will be able to conform to it themselves). > • protocol Decoder: Adopted by types which can take payloads in a > native format and decode Codable values out of them. > • class KeyedDecodingContainer<Key : CodingKey>: Subclasses of > this type provide a concrete way to retrieve encoded values from storage by > CodingKey. Types adopting Decoder should provide subclasses of > KeyedDecodingContainer to vend. > • protocol SingleValueDecodingContainer: Adopted by types which > provide a concrete way to retrieve a single encoded value from storage. Types > adopting Decoder should provide types conforming to > SingleValueDecodingContainer to vend (but in many cases will be able to > conform to it themselves). > For end users of this API, adoption will primarily involve the Codable and > CodingKey protocols. In order to participate in this new archival system, > developers must add Codable conformance to their types: > > // If all properties are Codable, implementation is automatically derived: > public struct Location : Codable { > > > public let latitude: Double > > > public let longitude: Double > } > > > > public enum Animal : Int, Codable { > > > case chicken = 1 > > > case > dog > > case > turkey > > case > cow > > } > > > > public struct Farm : Codable { > > > public let name: String > > > public let location: Location > > > public let animals: [Animal] > } > With developer participation, we will offer encoders and decoders (described > in SE-NNNN, not here) that take advantage of this conformance to offer > type-safe serialization of user models: > > let farm = Farm(name: "Old MacDonald's Farm", > > location > : Location(latitude: 51.621648, longitude: 0.269273), > > animals > : [.chicken, .dog, .cow, .turkey, .dog, .chicken, .cow, .turkey, .dog]) > let payload: Data = try JSONEncoder().encode(farm) > > > > do { > > > let farm = try JSONDecoder().decode(Farm.self, from: payload) > > > > // Extracted as user types: > > > let coordinates = "\(farm.location.latitude, farm.location.longitude)" > } catch { > > > // Encountered error during deserialization > } > This gives developers access to their data in a type-safe manner and a > recognizable interface. > > Detailed design > To support user types, we expose the Codable protocol: > > /// Conformance to `Codable` indicates that a type can marshal itself into > and out of an external representation. > public protocol Codable { > > > /// Initializes `self` by decoding from `decoder`. > > > /// > > > /// - parameter decoder: The decoder to read data from. > > > /// - throws: An error if reading from the decoder fails, or if read data is > corrupted or otherwise invalid. > > > init(from decoder: Decoder) throws > > > > /// Encodes `self` into the given encoder. > > > /// > > > /// If `self` fails to encode anything, `encoder` will encode an empty > `.default` container in its place. > > > /// > > > /// - parameter encoder: The encoder to write data to. > > > /// - throws: An error if any values are invalid for `encoder`'s format. > > > func encode(to encoder: Encoder) throws > } > By adopting Codable, user types opt in to this archival system. > > Structured types (i.e. types which encode as a collection of properties) > encode and decode their properties in a keyed manner. Keys may be > String-convertible or Int-convertible (or both), and user types which have > properties should declare semantic key enums which map keys to their > properties. Keys must conform to the CodingKey protocol: > > /// Conformance to `CodingKey` indicates that a type can be used as a key for > encoding and decoding. > public protocol CodingKey { > > > /// The string to use in a named collection (e.g. a string-keyed dictionary). > > > var stringValue: String? { get } > > > > /// Initializes `self` from a string. > > > /// > > > /// - parameter stringValue: The string value of the desired key. > > > /// - returns: An instance of `Self` from the given string, or `nil` if the > given string does not correspond to any instance of `Self`. > > > init?(stringValue: String) > > > > /// The int to use in an indexed collection (e.g. an int-keyed dictionary). > > > var intValue: Int? { get } > > > > /// Initializes `self` from an integer. > > > /// > > > /// - parameter intValue: The integer value of the desired key. > > > /// - returns: An instance of `Self` from the given integer, or `nil` if the > given integer does not correspond to any instance of `Self`. > > > init?(intValue: Int) > } > For most types, String-convertible keys are a reasonable default; for > performance, however, Int-convertible keys are preferred, and Encoders may > choose to make use of Ints over Strings. Framework types should provide keys > which have both for flexibility and performance across different types of > Encoders. It is generally an error to provide a key which has neither a > stringValue nor an intValue. > > By default, CodingKey conformance can be derived for enums which have either > String or Int backing: > > enum Keys1 : CodingKey { > > > case a // (stringValue: "a", intValue: nil) > > > case b // (stringValue: "b", intValue: nil) > } > > > > enum Keys2 : String, CodingKey { > > > case c = "foo" // (stringValue: "foo", intValue: nil) > > > case d // (stringValue: "d", intValue: nil) > } > > > > enum Keys3 : Int, CodingKey { > > > case e = 4 // (stringValue: "e", intValue: 4) > > > case f // (stringValue: "f", intValue: 5) > > > case g = 9 // (stringValue: "g", intValue: 9) > } > Coding keys which are not enums, have associated values, or have other raw > representations must implement these methods manually. > > In addition to automatic CodingKey conformance derivation for enums, > Codableconformance can be automatically derived for certain types as well: > > • Types whose properties are all either Codable or primitive get an > automatically derived String-backed CodingKeys enum mapping properties to > case names > • Types falling into (1) and types which provide a CodingKeys enum > (directly or via a typealias) whose case names map to properties which are > all Codableget automatic derivation of init(from:) and encode(to:) using > those properties and keys. Types may choose to provide a custom init(from:) > or encode(to:) (or both); whichever they do not provide will be automatically > derived > • Types which fall into neither (1) nor (2) will have to provide a > custom key type and provide their own init(from:) and encode(to:) > Many types will either allow for automatic derivation of all codability (1), > or provide a custom key subset and take advantage of automatic method > derivation (2). > > Encoding and Decoding > > Types which are encodable encode their data into a container provided by > their Encoder: > > /// An `Encoder` is a type which can encode values into a native format for > external representation. > public protocol Encoder { > > > /// Populates `self` with an encoding container (of `.default` type) and > returns it, keyed by the given key type. > > > /// > > > /// - parameter type: The key type to use for the container. > > > /// - returns: A new keyed encoding container. > > > /// - precondition: May not be called after a previous > `self.container(keyedBy:)` call of a different `EncodingContainerType`. > > > /// - precondition: May not be called after a value has been encoded through > a prior `self.singleValueContainer()` call. > > > func container<Key : CodingKey>(keyedBy type: Key.Type) -> > KeyedEncodingContainer<Key> > > > > /// Returns an encoding container appropriate for holding a single primitive > value. > > > /// > > > /// - returns: A new empty single value container. > > > /// - precondition: May not be called after a prior > `self.container(keyedBy:)` call. > > > /// - precondition: May not be called after a value has been encoded through > a previous `self.singleValueContainer()` call. > > > func singleValueContainer() -> SingleValueEncodingContainer > > > > /// The path of coding keys taken to get to this point in encoding. > > > var codingKeyContext: [CodingKey] { get } > } > > > > // Continuing examples from before; below is automatically generated by the > compiler if no customization is needed. > public struct Location : Codable { > > > private enum CodingKeys : CodingKey { > > > case > latitutude > > case > longitude > > } > > > > public func encode(to encoder: Encoder) throws { > > > // Generic keyed encoder gives type-safe key access: cannot encode with keys > of the wrong type. > > > let container = encoder.container(keyedBy: CodingKeys.self) > > > > // The encoder is generic on the key -- free key autocompletion here. > > > try container.encode(latitude, forKey: .latitude) > > > try container.encode(longitude, forKey: .longitude) > > > } > } > > > > public struct Farm : Codable { > > > private enum CodingKeys : CodingKey { > > > case > name > > case > location > > case > animals > > } > > > > public func encode(to encoder: Encoder) throws { > > > let container = encoder.container(keyedBy: CodingKeys.self) > > > try container.encode(name, forKey: .name) > > > try container.encode(location, forKey: .location) > > > try container.encode(animals, forKey: .animals) > > > } > } > Similarly, decodable types initialize from data read from their Decoder's > container: > > /// A `Decoder` is a type which can decode values from a native format into > in-memory representations. > public protocol Decoder { > > > /// Returns the data stored in `self` as represented in a container keyed by > the given key type. > > > /// > > > /// - parameter type: The key type to use for the container. > > > /// - returns: A keyed decoding container view into `self`. > > > /// - throws: `CocoaError.coderTypeMismatch` if the encountered stored value > is not a keyed container. > > > func container<Key : CodingKey>(keyedBy type: Key.Type) throws -> > KeyedDecodingContainer<Key> > > > > /// Returns the data stored in `self` as represented in a container > appropriate for holding a single primitive value. > > > /// > > > /// - returns: A single value container view into `self`. > > > /// - throws: `CocoaError.coderTypeMismatch` if the encountered stored value > is not a single value container. > > > func singleValueContainer() throws -> SingleValueDecodingContainer > > > > /// The path of coding keys taken to get to this point in decoding. > > > var codingKeyContext: [CodingKey] { get } > } > > > > // Continuing examples from before; below is automatically generated by the > compiler if no customization is needed. > public struct Location : Codable { > > > public init(from decoder: Decoder) throws { > > > let container = try decoder.container(keyedBy: CodingKeys.self) > > latitude > = try container.decode(Double.self, forKey: .latitude) > > longitude > = try container.decode(Double.self, forKey: .longitude) > > > } > } > > > > public struct Farm : Codable { > > > public init(from decoder: Decoder) throws { > > > let container = try decoder.container(keyedBy: CodingKeys.self) > > name > = try container.decode(String.self, forKey: .name) > > location > = try container.decode(Location.self, forKey: .location) > > animals > = try container.decode([Animal].self, forKey: .animals) > > > } > } > Keyed Encoding Containers > > Keyed encoding containers are the primary interface that most Codable types > interact with for encoding and decoding. Through these, Codable types have > strongly-keyed access to encoded data by using keys that are semantically > correct for the operations they want to express. > > Since semantically incompatible keys will rarely (if ever) share the same key > type, it is impossible to mix up key types within the same container (as is > possible with Stringkeys), and since the type is known statically, keys get > autocompletion by the compiler. > > /// `KeyedEncodingContainer` is a generic abstract base class that provides a > view into an `Encoders` storage and is used to hold the encoded properties of > a `Codable` type. > /// > /// Encoders should provide subclasses of `KeyedEncodingContainer` for their > format. > > open > class KeyedEncodingContainer<Key : CodingKey> { > > > /// Encodes the given value for the given key. > > > /// > > > /// - parameter value: The value to encode. > > > /// - parameter key: The key to associate the value with. > > > /// - throws: `CocoaError.coderInvalidValue` if the given value is invalid in > the current context for this format. > > > /// - precondition: The key must have a `stringValue` or `intValue` > appropriate for the encoding container type. > > open > func encode<Value : Codable>(_ value: Value?, forKey key: Key) throws > > > > /// Encodes the given value for the given key. > > > /// > > > /// - parameter value: The value to encode. > > > /// - parameter key: The key to associate the value with. > > > /// - throws: `CocoaError.coderInvalidValue` if the given value is invalid in > the current context for this format. > > > /// - precondition: The key must have a `stringValue` or `intValue` > appropriate for the encoding container type. > > open > func encode(_ value: Bool?, forKey key: Key) throws > > open > func encode(_ value: Int?, forKey key: Key) throws > > open > func encode(_ value: Int8?, forKey key: Key) throws > > open > func encode(_ value: Int16?, forKey key: Key) throws > > open > func encode(_ value: Int32?, forKey key: Key) throws > > open > func encode(_ value: Int64?, forKey key: Key) throws > > open > func encode(_ value: UInt?, forKey key: Key) throws > > open > func encode(_ value: UInt8?, forKey key: Key) throws > > open > func encode(_ value: UInt16?, forKey key: Key) throws > > open > func encode(_ value: UInt32?, forKey key: Key) throws > > open > func encode(_ value: UInt64?, forKey key: Key) throws > > open > func encode(_ value: Float?, forKey key: Key) throws > > open > func encode(_ value: Double?, forKey key: Key) throws > > open > func encode(_ value: String?, forKey key: Key) throws > > open > func encode(_ value: Data?, forKey key: Key) throws > > > > /// Encodes the given object weakly for the given key. > > > /// > > > /// For `Encoder`s that implement this functionality, this will only encode > the given object and associate it with the given key if it encoded > unconditionally elsewhere in the archive (either previously or in the future). > > > /// > > > /// For formats which don't support this feature, the default implementation > encodes the given object unconditionally. > > > /// > > > /// - parameter object: The object to encode. > > > /// - parameter key: The key to associate the object with. > > > /// - throws: `CocoaError.coderInvalidValue` if the given value is invalid in > the current context for this format. > > > /// - precondition: The key must have a `stringValue` or `intValue` > appropriate for the encoding container type. > > open > func encodeWeak<Object : AnyObject & Codable>(_ object: Object?, forKey key: > Key) throws > > > > /// The path of coding keys taken to get to this point in encoding. > > open > var codingKeyContext: [CodingKey] > } > > > > /// `KeyedDecodingContainer` is a generic abstract base class that provides a > view into an `Decoders` storage and is used to hold the encoded properties of > a `Codable` type. > /// > /// Decoders should provide subclasses of `KeyedDecodingContainer` for their > format. > > open > class KeyedDecodingContainer<Key : CodingKey> { > > > /// All the keys the `Decoder` has for this container. > > > /// > > > /// Different keyed containers from the same `Decoder` may return different > keys here; it is possible to encode with multiple key types which are not > convertible to one another. This should report all keys present which are > convertible to the requested type. > > open > var allKeys: [Key] > > > > /// Returns whether the `Decoder` contains a value associated with the given > key. > > > /// > > > /// The value associated with the given key may be a null value as > appropriate for the data format. > > > /// > > > /// - parameter key: The key to search for. > > > /// - returns: Whether the `Decoder` has an entry for the given key. > > open > func contains(_ key: Key) -> Bool > > > > /// Decodes a value of the given type for the given key. > > > /// > > > /// A default implementation is given for these types which calls into the > abstract `decodeIfPresent` implementations below. > > > /// > > > /// - parameter type: The type of value to decode. > > > /// - parameter key: The key that the decoded value is associated with. > > > /// - returns: A value of the requested type, if present for the given key > and convertible to the requested type. > > > /// - throws: `CocoaError.coderTypeMismatch` if the encountered encoded value > is not convertible to the requested type. > > > /// - throws: `CocoaError.coderValueNotFound` if `self` does not have an > entry for the given key or if the value is null. > > open > func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool > > open > func decode(_ type: Int.Type, forKey key: Key) throws -> Int > > open > func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 > > open > func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 > > open > func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 > > open > func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 > > open > func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt > > open > func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 > > open > func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 > > open > func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 > > open > func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 > > open > func decode(_ type: Float.Type, forKey key: Key) throws -> Float > > open > func decode(_ type: Double.Type, forKey key: Key) throws -> Double > > open > func decode(_ type: String.Type, forKey key: Key) throws -> String > > open > func decode(_ type: Data.Type, forKey key: Key) throws -> Data > > open > func decode<Value : Codable>(_ type: Value.Type, forKey key: Key) throws -> > Value > > > > /// 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 disambiguated 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: `CocoaError.coderTypeMismatch` if the encountered encoded value > is not convertible to the requested type. > > open > func decodeIfPresent(_ type: Bool.Type, forKey key: Key) throws -> Bool? > > open > func decodeIfPresent(_ type: Int.Type, forKey key: Key) throws -> Int? > > open > func decodeIfPresent(_ type: Int8.Type, forKey key: Key) throws -> Int8? > > open > func decodeIfPresent(_ type: Int16.Type, forKey key: Key) throws -> Int16? > > open > func decodeIfPresent(_ type: Int32.Type, forKey key: Key) throws -> Int32? > > open > func decodeIfPresent(_ type: Int64.Type, forKey key: Key) throws -> Int64? > > open > func decodeIfPresent(_ type: UInt.Type, forKey key: Key) throws -> UInt? > > open > func decodeIfPresent(_ type: UInt8.Type, forKey key: Key) throws -> UInt8? > > open > func decodeIfPresent(_ type: UInt16.Type, forKey key: Key) throws -> UInt16? > > open > func decodeIfPresent(_ type: UInt32.Type, forKey key: Key) throws -> UInt32? > > open > func decodeIfPresent(_ type: UInt64.Type, forKey key: Key) throws -> UInt64? > > open > func decodeIfPresent(_ type: Float.Type, forKey key: Key) throws -> Float? > > open > func decodeIfPresent(_ type: Double.Type, forKey key: Key) throws -> Double? > > open > func decodeIfPresent(_ type: String.Type, forKey key: Key) throws -> String? > > open > func decodeIfPresent(_ type: Data.Type, forKey key: Key) throws -> Data? > > open > func decodeIfPresent<Value : Codable>(_ type: Value.Type, forKey key: Key) > throws -> Value? > > > > /// The path of coding keys taken to get to this point in decoding. > > open > var codingKeyContext: [CodingKey] > } > These encode(_:forKey:) and decode(_:forKey:) overloads give strong, static > type guarantees about what is encodable (preventing accidental attempts to > encode an invalid type), and provide a list of primitive types which are > common to all encoders and decoders that users can rely on. > > Coming in Swift 4 is the ability to express that "a collection of things > which are Codable is Codable" (conditional conformance), allowing collections > which we extend (Array, Dictionary, etc.) to fall into these overloads as > well. > > Encoding Container Types > > For some types, the container into which they encode has meaning. Especially > when coding for a specific output format (e.g. when communicating with a JSON > API), a type may wish to explicitly encode as an array or a dictionary: > > // Continuing from before > public protocol Encoder { > > > /// Populates `self` with an encoding container of the given type and returns > it, keyed by the given key type. > > > /// > > > /// A default implementation of `Encoder.container(keyedBy:)` calls this > method with a container type of `.default`. > > > /// > > > /// - parameter keyType: The key type to use for the container. > > > /// - parameter containerType: The container type to create. > > > /// - returns: A new keyed encoding container. > > > /// - precondition: May not be called after a previous > `self.container(keyedBy:)` call of a different `EncodingContainerType`. > > > /// - precondition: May not be called after a value has been encoded through > a prior `self.singleValueContainer()` call. > > > func container<Key : CodingKey>(keyedBy keyType: Key.Type, type > containerType: EncodingContainerType) -> KeyedEncodingContainer<Key> > } > > > > /// An `EncodingContainerType` specifies the type of container an `Encoder` > should use to store values. > public enum EncodingContainerType { > > > /// The `Encoder`'s preferred container type; equivalent to either `.array` > or `.dictionary` as appropriate for the encoder. > > > case `default > ` > > > /// Explicitly requests the use of an array to store encoded values. > > > case > array > > > /// Explicitly requests the use of a dictionary to store encoded values. > > > case > dictionary > > } > Single Value Containers > > For other types, an array or dictionary container may not even make sense > (e.g. values which are RawRepresentable as a single primitive value). Those > types may encode and decode directly as a single value, instead of requesting > a keyed container: > > /// A `SingleValueEncodingContainer` is a container which can support the > storage and direct encoding of a single non-keyed value. > public protocol SingleValueEncodingContainer { > > > /// Encodes a single value of the given type. > > > /// > > > /// - parameter value: The value to encode. > > > /// - throws: `CocoaError.coderInvalidValue` if the given value is invalid in > the current context for this format. > > > /// - precondition: May not be called after a previous `self.encode(_:)` call. > > > func encode(_ value: Bool) throws > > > func encode(_ value: Int) throws > > > func encode(_ value: Int8) throws > > > func encode(_ value: Int16) throws > > > func encode(_ value: Int32) throws > > > func encode(_ value: Int64) throws > > > func encode(_ value: UInt) throws > > > func encode(_ value: UInt8) throws > > > func encode(_ value: UInt16) throws > > > func encode(_ value: UInt32) throws > > > func encode(_ value: UInt64) throws > > > func encode(_ value: Float) throws > > > func encode(_ value: Double) throws > > > func encode(_ value: String) throws > > > func encode(_ value: Data) throws > } > > > > /// A `SingleValueDecodingContainer` is a container which can support the > storage and direct decoding of a single non-keyed value. > public protocol SingleValueDecodingContainer { > > > /// Decodes a single value of the given type. > > > /// > > > /// - parameter type: The type to decode as. > > > /// - returns: A value of the requested type. > > > /// - throws: `CocoaError.coderTypeMismatch` if the encountered encoded value > cannot be converted to the requested type. > > > func decode(_ type: Bool.Type) throws -> Bool > > > func decode(_ type: Int.Type) throws -> Int > > > func decode(_ type: Int8.Type) throws -> Int8 > > > func decode(_ type: Int16.Type) throws -> Int16 > > > func decode(_ type: Int32.Type) throws -> Int32 > > > func decode(_ type: Int64.Type) throws -> Int64 > > > func decode(_ type: UInt.Type) throws -> UInt > > > func decode(_ type: UInt8.Type) throws -> UInt8 > > > func decode(_ type: UInt16.Type) throws -> UInt16 > > > func decode(_ type: UInt32.Type) throws -> UInt32 > > > func decode(_ type: UInt64.Type) throws -> UInt64 > > > func decode(_ type: Float.Type) throws -> Float > > > func decode(_ type: Double.Type) throws -> Double > > > func decode(_ type: String.Type) throws -> String > > > func decode(_ type: Data.Type) throws -> Data > } > > > > // Continuing example from before; below is automatically generated by the > compiler if no customization is needed. > public enum Animal : Int, Codable { > > > public func encode(to encoder: Encoder) throws { > > > // Encode as a single value; no keys. > > > try encoder.singleValueContainer.encode(self.rawValue) > > > } > > > > public init(from decoder: Decoder) throws { > > > // Decodes as a single value; no keys. > > > let intValue = try decoder.singleValueContainer().decode(Int.self) > > > if let value = Self(rawValue: intValue) { > > > self = > value > > } else { > > > throw CocoaError.error(.coderReadCorrupt) > > > } > > > } > } > In the example given above, since Animal uses a single value container, > [.chicken, .dog, .cow, .turkey, .dog, .chicken, .cow, .turkey, .dog]would > encode directly as [1, 2, 4, 3, 2, 1, 4, 3, 2]. > > Nesting > > In practice, some types may also need to control how data is nested within > their container, or potentially nest other containers within their container. > Keyed containers allow this by returning nested containers of differing key > types: > > // Continuing from before > > open > class KeyedEncodingContainer<Key : CodingKey> { > > > /// Stores an encoding container for the given key and returns it. > > > /// > > > /// - parameter keyType: The key type to use for the container. > > > /// - parameter containerType: The container type to create. > > > /// - parameter key: The key to encode the container for. > > > /// - returns: A new keyed encoding container. > > open > func nestedContainer<NestedKey : CodingKey>(keyedBy keyType: NestedKey.Type, > type containerType: EncodingContainerType, forKey key: Key) -> > KeyedEncodingContainer<NestedKey> > } > > > open > class KeyedDecodingContainer<Key : CodingKey> { > > > /// Returns the data stored for the given key as represented in a container > keyed by the given key type. > > > /// > > > /// - parameter type: The key type to use for the container. > > > /// - parameter key: The key that the nested container is associated with. > > > /// - returns: A keyed decoding container view into `self`. > > > /// - throws: `CocoaError.coderTypeMismatch` if the encountered stored value > is not a container. > > open > func nestedContainer<NestedKey : CodingKey>(keyedBy type: NestedKey.Type, > forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> > } > This can be common when coding against specific external data representations: > > // User type for interfacing with a specific JSON API. JSON API expects > encoding as {"id": ..., "properties": {"name": ..., "timestamp": ...}}. Swift > type differs from encoded type, and encoding needs to match a spec: > struct Record : Codable { > > > // We care only about these values from the JSON payload > > > let id: Int > > > let name: String > > > let timestamp: Double > > > > // ... > > > > private enum Keys : CodingKey { > > > case > id > > case > properties > > } > > > > private enum PropertiesKeys : CodingKey { > > > case > name > > case > timestamp > > } > > > > public func encode(to encoder: Encoder) throws { > > > let container = encoder.container(keyedBy: Keys.self, type: .dictionary) > > > try container.encode(id, forKey: .id) > > > > // Set a dictionary for the "properties" key > > > let nested = container.nestedContainer(keyedBy: PropertiesKeys.self, type: > .dictionary, forKey: .properties) > > > try nested.encode(name, forKey: .name) > > > try nested.encode(timestamp, forKey: .timestamp) > > > } > > > > public init(from decoder: Decoder) throws { > > > let container = try decoder.container(keyedBy: Keys.self) > > id > = try container.decode(Int.self, forKey: .id) > > > > let nested = try container.nestedContainer(keyedBy: PropertiesKeys.self, > forKey: .properties) > > name > = try nested.decode(String.self, forKey: .name) > > timestamp > = try nested.decode(Double.self, forKey: .timestamp) > > > } > } > Inheritance > > Inheritance in this system is supported much like it is with NSCoding — on > encoding, objects which inherit from a type that is Codable encode super > using their encoder, and pass a decoder to super.init(from:) on decode. With > the existing NSCoding API, this is most often done like so, by convention: > > - (void)encodeWithCoder:(NSCoder *)encoder { > > > [super encodeWithCoder:encoder]; > > > // ... encode properties > } > > > > - (instancetype)initWithCoder:(NSCoder *)decoder { > > > if ((self = [super initWithCoder:decoder])) { > > > // ... decode properties > > > } > > > > return self; > } > In practice, this approach means that the properties of self and the > properties of super get encoded into the same container: if self encodes > values for keys "a", "b", and "c", and super encodes "d", "e", and "f", the > resulting object is archived as {"a": ..., "b": ..., "c": ..., "d": ..., "e": > ..., "f": ...}. This approach has two drawbacks: > > • Things which self encodes may overwrite super's (or vice versa, > depending on when -[super encodeWithCoder:] is called > • self and super may not encode into different container types (e.g. > self in a sequential fashion, and super in a keyed fashion) > The second point is not an issue for NSKeyedArchiver, since all values encode > with keys (sequentially coded elements get autogenerated keys). This proposed > API, however, allows for self and super to explicitly request conflicting > containers (.arrayand .dictionary, which may not be mixed, depending on the > data format). > > To remedy both of these points, we adopt a new convention for > inheritance-based coding — encoding super as a sub-object of self: > > public class MyCodable : SomethingCodable { > > > public func encode(to encoder: Encoder) throws { > > > let container = encoder.container(keyedBy: CodingKeys.self) > > > // ... encode some properties > > > > // superEncoder() gives `super` a nested container to encode into (for > > > // a predefined key). > > > try super.encode(to: container.superEncoder()) > > > } > > > > public init(from decoder: Decoder) throws { > > > let container = try decoder.container(keyedBy: CodingKeys.self) > > > // ... decode some properties > > > > // Allow `super` to decode from the nested container. > > > try super.init(from: container.superDecoder()) > > > } > } > If a shared container is desired, it is still possible to call > super.encode(to: encoder) and super.init(from: decoder), but we recommend the > safer containerized option. > > superEncoder() and superDecoder() are provided on KeyedEncodingContainer and > KeyedDecodingContainer to provide handles to containers for super to use. > While users may specify a custom key to encode super with, the default > behavior is to use a key with a stringValue of "super" and an intValue of 0: > > // Continuing from before > > open > class KeyedEncodingContainer<Key : CodingKey> { > > > /// Stores a new nested container for the default `super` key and returns a > new `Encoder` instance for encoding `super` into that container. > > > /// > > > /// Equivalent to calling `superEncoder(forKey:)` with `Key(stringValue: > "super", intValue: 0)`. > > > /// > > > /// - returns: A new `Encoder` to pass to `super.encode(to:)`. > > open > func superEncoder() -> Encoder > > > > /// Stores a new nested container for the given key and returns a new > `Encoder` instance for encoding `super` into that container. > > > /// > > > /// - parameter key: The key to encode `super` for. > > > /// - returns: A new `Encoder` to pass to `super.encode(to:)`. > > > /// - precondition: The key must have a `stringValue` or `intValue` > appropriate for the encoding container type. > > open > func superEncoder(forKey key: Key) -> Encoder > } > > > open > class KeyedDecodingContainer<Key : CodingKey> { > > > /// Returns a `Decoder` instance for decoding `super` from the container > associated with the default `super` key. > > > /// > > > /// Equivalent to calling `superDecoder(forKey:)` with `Key(stringValue: > "super", intValue: 0)`. > > > /// > > > /// - returns: A new `Decoder` to pass to `super.init(from:)`. > > > /// - throws: `CocoaError.coderValueNotFound` if `self` does not have an > entry for the default `super` key, or if the stored value is null. > > open > func superDecoder() throws -> Decoder > > > > /// Returns a `Decoder` instance for decoding `super` from the container > associated with the given key. > > > /// > > > /// - parameter key: The key to decode `super` for. > > > /// - returns: A new `Decoder` to pass to `super.init(from:)`. > > > /// - throws: `CocoaError.coderValueNotFound` if `self` does not have an > entry for the given key, or if the stored value is null. > > open > func superDecoder(forKey key: Key) throws -> Decoder > } > Primitive Codable Conformance > > The encoding container types offer overloads for working with and processing > the API's primitive types (String, Int, Double, etc.). However, for ease of > implementation (both in this API and others), it can be helpful for these > types to conform to Codable themselves. Thus, along with these overloads, we > will offer Codableconformance on these types: > > extension Bool : Codable { > > > public init(from decoder: Decoder) throws { > > > self = try decoder.singleValueContainer().decode(Bool.self) > > > } > > > > public func encode(to encoder: Encoder) throws { > > > try encoder.singleValueContainer().encode( self) > > > } > } > > > > // Repeat for others... > This conformance allows one to write functions which accept Codable types > without needing specific overloads for the fifteen primitive types as well. > This also simplifies conditional conformance (e.g. expressing "extension > Array : Codable where Element : Codable") by removing the need for additional > explicit conformances for these types. > > Since Swift's function overload rules prefer more specific functions over > generic functions, the specific overloads are chosen where possible (e.g. > encode("Hello, world!", forKey: .greeting) will choose encode(_: String, > forKey: Key) over encode<T : Codable>(_: T, forKey: Key)). This maintains > performance over dispatching through the Codable existential, while allowing > for the flexibility of fewer overloads where applicable. > > Additional Extensions > > Along with the primitive Codable conformance above, extensions on > CodableRawRepresentable types whose RawValue is a primitive types will > provide default implementations for encoding and decoding: > > public extension RawRepresentable where RawValue == Bool, Self : Codable { > > > public init(from decoder: Decoder) throws { > > > let decoded = try decoder.singleValueContainer().decode(RawValue.self) > > > guard let value = Self(rawValue: decoded) else { > > > throw CocoaError.error(.coderReadCorrupt) > > > } > > > > self = > value > > } > > > > public func encode(to encoder: Encoder) throws { > > > try encoder.singleValueContainer().encode(self.rawValue) > > > } > } > > > > // Repeat for others... > This allows for trivial Codable conformance of enum types (and manual > RawRepresentable implementations) with primitive backing. > > Source compatibility > This proposal is additive — existing code will not have to change due to this > API addition. This implementation can be made available in both Swift 4 and > the Swift 3 compatibility mode. > > Effect on ABI stability > The addition of this API will not be an ABI-breaking change. However, this > will add limitations for changes in future versions of Swift, as parts of the > API will have to remain unchanged between versions of Swift (barring some > additions, discussed below). > > Effect on API resilience > Much like new API added to the standard library, once added, many changes to > this API will be ABI- and source-breaking changes. In particular, changes > which change the types or names of methods or arguments, add required methods > on protocols or classes, or remove supplied default implementations will > break client behavior. > > The following protocols and classes may not have methods added to them > without providing default implementations: > > • Codable > • CodingKey > • Encoder > • SingleValueEncodingContainer > • KeyedEncodingContainer > • Decoder > • SingleValueDecodingContainer > • KeyedDecodingContainer > The following classes may not remove existing default implementations: > > • KeyedEncodingContainer > • KeyedDecodingContainer > Various extensions to Swift primitive types (Bool, Int, Double, etc.) and to > RawRepresentable types (where RawValue == Bool, == Int, == Double, etc.) may > also not be removed. > > In general, changes to the proposed types will be restricted as described in > the library evolution document in the Swift repository. > > _______________________________________________ > swift-evolution mailing list > [email protected] > https://lists.swift.org/mailman/listinfo/swift-evolution _______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
