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

Reply via email to