- Proposal: FOU-NNNN
- Author(s): Jeremy Schonfeld
- Status: Pitch
- v1 Initial version
Currently, the TopLevelEncoder
/TopLevelDecoder
protocols along with Foundation's conformers (JSONEncoder
, JSONDecoder
, PropertyListEncoder
, and PropertyListDecoder
) offer an encode
/decode
function that accepts some Codable
instance. In previous releases, we also introduced the CodableWithConfiguration
protocol (which is now used by AttributedString
and Predicate
in Foundation) to allow for encoding/decoding types with extra provided information. Currently, CodableWithConfiguration
types cannot be directly encoded/decoded by Foundation's coders. Instead, callers must wrap their CodableWithConfiguration
type within a separate Codable
type to provide to a coder like JSONEncoder
. While in many cases this may already be necessary, in some cases this is unnecessary extra work developers must do in order to get started with serializing these types. With new adoption of CodableWithConfiguration
for Predicate
this year, we'd like to improve this experience.
With these new APIs, developers will be able to directly encode/decode a CodableWithConfiguration
type with Foundation's top level encoder/decoders. For example, developers will be able to write the following without needing to wrap the predicate in an arbitrary box type:
let predicate = #Predicate<Message> {
$0.sender.firstName == "Jeremy"
}
let configuration: PredicateCodableConfiguration = /* ... */
let encoder = JSONEncoder()
let jsonData = encoder.encode(predicate, configuration: configuration)
For context, today the above code would need to be written with the following in order to invoke the existing APIs on the encoding/decoding containers:
let predicate = #Predicate<Message> {
$0.sender.firstName == "Jeremy"
}
struct Box : Codable {
let predicate: Predicate<Message>
func encode(to encoder: Encoder) throws {
let configuration: PredicateCodableConfiguration = /* ... */
var container = try encoder.unkeyedContainer()
try container.encode(predicate, configuration: configuration)
}
init(from decoder: Decoder) throws {
let configuration: PredicateCodableConfiguration = /* ... */
var container = try decoder.unkeyedContainer()
predicate = try container.decode(Predicate<Message>.self, configuration: configuration)
}
}
let encoder = JSONEncoder()
let jsonData = encoder.encode(Box(predicate: predicate))
We propose adding the following APIs to Foundation's various top level encoder/decoders:
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *)
extension JSONEncoder {
open func encode<T : EncodableWithConfiguration>(_ value: T, configuration: T.EncodingConfiguration) throws -> Data
open func encode<T : EncodableWithConfiguration, C : EncodingConfigurationProviding>(_ value: T, configuration: C.Type) throws -> Data where T.EncodingConfiguration == C.EncodingConfiguration
}
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *)
extension JSONDecoder {
open func decode<T: DecodableWithConfiguration>(_ type: T.Type, from data: Data, configuration: T.DecodingConfiguration) throws -> T
open func decode<T: DecodableWithConfiguration, C : DecodingConfigurationProviding>(_ type: T.Type, from data: Data, configuration: C.Type) throws -> T where T.DecodingConfiguration == C.DecodingConfiguration
}
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *)
extension PropertyListEncoder {
open func encode<Value : EncodableWithConfiguration>(_ value: Value, configuration: Value.EncodingConfiguration) throws -> Data
open func encode<Value : EncodableWithConfiguration, C : EncodingConfigurationProviding>(_ value: Value, configuration: C.Type) throws -> Data where Value.EncodingConfiguration == C.EncodingConfiguration
}
extension PropertyListDecoder {
open func decode<T : DecodableWithConfiguration>(_ type: T.Type, from data: Data, configuration: T.DecodingConfiguration) throws -> T
open func decode<T : DecodableWithConfiguration, C : DecodingConfigurationProviding>(_ type: T.Type, from data: Data, configuration: C.Type) throws -> T where T.DecodingConfiguration == C.DecodingConfiguration
open func decode<T : DecodableWithConfiguration>(_ type: T.Type, from data: Data, format: inout PropertyListSerialization.PropertyListFormat, configuration: T.DecodingConfiguration) throws -> T
open func decode<T : DecodableWithConfiguration, C : DecodingConfigurationProviding>(_ type: T.Type, from data: Data, format: inout PropertyListSerialization.PropertyListFormat, configuration: C.Type) throws -> T where T.DecodingConfiguration == C.DecodingConfiguration
}
These changes are additive only; there is no impact on existing code.
Ideally, the generic protocols TopLevelEncoder
& TopLevelDecoder
would also have functions that accept CodableWithConfiguration
types. However, given that these are already-shipping protocols we cannot add a new requirement without also providing a default implementation. Unfortunately, it is not possible to write a default implementation of these requirements. Instead, we've decided to add these new APIs directly to Foundation's encoders/decoders which will cover the majority of cases and leaves the flexibility for other third party coders to do the same if they are able.