<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>0146-swift-archival-serialization-updates</title>
<style type="text/css">
body {
font-family: "SF Hello", Helvetica, arial, sans-serif;
font-size: 14px;
line-height: 1.6;
padding-top: 10px;
padding-bottom: 10px;
background-color: white;
padding: 30px; }
body > *:first-child {
margin-top: 0 !important; }
body > *:last-child {
margin-bottom: 0 !important; }
a {
color: #4183C4; }
a.absent {
color: #cc0000; }
a.anchor {
display: block;
padding-left: 30px;
margin-left: -30px;
cursor: pointer;
position: absolute;
top: 0;
left: 0;
bottom: 0; }
h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
cursor: text;
position: relative; }
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA09pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoMTMuMCAyMDEyMDMwNS5tLjQxNSAyMDEyLzAzLzA1OjIxOjAwOjAwKSAgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUM2NjlDQjI4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUM2NjlDQjM4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QzY2OUNCMDg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QzY2OUNCMTg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsQhXeAAAABfSURBVHjaYvz//z8DJYCRUgMYQAbAMBQIAvEqkBQWXI6sHqwHiwG70TTBxGaiWwjCTGgOUgJiF1J8wMRAIUA34B4Q76HUBelAfJYSA0CuMIEaRP8wGIkGMA54bgQIMACAmkXJi0hKJQAAAABJRU5ErkJggg==) no-repeat 10px center;
text-decoration: none; }
h1 tt, h1 code {
font-size: inherit; }
h2 tt, h2 code {
font-size: inherit; }
h3 tt, h3 code {
font-size: inherit; }
h4 tt, h4 code {
font-size: inherit; }
h5 tt, h5 code {
font-size: inherit; }
h6 tt, h6 code {
font-size: inherit; }
h1 {
font-size: 28px;
color: black; }
h2 {
font-size: 24px;
border-bottom: 1px solid #cccccc;
color: black; }
h3 {
font-size: 18px; }
h4 {
font-size: 16px; }
h5 {
font-size: 14px; }
h6 {
color: #777777;
font-size: 14px; }
p, blockquote, ul, ol, dl, li, table, pre {
margin: 15px 0; }
hr {
background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC) repeat-x 0 0;
border: 0 none;
color: #cccccc;
height: 4px;
padding: 0;
}
body > h2:first-child {
margin-top: 0;
padding-top: 0; }
body > h1:first-child {
margin-top: 0;
padding-top: 0; }
body > h1:first-child + h2 {
margin-top: 0;
padding-top: 0; }
body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
margin-top: 0;
padding-top: 0; }
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0; }
h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
margin-top: 0; }
li p.first {
display: inline-block; }
li {
margin: 0; }
ul, ol {
padding-left: 30px; }
ul :first-child, ol :first-child {
margin-top: 0; }
dl {
padding: 0; }
dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px; }
dl dt:first-child {
padding: 0; }
dl dt > :first-child {
margin-top: 0; }
dl dt > :last-child {
margin-bottom: 0; }
dl dd {
margin: 0 0 15px;
padding: 0 15px; }
dl dd > :first-child {
margin-top: 0; }
dl dd > :last-child {
margin-bottom: 0; }
blockquote {
border-left: 4px solid #dddddd;
padding: 0 15px;
color: #777777; }
blockquote > :first-child {
margin-top: 0; }
blockquote > :last-child {
margin-bottom: 0; }
table {
padding: 0;border-collapse: collapse; }
table tr {
border-top: 1px solid #cccccc;
background-color: white;
margin: 0;
padding: 0; }
table tr:nth-child(2n) {
background-color: #f8f8f8; }
table tr th {
font-weight: bold;
border: 1px solid #cccccc;
margin: 0;
padding: 6px 13px; }
table tr td {
border: 1px solid #cccccc;
margin: 0;
padding: 6px 13px; }
table tr th :first-child, table tr td :first-child {
margin-top: 0; }
table tr th :last-child, table tr td :last-child {
margin-bottom: 0; }
img {
max-width: 100%; }
span.frame {
display: block;
overflow: hidden; }
span.frame > span {
border: 1px solid #dddddd;
display: block;
float: left;
overflow: hidden;
margin: 13px 0 0;
padding: 7px;
width: auto; }
span.frame span img {
display: block;
float: left; }
span.frame span span {
clear: both;
color: #333333;
display: block;
padding: 5px 0 0; }
span.align-center {
display: block;
overflow: hidden;
clear: both; }
span.align-center > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: center; }
span.align-center span img {
margin: 0 auto;
text-align: center; }
span.align-right {
display: block;
overflow: hidden;
clear: both; }
span.align-right > span {
display: block;
overflow: hidden;
margin: 13px 0 0;
text-align: right; }
span.align-right span img {
margin: 0;
text-align: right; }
span.float-left {
display: block;
margin-right: 13px;
overflow: hidden;
float: left; }
span.float-left span {
margin: 13px 0 0; }
span.float-right {
display: block;
margin-left: 13px;
overflow: hidden;
float: right; }
span.float-right > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: right; }
code, tt {
margin: 0 2px;
padding: 0 5px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px; }
pre code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent; }
.highlight pre {
background-color: #f8f8f8;
border: 1px solid #cccccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px; }
pre {
background-color: #f8f8f8;
border: 1px solid #cccccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px; }
pre code, pre tt {
background-color: transparent;
border: none; }
sup {
font-size: 0.83em;
vertical-align: super;
line-height: 0;
}
kbd {
display: inline-block;
padding: 3px 5px;
font-size: 11px;
line-height: 10px;
color: #555;
vertical-align: middle;
background-color: #fcfcfc;
border: solid 1px #ccc;
border-bottom-color: #bbb;
border-radius: 3px;
box-shadow: inset 0 -1px 0 #bbb
}
* {
        -webkit-print-color-adjust: exact;
}
@media screen and (min-width: 914px) {
body {
width: 854px;
margin:0 auto;
}
}
@media print {
        table, pre {
                page-break-inside: avoid;
        }
        pre {
                word-wrap: break-word;
        }
}
</style>
<style type="text/css">
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
        color: black;
        background: none;
        text-shadow: 0 1px white;
        font-family: "SF Mono", Menlo, Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
        text-align: left;
        white-space: pre-wrap;
        word-spacing: normal;
        word-break: normal;
        word-wrap: normal;
        line-height: 1.5;
        -moz-tab-size: 4;
        -o-tab-size: 4;
        tab-size: 4;
        -webkit-hyphens: none;
        -moz-hyphens: none;
        -ms-hyphens: none;
        hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
        text-shadow: none;
        background: #b3d4fc;
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
        text-shadow: none;
        background: #b3d4fc;
}
@media print {
        code[class*="language-"],
        pre[class*="language-"] {
                text-shadow: none;
        }
}
/* Code blocks */
pre[class*="language-"] {
        padding: 1em;
        margin: .5em 0;
        overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
        background: #f5f2f0;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
        padding: .1em;
        border-radius: .3em;
        white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
        color: slategray;
}
.token.punctuation {
        color: #999;
}
.namespace {
        opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
        color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
        color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
        color: #a67f59;
        background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
        color: #07a;
}
.token.function {
        color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
        color: #e90;
}
.token.important,
.token.bold {
        font-weight: bold;
}
.token.italic {
        font-style: italic;
}
.token.entity {
        cursor: help;
}
</style>
<style type="text/css">
div.prism-show-language {
        position: relative;
}
div.prism-show-language > div.prism-show-language-label {
        color: black;
        background-color: #CFCFCF;
        display: inline-block;
        position: absolute;
        bottom: auto;
        left: auto;
        top: 0;
        right: 0;
        width: auto;
        height: auto;
        font-size: 0.9em;
        border-radius: 0 0 0 5px;
        padding: 0 0.5em;
        text-shadow: none;
        z-index: 1;
        -webkit-box-shadow: none;
        -moz-box-shadow: none;
        box-shadow: none;
        -webkit-transform: none;
        -moz-transform: none;
        -ms-transform: none;
        -o-transform: none;
        transform: none;
}
</style>
</head>
<body>
<h1 id="toc_0">Swift Archival & Serialization Updates</h1>
<h2 id="toc_3">Introduction</h2>
<p>In implementing the bulk of <a href="https://github.com/apple/swift-evolution/blob/master/proposals/0166-swift-archival-serialization.md">SE-0166 (Swift Archival & Serialization)</a> and <a href="https://github.com/apple/swift-evolution/blob/master/proposals/0167-swift-encoders.md">SE-0167 (Swift Encoders)</a>, a few changes have come up for review which we would like to integrate with the core implementation. Primarily:</p>
<ol>
<li>A decision was made to sink the core types and protocols into the Swift standard library to better interoperate with the compiler as a core feature of the Swift language. With this change, Foundation-specific APIs were removed from the core of the proposal to prevent a Swift ↔ Foundation dependency. This removal necessitates a change to the error model of those core types, as before we were able to rely on throwing <code>NSError</code> instances from failure points.</li>
<li>From writing implementations of <code>Codable</code> for Foundation value types, some QoL improvements can be made to the API to improve the experience of using the API</li>
</ol>
<h2 id="toc_4">Detailed Design</h2>
<p>We intend to introduce the following changes to the implementation of the <code>Codable</code> API in the standard library and in the Foundation overlay:</p>
<h3 id="toc_5">API Revisions</h3>
<h4 id="toc_6">1. Non-Optional Coding Paths</h4>
<p>On all <code>Encoder</code>s, <code>Decoder</code>s, and containers, the type of <code>codingPath</code> (which indicates where we are in the current encode or decode process) is <code>[CodingKey?]</code>. This is because <code>UnkeyedEncodingContainer</code>s and <code>UnkeyedDecodingContainer</code>s don't have keys by definition, so when we traverse through one of them, they insert <code>nil</code> into the <code>codingPath</code>.</p>
<p>This, however, is not useful for debugging, especially on decode. When debugging why a value in an array failed to decode, it's important to know whether it is the 2nd or 87th or 993rd value. Currently, the <code>codingPath</code> just reports <code>nil</code> for the key, and the information is lost.</p>
<p>To help with this, all instances of <code>var codingPath</code> will be modified to contain non-optional keys:</p>
<div><pre><code class="language-swift">// Presently:
public protocol Encoder {
/// The path of coding keys taken to get to this point in encoding.
/// A `nil` value indicates an unkeyed container.
var codingPath: [CodingKey?] { get }
}
// New:
public protocol Encoder {
/// The path of coding keys taken to get to this point in encoding.
var codingPath: [CodingKey] { get }
}</code></pre></div>
<p>This implicitly necessitates <code>UnkeyedEncodingContainer</code> and <code>UnkeyedDecodingContainer</code>s to be able to offer a key representing the index or current position in the container. This key type can be private, which is acceptable as long as the key's <code>intValue</code> reflects the current position in the <code>codingPath</code>.</p>
<h4 id="toc_7">2. Optionality Changes</h4>
<p>Currently, <code>Optional</code> is a sort of unspoken primitive type in the <code>Codable</code> API — encoding containers accept <code>Optional</code> parameters by default and must implicitly support <code>nil</code> values. Decoding containers expose <code>decodeIfPresent</code> which allows for the representation of null values in payloads.</p>
<p>However, this approach is not enough to handle the range of how users express nullability in their code. This is primarily because <code>Optional</code> does not itself conform to <code>Codable</code>, so using it in conjunction with other types renders them non-<code>Codable</code> as well (e.g. <code>[T]</code> is <code>Codable</code> if <code>T</code> is <code>Codable</code>, but <code>[T?]</code> is not, since <code>T?</code> is not <code>Codable</code>). To remedy this, we propose to allow <code>Optional</code> to conform to <code>Codable</code>, and formalize its relationship with the <code>Codable</code> API:</p>
<div><pre><code class="language-swift">// The implementation can be refined when Conditional Conformance arrives in Swift 4.x.
// This is discussed further below.
extension Optional : Encodable /* where Wrapped : Encodable */ { /* ... */ }
extension Optional : Decodable /* where Wrapped : Decodable */ { /* ... */ }</code></pre></div>
<p>When <code>Optional</code> conforms to <code>Codable</code>, even complex types like <code>[String : [T??]?]?</code> are <code>Codable</code>.</p>
<p><details>
<summary><strong><code>encodeNil()</code>/<code>decodeNil()</code></strong></summary></p>
<p>This change necessitates the addition and revision of API. For one, <code>Optional</code> needs to be able to provide an implementation of <code>encode(to:)</code> and <code>decode(from:)</code> which calls into the <code>Codable</code> API. Since it cannot give an implementation which calls a method taking an <code>Optional</code> (which would lead to an infinite loop), we provide new API to directly encode <code>nil</code> in containers:</p>
<div><pre><code class="language-swift">protocol SingleValueEncodingContainer {
/// Encodes a single null value.
///
/// - throws: `EncodingError.invalidValue` if a null value is invalid in the current context for this format.
/// - precondition: May not be called after a previous `self.encode(_:)` call.
func encodeNil() throws
}
protocol SingleValueDecodingContainer {
/// Decodes a single expected null value.
///
/// - returns: `true` if the encountered value was a null value; `false` otherwise.
func decodeNil() -> Bool
}</code></pre></div>
<blockquote>
<p>NOTE: Similar <code>encodeNil(forKey:)</code>/<code>decodeNil(forKey:)</code> and <code>encodeNil()</code>/<code>decodeNil()</code> will be provided on keyed and unkeyed containers, respectively.</p>
</blockquote>
<p></details></p>
<p><details>
<summary><strong>Altered Default Implementation of <code>decode()</code>/<code>decodeIfPresent()</code></strong></summary></p>
<p>This change in the semantics of <code>Optional</code> has an effect on the semantics and behavior of <code>decode()</code> and <code>decodeIfPresent()</code>. Previously, <code>decodeIfPresent()</code> was the only way to request the decode of a null value without throwing an error. Now, it is possible to request to <code>decode(Optional<T>.self)</code> (or importantly, other types which can decode from a null value validly), leaving the semantics of <code>decodeIfPresent()</code> lacking.</p>
<p>Specifically, <code>decodeIfPresent()</code> inspects the value at the current position or key and checks whether it is a null value. If the value is null, then it returns <code>nil</code>. Otherwise, it attempts to decode a value. This is problematic in the case of a value which decodes via a <code>SingleValueDecodingContainer</code> — even if it can properly handle the null value to decode as something valid, <code>nil</code> is always returned in its stead. To give an example of such a type:</p>
<div><pre><code class="language-swift">enum EnhancedBool : Codable {
case `true` // encodes/decodes as `true`
case `false` // encodes/decodes as `false`
case fileNotFound // encodes/decodes as `nil`
// ...
}</code></pre></div>
<p>This is especially problematic for <code>decode()</code>, which is given a default implementation in terms of <code>decodeIfPresent()</code>: <code>decode()</code> calls <code>decodeIfPresent()</code>, and if the returned value is <code>nil</code>, throws an error. This has been valid thus far, but is no longer valid for types like the following:</p>
<div><pre><code class="language-swift">let json = "[ null ]".data(using: .utf8)!
let decoder = JSONDecoder()
try decoder.decode([EnhancedBool].self, from: json) // throws an error</code></pre></div>
<p>The issue here is as follows:</p>
<ol>
<li>In order to decode, <code>Array</code> requests an unkeyed decoding container</li>
<li>As long as the container has remaining elements, it calls <code>decode(EnhancedBool.self)</code></li>
<li><code>decode()</code> calls <code>decodeIfPresent(EnhancedBool.self)</code></li>
<li><code>decodeIfPresent()</code> sees a null value and returns <code>nil</code>, even though <code>EnhancedBool</code> can return something valid from <code>null</code></li>
<li><code>decode()</code> receives the <code>nil</code>, and throws an error</li>
</ol>
<p>To fix this, this relationship should be inverted: with the new <code>decodeNil()</code> methods, a default implementation of <code>decodeIfPresent</code> can be given in terms of <code>decode()</code>:</p>
<div><pre><code class="language-swift">func decodeIfPresent<T : Decodable>(_ type: T.self, ...) throws -> T? {
if (decodeNil(...)) { return nil }
return decode(T.self, ...)
}</code></pre></div>
<p>This inverts the relationship and allows <code>decode(EnhancedBool.self)</code> to succeed while maintaining the semantics of <code>decodeIfPresent</code> as they are — <code>decode()</code> will always attempt to decode, even if the value is null, and <code>decodeIfPresent</code> will always intervene if it sees a null value.</p>
<p>This change in API will primarily affect developers writing their own <code>Decoder</code>s. They will now need to give an implementation of <code>decode()</code> instead of <code>decodeIfPresent()</code>.</p>
<p></details></p>
<p><details>
<summary><strong><code>encode<T>()</code>/<code>decode<T>()</code> in Single Value Containers</strong></summary></p>
<p>Additionally, to support the encoding and decoding of values wrapped within <code>Optional</code> types directly, we will also add the following methods to single value containers:</p>
<div><pre><code class="language-swift">protocol SingleValueEncodingContainer {
/// Encodes the given single value.
///
/// - parameter value: The value to encode.
/// - throws: `EncodingError.invalidValue` if the given value is invalid in the current context for this format.
func encode<T : Encodable>(_ value: T) throws
}
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: `DecodingError.coderTypeMismatch` if the encountered encoded value is not convertible to the requested type.
func decode<T : Decodable>(_ type: T.self) throws -> T
}</code></pre></div>
<p>These methods were previously missing on single value containers, but now allow for a direct implementation of <code>encode(to:)</code> and <code>init(from:)</code> for many types (including <code>Optional</code>) which wish to encode in terms of a different type.</p>
<p></details></p>
<p><details>
<summary><strong>Non-Optional Arguments to <code>encode(...)</code></strong></summary></p>
<p>Now, with <code>Optional</code> being <code>Codable</code>, all <code>encode(...)</code> methods may stop taking <code>Optional</code> parameters, since <code>Optional<T></code> can safely fall into <code>encode<T : Encodable>(_ value: T, ...)</code>:</p>
<div><pre><code class="language-swift">// Previously:
func encode(_ value: Bool?) throws { /* ... */ }
func encode(_ value: Int?) throws { /* ... */ }
// ...
func encode<T : Encodable>(_ value: T?) throws { /* ... */ }
// Now:
func encode(_ value: Bool) throws { /* ... */ }
func encode(_ value: Int) throws { /* ... */ }
// ...
// This handles `Optional<T>`.
func encode<T : Encodable>(_ value: T) throws { /* ... */ }</code></pre></div>
<p></details></p>
<h4 id="toc_8">3. Stronger Reference Semantic Requirements on Encoders and Decoders</h4>
<p>At the moment, the API contract on encoders and decoders (and their containers) put no real requirements on the support of reference types, which means that users who rely on reference semantics have no guarantees that a given <code>Encoder</code> or <code>Decoder</code> will respect the reference semantics of the object graph they want to encode.</p>
<p>We would like to offer more stringent requirements to support reference types in <em>all</em> encoders and decoders, and thus intend to update the API contract to require their support, e.g.:</p>
<div><pre><code class="language-swift">// Previously:
protocol KeyedEncodingContainerProtocol {
/// Encodes the given value for the given key.
mutating func encode<T : Encodable>(_ value: T?, forKey key: Key) throws
}
protocol KeyedDecodingContainerProtocol {
/// Decodes a value of the given type for the given key.
func decode<T : Decodable>(_ type: T.Type, forKey key: Key) throws -> T
}
// Now:
protocol KeyedEncodingContainerProtocol {
/// Encodes the given value for the given key.
///
/// If the given value is an object which has been previously encoded as part of this object graph, this will encode a reference to the object instead of a new copy of it.
mutating func encode<T : Encodable>(_ value: T, forKey key: Key) throws
}
protocol KeyedDecodingContainerProtocol {
/// Decodes a value of the given type for the given key.
///
/// If the encountered value is a reference to an existing object previously decoded as part of this object graph, this will return the existing object instead of a new copy of it.
func decode<T : Decodable>(_ type: T.Type, forKey key: Key) throws -> T
}</code></pre></div>
<blockquote>
<p>NOTE: This change will be made on <code>UnkeyedEncodingContainer</code>, <code>UnkeyedDecodingContainer</code>, <code>SingleValueEncodingContainer</code>, and <code>SingleValueDecodingContainer</code> as well. <code>encodeWeak</code> will also be update to reflect this new requirement.</p>
</blockquote>
<p>The existing JSON and plist encoder implementations will be updated to reflect this change in requirements.</p>
<p><details>
<summary><strong><code>encodeWeak()</code> → <code>encodeConditional()</code></strong></summary></p>
<p>The <code>encodeWeak()</code> methods will be renamed back to <code>encodeConditional()</code> now that the <code>encode(...)</code> methods no longer take <code>Optional</code> arguments. <code>encodeConditional</code> was originally deemed confusing in light of the optionality of the arguments on all other overloads, but now that the source of confusion is gone, <code>encodeWeak</code> is a less appealing name because of the overloaded use of the word <code>weak</code> in a non-memory-management context.</p>
<p></details></p>
<h3 id="toc_9">API Additions</h3>
<h4 id="toc_10">1. New Error Types</h4>
<p>Because of the decision to sink the core <code>Codable</code> protocols into the Swift standard library, we can no longer throw <code>NSError</code> instances from the APIs (since we would have a Swift stdlib ↔︎ Foundation dependency). Instead, we need to define new errors, which live in the Swift stdlib, but which can bridge to <code>NSError</code>s:</p>
<div><pre><code class="language-swift">/// An `EncodingError` indicates that something has gone wrong during encoding of a value.
public enum EncodingError : Error {
/// The context in which the error occurred.
public struct Context {
/// The path of `CodingKey`s taken to get to the point of the failing encode call.
public let codingPath: [CodingKey]
/// A description of what went wrong, for debugging purposes.
public let debugDescription: String
/// The underlying error which caused this error, if any.
public let underlyingError: Error?
/// Initializes `self` with the given path of `CodingKey`s and a description of what went wrong.
///
/// - parameter codingPath: The path of `CodingKey`s taken to get to the point of the failing encode call.
/// - parameter debugDescription: A description of what went wrong, for debugging purposes.
/// - parameter underlyingError: The underlying error which caused this error, if any.
public init(codingPath: [CodingKey], debugDescription: String, underlyingError: Error? = nil)
/// `.invalidValue` indicates that an `Encoder` or its containers could not encode the given value.
///
/// Contains the attempted value, along with context for debugging.
case invalidValue(Any, Context)
}
/// A `DecodingError` indicates that something has gone wrong during decoding of a value.
public enum DecodingError : Error {
/// The context in which the error occurred.
public struct Context {
/// The path of `CodingKey`s taken to get to the point of the failing decode call.
public let codingPath: [CodingKey]
/// A description of what went wrong, for debugging purposes.
public let debugDescription: String
/// The underlying error which caused this error, if any.
public let underlyingError: Error?
/// Initializes `self` with the given path of `CodingKey`s and a description of what went wrong.
///
/// - parameter codingPath: The path of `CodingKey`s taken to get to the point of the failing decode call.
/// - parameter debugDescription: A description of what went wrong, for debugging purposes.
/// - parameter underlyingError: The underlying error which caused this error, if any.
public init(codingPath: [CodingKey], debugDescription: String, underlyingError: Error? = nil)
/// `.typeMismatch` indicates that a value of the given type could not be decoded because it did not match the type of what was found in the encoded payload.
///
/// Contains the attempted type, along with context for debugging.
case typeMismatch(Any.Type, Context)
/// `.valueNotFound` indicates that a non-optional value of the given type was expected, but a null value was found.
///
/// Contains the attempted type, along with context for debugging.
case valueNotFound(Any.Type, Context)
/// `.keyNotFound` indicates that a `KeyedDecodingContainer` was asked for an entry for the given key, but did not contain one.
///
/// Contains the attempted key, along with context for debugging.
case keyNotFound(CodingKey, Context)
/// `.dataCorrupted` indicates that the data is corrupted or otherwise invalid.
///
/// Contains context for debugging.
case dataCorrupted(Context)
}</code></pre></div>
<p>The errors above capture the information necessary to act on errors and can expose that information via associated values.</p>
<p><details>
<summary><strong>Localization & <code>NSError</code> Interoperability</strong></summary></p>
<p>A major benefit that <code>NSError</code> provided (which native Swift errors do not) is localization of information for user presentation. These errors do not provide localized information about what went wrong, and are thus not suitable to be displayed to a user as-is.</p>
<p>To provide this functionality, we will add the following extensions to the standard library to allow for native bridging between these types and <code>NSError</code>:</p>
<div><pre><code class="language-swift">extension EncodingError {
var _domain: String { return "NSCocoaErrorDomain" }
var _code: Int { ... }
var _userInfo: AnyObject? { ... }
}
extension DecodingError {
var _domain: String { return "NSCocoaErrorDomain" }
var _code: Int { ... }
var _userInfo: AnyObject? { ... }
}</code></pre></div>
<p>When Foundation is loaded, the above extensions allow for <code>EncodingError</code> and <code>DecodingError</code> to bridge natively to <code>NSError</code> instances, complete with localized descriptions provided by Foundation (based on the error code):</p>
<div><pre><code class="language-swift">do {
let myFoo = decoder.decode(Foo.self, from: payload)
} catch {
// error.code == CocoaError.coderReadCorrupt
print(error.localizedDescription) // => "The data couldn’t be read because it isn’t in the correct format."
}</code></pre></div>
<p>The <code>Error</code> → <code>NSError</code> code mapping we provide will be as follows:</p>
<ul>
<li><code>DecodingError.keyNotFound</code>, <code>DecodingError.valueNotFound</code> → <code>NSCoderValueNotFoundError</code></li>
<li><code>DecodingError.typeMismatch</code>, <code>DecodingError.dataCorrupted</code> → <code>NSCoderReadCorruptError</code></li>
<li><code>EncodingError.invalidValue</code> → <code>NSCoderInvalidValue</code>, a new code as proposed in FOU-0108</li>
</ul>
<blockquote>
<p>NOTE: Since <code>NSCoderInvalidValue</code> will be a new error code that we are introducing, OSes prior to macOS 10.13, iOS 11.0, watchOS 4.0, and tvOS 11.0 will not contain localized information for that error code. (However, the error code will still be present and usable for developers, and will still contain debug information; users will simply be presented with the default "The operation couldn’t be completed." message.)</p>
</blockquote>
<p>These errors will also expose the <code>EncodingError</code>/<code>DecodingError</code>'s <code>Context.debugDescription</code> and <code>.underlyingError</code> in their user info dictionaries under the <code>NSDebugDescriptionErrorKey</code> and <code>NSUnderlyingErrorKey</code>, respectively, and we will provide a new <code>NSCodingPathErrorKey</code> to access the <code>Context.codingPath</code> as well.</p>
<p></details></p>
<p><details>
<summary><strong><code>.dataCorrupted</code> Conveniences</strong></summary></p>
<p>Of these new errors, <code>EncodingError.invalidValue</code>, <code>DecodingError.typeMismatch</code>, <code>.valueNotFound</code>, and <code>.keyNotFound</code> will rarely, if ever, be thrown from API consumer code.</p>
<p>However, <code>DecodingError.dataCorrupted</code> <em>is</em> an error that will often be thrown from user code, to indicate invalid data found during an <code>init(from:)</code>. Throwing such an error can be quite verbose, so we intend to provide conveniences for throwing such an error:</p>
<div><pre><code class="language-swift">extension DecodingError {
/// Returns a new `DecodingError.dataCorrupt` error indicating that the data found for the given key in the given keyed container was corrupted.
///
/// - parameter key: The key at which the invalid data was encountered.
/// - parameter container: The container holding the invalid data.
/// - parameter debugDescription: A description of what went wrong, for debugging purposes.
/// - returns: A new `.dataCorrupt` error with an appropriate coding path and debug description set.
public static func dataCorruptedError<C : KeyedDecodingContainerProtocol>(forKey key: C.Key, in container: C, debugDescription: String) -> DecodingError
/// Returns a new `DecodingError.dataCorrupt` error indicating that the data found at the current position in the given unkeyed decoding container was corrupted.
///
/// - parameter container: The container holding the invalid data.
/// - parameter debugDescription: A description of what went wrong, for debugging purposes.
/// - returns: A new `.dataCorrupt` error with an appropriate coding path and debug description set.
public static func dataCorruptedError(in container: UnkeyedDecodingContainer, debugDescription: String) -> DecodingError
/// Returns a new `DecodingError.dataCorrupt` error indicating that the data found in the given single value decoding container was corrupted.
///
/// - parameter container: The container holding the invalid data.
/// - parameter debugDescription: A description of what went wrong, for debugging purposes.
/// - returns: A new `.dataCorrupt` error with an appropriate coding path and debug description set.
public static func dataCorruptedError(in container: SingleValueDecodingContainer, debugDescription: String) -> DecodingError
}</code></pre></div>
<p>Usage of these conveniences simplifies error throwing:</p>
<div><pre><code class="language-swift">// Before (in a keyed container):
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath + [CodingKeys.myKey] /* appending this key is easy to forget */,
debugDescription: "Something went wrong."))
// Now:
throw DecodingError.dataCorruptedError(forKey: .myKey, in: container, debugDescription: "Something went wrong.")</code></pre></div>
<p></details></p>
<h4 id="toc_11">2. Collection Conformance to <code>Codable</code></h4>
<p>Collection types are ubiquitous in user code, and very common in model types which are expected to be <code>Codable</code>. Without collection conformance, encoding of collection properties must be performed manually, and the custom work causes a <code>Codable</code> type to fall out of the compiler-generated default implementation. To support such types, we intend to add the following conformances to <code>Codable</code> in the Swift standard library:</p>
<div><pre><code class="language-swift">extension Array : Codable { /* ... */ }
extension Set : Codable { /* ... */ }
extension Dictionary : Codable { /* ... */ }</code></pre></div>
<p>The conformances of Swift collections was mentioned in <a href="https://github.com/apple/swift-evolution/blob/master/proposals/0166-swift-archival-serialization.md">FOU-0102</a> with reference to conditional conformance (to statically prevent <code>[MyNonCodableType]</code> from being encoded). Since the conditional conformance feature will not be available in the initial release of Swift 4, we will be adding a temporary blanket conformance to all <code>Array</code>s, <code>Set</code>s, and <code>Dictionary</code>s to conform with <code>Codable</code>. The implementations of these conformances will <code>preconditionFailure</code> at runtime if the element type (or key/value type in the case of <code>Dictionary</code>) is not <code>Codable</code>. When conditional conformance is available, the conformance will be changed to reflect this:</p>
<div><pre><code class="language-swift">extension Array : Encodable where Element : Encodable { /* ... */ }
extension Array : Decodable where Element : Decodable { /* ... */ }
extension Set : Encodable where Element : Encodable { /* ... */ }
extension Set : Decodable where Element : Decodable { /* ... */ }
extension Dictionary : Encodable where Key : Encodable, Value : Encodable { /* ... */ }
extension Dictionary : Decodable where Key : Decodable, Value : Decodable { /* ... */ }</code></pre></div>
<blockquote>
<p>NOTE: While the future change will be a potentially source-breaking change for some, the alternative is preventing the encoding and decoding of Swift collections until the feature becomes available, which is prohibitive.</p>
</blockquote>
<p>For compiler-derived conformance to <code>Codable</code>, we will add checks to the compiler so that we do not derive an implementation if a user type contains property which is a collection generic on a type which is not <code>Codable</code> (i.e. we should not generate code for users that will implicitly set them up to fail).</p>
<h4 id="toc_12">3. <code>JSONEncoder</code>/<code>JSONDecoder</code> Changes</h4>
<p><details>
<summary><strong><code>JSONEncoder</code> Sorted Keys</strong></summary></p>
<p>We intend to add a new formatting option to <code>JSONEncoder</code> which will allow it to output JSON with sorted dictionary keys. The formatting options on <code>JSONEncoder</code> will change to reflect this:</p>
<div><pre><code class="language-swift">// Previously:
public enum OutputFormatting {
/// Produce JSON compacted by removing whitespace. This is the default formatting.
case compact
/// Produce human-readable JSON with indented output.
case prettyPrinted
}
// Now:
public struct OutputFormatting: OptionSet {
let rawValue: UInt
/// Produce human-readable JSON with indented output.
static let prettyPrinted = OutputFormatting(rawValue: 1 << 0)
/// Produce JSON with dictionary keys sorted lexicographically.
@available(OSX 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *)
static let sortedKeys = OutputFormatting(rawValue: 1 << 1)
}</code></pre></div>
<p>The default <code>outputFormatting</code> value on <code>JSONEncoder</code> will change to reflect this:</p>
<div><pre><code class="language-swift">/// The output format to produce. Defaults to `[]`.
open var outputFormatting: OutputFormatting = []</code></pre></div>
<p></details></p>
<p><details>
<summary><strong><code>JSONEncoder</code>/<code>JSONDecoder</code> Defer-to-<code>Data</code> Strategy</strong></summary></p>
<p>When <code>Data</code> was removed as a primitive <code>Codable</code> type, it got a default implementation which encodes its bytes as an array. As part of that transition, <code>JSONEncoder</code> and <code>JSONDecoder</code> now need to offer a data encoding strategy which allow them to defer to that default implementation.</p>
<p>As such, we will be expanding the strategies available on those types:</p>
<div><pre><code class="language-swift">/// The strategy to use for encoding `Data` values.
public enum DataEncodingStrategy {
/// Defer to `Data` for choosing an encoding.
case deferredToData
/// Encoded the `Data` as a Base64-encoded string. This is the default strategy.
case base64
/// Encode the `Data` as a custom value encoded by the given closure.
///
/// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place.
case custom((Data, Encoder) throws -> Void)
}
/// The strategy to use for decoding `Data` values.
public enum DataDecodingStrategy {
/// Defer to `Data` for decoding.
case deferredToDate
/// Decode the `Data` from a Base64-encoded string. This is the default strategy.
case base64
/// Decode the `Data` as a custom value decoded by the given closure.
case custom((_ decoder: Decoder) throws -> Data)
}</code></pre></div>
<blockquote>
<p>NOTE: Base64 encoding and decoding will remain the default option on <code>JSONEncoder</code>/<code>JSONDecoder</code>. This is because, unlike <code>Date</code>, <code>Data</code>'s default generic implementation of <code>encode(to:)</code> and <code>init(from:)</code> is relatively inefficient, both in terms of space required for representation, and efficiency of decoding. More importantly, binary data is rarely encoded in JSON in formats other than Base64-encoding (as opposed to dates, which have a very wide range of representations), so this is a safe default that will rarely require customization, something we want to encourage.</p>
</blockquote>
<p></details></p>
<h4 id="toc_13">4. Miscellaneous Additions</h4>
<p><details>
<summary><strong><code>encodeIfPresent</code></strong></summary></p>
<p>Along with these changes to support <code>Optional</code> values, we will add <code>encodeIfPresent</code> conveniences on the <code>KeyedEncodingContainerProtocol</code> to mirror the behavior of <code>decodeIfPresent</code>:</p>
<div><pre><code class="language-swift">protocol KeyedEncodingContainerProtocol {
// ...
/// Encodes the given value for the given key if it is not `nil`.
///
/// - parameter value: The value to encode.
/// - parameter key: The key to associate the value with.
/// - throws: `EncodingError.invalidValue` if the given value is invalid in the current context for this format.
func encodeIfPresent<T : Encodable>(_ value: T?, forKey key: Key) throws
// Repeat for primitive types.
}</code></pre></div>
<p>This convenience method will be given a default implementation in an extension which calls into the regular <code>encode(_:forKey:)</code> overloads if the value is non-<code>nil</code>.</p>
<p>By default, derived conformance for <code>Encodable</code> will call <code>encodeIfPresent</code> for <code>Optional</code> properties so information is not encoded unless the properties are non-<code>nil</code>.</p>
<p></details></p>
<p><details>
<summary><strong><code>SingleValueEncodingContainer</code>/<code>SingleValueDecodingContainer</code> <code>codingPath</code> Exposition</strong></summary></p>
<p>As an oversight in the original draft of the proposal, the above <code>codingPath</code> property was accidentally left off of <code>SingleValueEncodingContainer</code> and <code>SingleValueDecodingContainer</code>. We intend to add this property to both protocols to bring it to parity with keyed and unkeyed containers:</p>
<div><pre><code class="language-swift">protocol SingleValueEncodingContainer {
/// The path of coding keys taken to get to this point in encoding.
var codingPath: [CodingKey]
}
protocol SingleValueDecodingContainer {
/// The path of coding keys taken to get to this point in decoding.
var codingPath: [CodingKey]
}</code></pre></div>
<p></details></p>
<p><details>
<summary><strong><code>UnkeyedDecodingContainer</code> Current Index</strong></summary></p>
<p>During decoding using unkeyed containers, it can be helpful to know the current index of the container for error reporting. We intend to add a <code>currentIndex</code> property to such containers to accompany <code>count</code> and <code>isAtEnd</code>:</p>
<div><pre><code class="language-swift">protocol UnkeyedDecodingContainer {
// The following two already exist:
/// Returns the number of elements (if known) contained within this container.
var count: Int? { get }
/// Returns whether there are no more elements left to be decoded in the container.
var isAtEnd: Bool { get }
// New addition:
/// Returns the position of the next element to be decoded within `self`.
var currentIndex: Int { get }
}</code></pre></div>
<p></details></p>
<h2 id="toc_14">Impact on Existing Code</h2>
<p>The Swift standard library will need to be updated to reflect these changes, as well as the implementations of <code>JSON{Encoder,Decoder}</code> and <code>PropertyList{Encoder,Decoder}</code> in the Foundation overlay.</p>
<p>This is a change primarily for writers of <code>Encoder</code>s and <code>Decoder</code>s; anyone currently writing an <code>Encoder</code> or <code>Decoder</code> will have to adopt these changes. Except for <code>JSONEncoder</code> output formatting changes, these will not be source-breaking changes for any end consumers of the API, if there are any at the moment. Since this is a brand new API, these are not expected to be risky changes.</p>
<h2 id="toc_15">Alternatives Considered</h2>
<h3 id="toc_16"><code>codingPath</code> Optionality</h3>
<ul>
<li>Keeping <code>codingPath</code> as <code>[CodingKey?]</code>. However, this makes debugging any errors through unkeyed containers difficult as the index through which the error occurred totally opaque.</li>
</ul>
<h3 id="toc_17">Optional Conformance to Codable</h3>
<ul>
<li>Not adding <code>Codable</code> conformance to <code>Optional</code> at all. The API today does not have <code>Optional</code> conform to <code>Codable</code>. However, this leaves many types (especially generic ones) non-<code>Codable</code> by default, e.g. <code>[T?]</code>. Supporting optionality requires adding additional extensions (e.g. <code>extension Array : Encodable where Element == Optional<T>, T : Encodable</code>), but this duplicates a lot of work, and will not handle arbitrary levels of nested optionality (e.g. <code>[T??]</code>)</li>
<li>Not adding <code>Codable</code> conformance to <code>Optional</code> until conditional conformance is added. This has the same caveats as mentioned above for collections, and unlike collections, requires changes to the API which cannot be added later (e.g. adding <code>encodeNil(...)</code> as a primitive type). We can make the base changes now and add <code>Optional</code> conformance later, but this doesn't address the issues listed in the previous bullet point, and is a confusing hole in the API</li>
</ul>
<h3 id="toc_18">Stronger Reference Semantic Requirements on Encoders and Decoders</h3>
<ul>
<li>Not adding these more stringent requirements. However, this means that anyone who wants to encode an object graph which relies on reference semantics has no way of knowing whether their object graph will round-trip correctly or not. This is very error-prone, and subtly so, as decoding an object graph containing copies of objects instead of shared references is likely not immediately apparent</li>
<li>Adding a way to tell dynamically whether a given <code>Encoder</code> supports reference semantics (either via a boolean accessor <code>supportsReferenceSemantics</code> or a downcast of the encoding containers [which would require even more duplication of API]). This isn't terribly useful, though, because there's not much that can be done short of aborting the encoding process</li>
<li>Adding these requirements, but on a new method called <code>encodeReference(...)</code> which clearly hints to both writers of an <code>Encoder</code> and consumers of the API that it is required to encode a reference to the given object. However, it is too easy to accidentally call <code>encode(...)</code> instead of <code>encodeReference(...)</code>, a mistake which can lead to very subtle bugs</li>
<li>Adding these requirements on an overload of <code>encode<T : Encodable>(...)</code> which takes <code>encode<T : Encodable & AnyObject>(...)</code> like <code>encodeWeak(...)</code> does. However, this doesn't seem to add much benefit since it is trivial to check <code>if value is AnyObject</code> in <code>encode<T : Encodable>(...)</code>, and would lead to duplication of code</li>
</ul>
<h3 id="toc_19">New Error Types</h3>
<ul>
<li>Not changing anything and continuing to throw <code>NSError</code> instances from the Swift standard library. However, this adds another point at which there is an stdlib ↔ Foundation dependency (except this time it's explicit and part of the API), and the thrown errors are opaque without loading Foundation</li>
<li>Not introducing new nested types (<code>EncodingError.Context</code> and <code>DecodingError.Context</code>) and instead including the contextual information directly in the enum cases (e.g. <code>case typeMismatch(Any.Type, [CodingKey?], String)</code>, or with descriptive labels: <code>case typeMismatch(expectedType: Any.Type, codingPath: [CodingKey?], debugDescription: String)</code>). This has the benefit of not introducing additional types, but at the potential cost of making adding more contextual fields in the future a source-breaking change (i.e. adding new fields to <code>Context</code> is possible as a non-source-breaking change; adding new associated value fields to the enum is not)</li>
</ul>
<h3 id="toc_20">Collection Conformance to Codable</h3>
<ul>
<li><p>Not introducing collection conformance to <code>Codable</code> until conditional conformance is an available feature. With the feature as implemented currently, nothing prevents an API consumer from trying to encode <code>[MyNonCodableType]</code> statically; they will get a fatal error at runtime, which is unfortunate. However, the alternative (preventing <code>Array</code>, <code>Set</code>, and <code>Dictionary</code> from being <code>Codable</code>) makes for extremely poor user experience:</p>
<ol>
<li>Without additional compiler changes, any type containing an <code>Array</code>, <code>Set</code>, or <code>Dictionary</code> would not be able to derive <code>Codable</code>, requiring a custom implementation</li>
<li><p>Encoding an <code>Array</code> or <code>Set</code> requires requesting an <code>UnkeyedEncodingContainer</code> and calling <code>encode(contentsOf:)</code>, like so:</p>
<div><pre><code class="language-swift">var arrayContainer = container.nestedUnkeyedContainer(forKey: .myArray)
arrayContainer.encode(contentsOf: self.myArray)
var setContainer = container.nestedUnkeyedContainer(forKey: .mySet)
setContainer.encode(contentsOf: self.mySet)</code></pre></div>
<p>Decoding has a worse user experience, however, as the container must be decoded manually:</p>
<div><pre><code class="language-swift">self.myArray = []
var arrayContainer = try container.nestedUnkeyedContainer(forKey: .myArray)
while !arrayContainer.isAtEnd {
let element = try arrayContainer.decode(MyType.self)
self.myArray.append(element)
}
self.mySet = Set()
var setContainer = try container.nestedUnkeyedContainer(forKey: .mySet)
while !setContainer.isAtEnd {
let element = try setContainer.decode(MyType.self)
self.mySet.insert(element)
}</code></pre></div></li>
<li><p>Encoding a <code>Dictionary</code> is significantly worse since keyed containers require a static key type. This means that every API consumer would have to invent a key type for their dictionary to use (which will likely have to accept arbitrary <code>String</code>s and <code>Int</code>s). Given a dictionary whose keys and values are not <code>String</code> or <code>Int</code> (i.e. the key is not easily convertible to a <code>CodingKey</code>), they would also have to invent their own way of encoding the container. Once that representation is in an archive, they will have to continue supporting it forever, even after <code>Dictionary</code> gains <code>Codable</code> conformance and a standard representation in archives.</p></li>
</ol></li>
</ul>
<p>Given these caveats, we think that providing this less safe implementation at the moment and refining it later is better than not offering it at all.</p>
<script type="text/javascript">
var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function(e){var t=n.util.type(e);switch(t){case"Object":var a={};for(var r in e)e.hasOwnProperty(r)&&(a[r]=n.util.clone(e[r]));return a;case"Array":return e.map&&e.map(function(e){return n.util.clone(e)})}return e}},languages:{extend:function(e,t){var a=n.util.clone(n.languages[e]);for(var r in t)a[r]=t[r];return a},insertBefore:function(e,t,a,r){r=r||n.languages;var l=r[e];if(2==arguments.length){a=arguments[1];for(var i in a)a.hasOwnProperty(i)&&(l[i]=a[i]);return l}var o={};for(var s in l)if(l.hasOwnProperty(s)){if(s==t)for(var i in a)a.hasOwnProperty(i)&&(o[i]=a[i]);o[s]=l[s]}return n.languages.DFS(n.languages,function(t,n){n===r[e]&&t!=e&&(this[t]=o)}),r[e]=o},DFS:function(e,t,a,r){r=r||{};for(var l in e)e.hasOwnProperty(l)&&(t.call(e,l,e[l],a||l),"Object"!==n.util.type(e[l])||r[n.util.objId(e[l])]?"Array"!==n.util.type(e[l])||r[n.util.objId(e[l])]||(r[n.util.objId(e[l])]=!0,n.languages.DFS(e[l],t,l,r)):(r[n.util.objId(e[l])]=!0,n.languages.DFS(e[l],t,null,r)))}},plugins:{},highlightAll:function(e,t){var a={callback:t,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};n.hooks.run("before-highlightall",a);for(var r,l=a.elements||document.querySelectorAll(a.selector),i=0;r=l[i++];)n.highlightElement(r,e===!0,a.callback)},highlightElement:function(t,a,r){for(var l,i,o=t;o&&!e.test(o.className);)o=o.parentNode;o&&(l=(o.className.match(e)||[,""])[1],i=n.languages[l]),t.className=t.className.replace(e,"").replace(/\s+/g," ")+" language-"+l,o=t.parentNode,/pre/i.test(o.nodeName)&&(o.className=o.className.replace(e,"").replace(/\s+/g," ")+" language-"+l);var s=t.textContent,u={element:t,language:l,grammar:i,code:s};if(!s||!i)return n.hooks.run("complete",u),void 0;if(n.hooks.run("before-highlight",u),a&&_self.Worker){var c=new Worker(n.filename);c.onmessage=function(e){u.highlightedCode=e.data,n.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,r&&r.call(u.element),n.hooks.run("after-highlight",u),n.hooks.run("complete",u)},c.postMessage(JSON.stringify({language:u.language,code:u.code,immediateClose:!0}))}else u.highlightedCode=n.highlight(u.code,u.grammar,u.language),n.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,r&&r.call(t),n.hooks.run("after-highlight",u),n.hooks.run("complete",u)},highlight:function(e,t,r){var l=n.tokenize(e,t);return a.stringify(n.util.encode(l),r)},tokenize:function(e,t){var a=n.Token,r=[e],l=t.rest;if(l){for(var i in l)t[i]=l[i];delete t.rest}e:for(var i in t)if(t.hasOwnProperty(i)&&t[i]){var o=t[i];o="Array"===n.util.type(o)?o:[o];for(var s=0;s<o.length;++s){var u=o[s],c=u.inside,g=!!u.lookbehind,h=!!u.greedy,f=0,d=u.alias;u=u.pattern||u;for(var p=0;p<r.length;p++){var m=r[p];if(r.length>e.length)break e;if(!(m instanceof a)){u.lastIndex=0;var y=u.exec(m),v=1;if(!y&&h&&p!=r.length-1){var b=r[p+1].matchedStr||r[p+1],k=m+b;if(p<r.length-2&&(k+=r[p+2].matchedStr||r[p+2]),u.lastIndex=0,y=u.exec(k),!y)continue;var w=y.index+(g?y[1].length:0);if(w>=m.length)continue;var _=y.index+y[0].length,P=m.length+b.length;if(v=3,P>=_){if(r[p+1].greedy)continue;v=2,k=k.slice(0,P)}m=k}if(y){g&&(f=y[1].length);var w=y.index+f,y=y[0].slice(f),_=w+y.length,S=m.slice(0,w),O=m.slice(_),j=[p,v];S&&j.push(S);var A=new a(i,c?n.tokenize(y,c):y,d,y,h);j.push(A),O&&j.push(O),Array.prototype.splice.apply(r,j)}}}}}return r},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,l=0;r=a[l++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.matchedStr=a||null,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var l={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==l.type&&(l.attributes.spellcheck="true"),e.alias){var i="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}n.hooks.run("wrap",l);var o="";for(var s in l.attributes)o+=(o?" ":"")+s+'="'+(l.attributes[s]||"")+'"';return"<"+l.tag+' class="'+l.classes.join(" ")+'" '+o+">"+l.content+"</"+l.tag+">"},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,l=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),l&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",n.highlightAll)),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
</script>
<script type="text/javascript">
Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/};
</script>
<script type="text/javascript">
Prism.languages.swift=Prism.languages.extend("clike",{string:{pattern:/("|')(\\(?:\((?:[^()]|\([^)]+\))+\)|\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,inside:{interpolation:{pattern:/\\\((?:[^()]|\([^)]+\))+\)/,inside:{delimiter:{pattern:/^\\\(|\)$/,alias:"variable"}}}}},keyword:/\b(as|associativity|break|case|catch|class|continue|convenience|default|defer|deinit|didSet|do|dynamic(?:Type)?|else|enum|extension|fallthrough|final|for|func|get|guard|if|import|in|infix|init|inout|internal|is|lazy|left|let|mutating|new|none|nonmutating|operator|optional|override|postfix|precedence|prefix|private|Protocol|public|repeat|required|rethrows|return|right|safe|self|Self|set|static|struct|subscript|super|switch|throws?|try|Type|typealias|unowned|unsafe|var|weak|where|while|willSet|__(?:COLUMN__|FILE__|FUNCTION__|LINE__))\b/,number:/\b([\d_]+(\.[\de_]+)?|0x[a-f0-9_]+(\.[a-f0-9p_]+)?|0b[01_]+|0o[0-7_]+)\b/i,constant:/\b(nil|[A-Z_]{2,}|k[A-Z][A-Za-z_]+)\b/,atrule:/@\b(IB(?:Outlet|Designable|Action|Inspectable)|class_protocol|exported|noreturn|NS(?:Copying|Managed)|objc|UIApplicationMain|auto_closure)\b/,builtin:/\b([A-Z]\S+|abs|advance|alignof(?:Value)?|assert|contains|count(?:Elements)?|debugPrint(?:ln)?|distance|drop(?:First|Last)|dump|enumerate|equal|filter|find|first|getVaList|indices|isEmpty|join|last|lexicographicalCompare|map|max(?:Element)?|min(?:Element)?|numericCast|overlaps|partition|print(?:ln)?|reduce|reflect|reverse|sizeof(?:Value)?|sort(?:ed)?|split|startsWith|stride(?:of(?:Value)?)?|suffix|swap|toDebugString|toString|transcode|underestimateCount|unsafeBitCast|with(?:ExtendedLifetime|Unsafe(?:MutablePointers?|Pointers?)|VaList))\b/}),Prism.languages.swift.string.inside.interpolation.inside.rest=Prism.util.clone(Prism.languages.swift);
</script>
<script type="text/javascript">
!function(){if("undefined"!=typeof self&&self.Prism&&self.document){var e={html:"HTML",xml:"XML",svg:"SVG",mathml:"MathML",css:"CSS",clike:"C-like",javascript:"JavaScript",abap:"ABAP",actionscript:"ActionScript",apacheconf:"Apache Configuration",apl:"APL",applescript:"AppleScript",asciidoc:"AsciiDoc",aspnet:"ASP.NET (C#)",autoit:"AutoIt",autohotkey:"AutoHotkey",basic:"BASIC",csharp:"C#",cpp:"C++",coffeescript:"CoffeeScript","css-extras":"CSS Extras",fsharp:"F#",glsl:"GLSL",http:"HTTP",inform7:"Inform 7",json:"JSON",latex:"LaTeX",lolcode:"LOLCODE",matlab:"MATLAB",mel:"MEL",nasm:"NASM",nginx:"nginx",nsis:"NSIS",objectivec:"Objective-C",ocaml:"OCaml",parigp:"PARI/GP",php:"PHP","php-extras":"PHP Extras",powershell:"PowerShell",jsx:"React JSX",rest:"reST (reStructuredText)",sas:"SAS",sass:"Sass (Sass)",scss:"Sass (Scss)",sql:"SQL",typescript:"TypeScript",vhdl:"VHDL",vim:"vim",wiki:"Wiki markup",yaml:"YAML"};Prism.hooks.add("before-highlight",function(s){var a=s.element.parentNode;if(a&&/pre/i.test(a.nodeName)){var t,i,r=a.getAttribute("data-language")||e[s.language]||s.language.substring(0,1).toUpperCase()+s.language.substring(1),l=a.previousSibling;l&&/\s*\bprism-show-language\b\s*/.test(l.className)&&l.firstChild&&/\s*\bprism-show-language-label\b\s*/.test(l.firstChild.className)?i=l.firstChild:(t=document.createElement("div"),i=document.createElement("div"),i.className="prism-show-language-label",t.className="prism-show-language",t.appendChild(i),a.parentNode.insertBefore(t,a)),i.innerHTML=r}})}}();
</script>
</body>
</html>