Getting back the underlying data from JSONSerialization
is the exact idea behind Unevaluated
— the difference between AnyCodable
and Unevaluated
is that AnyCodable
attempts to decode types that it knows about on its own and is performing conversions; on the other hand, Unevaluated
would be a marker type which asks the Decoder
to stick whatever existing representation it has of the underlying data into its .value
and returns that. If the Decoder
supports Unevaluated
(e.g. in formats where it's possible to grab an underlying representation like NSNull
s, String
s, etc.), it can do that; if the Decoder
does nothing special to recognize Unevaluated
and ends up calling its init(from:)
, Unevaluated
will just throw a .typeMismatch
letting you know that the Decoder
doesn't support it.
To make this concrete, the following implementation of unbox
is how JSONDecoder
handles taking an existing container and coercing it into the value you've asked for:
fileprivate func unbox<T : Decodable>(_ value: Any, as type: T.Type) throws -> T? {
if type == Date.self || type == NSDate.self {
return try self.unbox(value, as: Date.self) as? T
} else if type == Data.self || type == NSData.self {
return try self.unbox(value, as: Data.self) as? T
} else if type == URL.self || type == NSURL.self {
guard let urlString = try self.unbox(value, as: String.self) else {
return nil
}
guard let url = URL(string: urlString) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Invalid URL string."))
}
return url as! T
} else if type == Decimal.self || type == NSDecimalNumber.self {
return try self.unbox(value, as: Decimal.self) as? T
} else {
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try type.init(from: self)
}
}
The value
passed in to the method is the value returned from JSONSerialization
(e.g. NSDictionary
containing NSString
and NSArray
); today, JSONDecoder
knows about a few special types and intercepts them to reinterpret the data. If we were unbox(value, as: Unevaluated.self)
today, we'd fall into that last case:
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try type.init(from: self)
That last line would end up calling Unevaluated.init(from:)
, which would just throw a .typeMismatch
. In order to support Unevaluated
, we'd expand unbox to do this:
fileprivate func unbox<T : Decodable>(_ value: Any, as type: T.Type) throws -> T? {
if type == Unevaluated.self {
return Unevaluated(value)
} else if ... {
// ...
} else {
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try type.init(from: self)
}
}
This would just return an Unevaluated
instances whose contents are exactly what JSONSerialization
returned: the collection of NS
values it decoded. When you get back the Unevaluated
type, its .value
contains exactly what you're getting at. (This is the raw value access you're looking for.)