Sent from my iPhone
> On 16 Mar 2017, at 01:18, Joe Groff via swift-evolution > <[email protected]> wrote: > > 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? > Hopefully because we realise we need abstract classes as a feature ;)? > -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 _______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
