@josh2 Is exactly right:
GoodWrapper
calls into the decoder to decode T
— this gives the decoder the opportunity to inspect T
and apply specific behavior for it. In this case, JSONDecoder
has special handling for URL
BadWrapper
calls into T
directly, never giving the Decoder
the opportunity to "see" T
, and always falling back to T
's implementation
As @ahti mentions, you should always give the Decoder
the opportunity to apply its logic to T
— otherwise, you're likely to get inconsistent results throughout an archive when decoding.
As for:
When creating a URL
, you can create one out of an absolute URL string, or with a path relative to a base URL
:
import Foundation
let url1 = URL(string: "https://example.com/sample/path?query=yes")!
let url2 = URL(string: "sample/path?query=yes", relativeTo: URL(string: "https://example.com"))!
Both of these URL
s have the same absolute representation:
print(url1.absoluteString) // https://example.com/sample/path?query=yes
print(url1.absoluteString == url2.absoluteString) // true
print(url1.absoluteURL == url2.absoluteURL) // true
However, these URL
s are not equal to one another:
print(url1.baseURL, url1.relativeString, url1.relativePath, separator: ", ") // nil, https://example.com/sample/path?query=yes, /sample/path
print(url2.baseURL, url2.relativeString, url2.relativePath, separator: ", ") // Optional(https://example.com), sample/path?query=yes, sample/path
print(url1 == url2) // false
The two URL
s' base and relative portions are not equal, so the URL
s are considered not equal. This stands out a little bit more when you look at URL
s whose baseURL
already has a path component:
let url1 = URL(string: "https://example.com/sample/path?query=yes")!
let url2 = URL(string: "sample/path?query=yes", relativeTo: URL(string: "https://example.com"))!
let url3 = URL(string: "path?query=yes", relativeTo: URL(string: "https://example.com/sample/"))!
print(url1.baseURL, url1.relativeString, url1.relativePath, separator: ", ") // nil, https://example.com/sample/path?query=yes, /sample/path
print(url2.baseURL, url2.relativeString, url2.relativePath, separator: ", ") // Optional(https://example.com), sample/path?query=yes, sample/path
print(url3.baseURL, url3.relativeString, url3.relativePath, separator: ", ") // Optional(https://example.com/sample/), path?query=yes, path
Here, both the relativeString
s (whole path relative to the root base URL) and the relativePath
s (path given relative to the full base URL) are different, and these URL
s are not equal.
Thus, in the general case, URL
always encodes conservatively, encoding its baseURL
and relativeString
separately to ensure that round-tripping a URL
with its implementation guarantees that the decoded value is equal to the original. However, this encoding format isn't terribly useful, especially when interfacing with 3rd-party APIs which expect URLs in the form of absolute strings. This is especially true for most JSON endpoints, so JSONEncoder
/JSONDecoder
special-case URL
s to produce absolute strings; although not all URL
s round-trip equally through JSON encoding, they are more generally useful this way. (PropertyListEncoder
, for instance, does not have this preference, and delegates to URL
to do its encoding.)