I'm having difficulties decoding and encoding one of my classes in Swift. I have tried following the Encoding and Decoding Custom Types documentation but with no luck.
My class layout is as follows:
public struct MapLocation: Identifiable, Codable {
#DocumentID public var id: String?
let originLocation: [MapLandmark]
let destinationLocation: [MapLandmark]
enum CodingKeys: String, CodingKey {
case originLocation
case destinationLocation
}
}
import Foundation
import MapKit
struct MapLandmark: Codable {
let placemark: MKPlacemark
var id: UUID {
return UUID()
}
var name: String {
self.placemark.name ?? ""
}
var title: String {
self.placemark.title ?? ""
}
var coordinate: CLLocationCoordinate2D {
self.placemark.coordinate
}
}
I have tried adding the encoding and decoding classes from the above link but keep running into errors. What is the best way to implement a solution?
EDIT:
I forgot to mention the errors I am getting are:
"Type 'MapLandmark' does not conform to protocol 'Decodable'"
"Type 'MapLandmark' does not conform to protocol 'Encodable"
MKPlacemark conforms to NSSecureCoding. You can just use NSKeyedArchiver and NSKeyedUnarchiver to encode/decode it. UUID already conforms to Codable. Try as follow:
import MapKit
extension NSSecureCoding {
func archived() throws -> Data {
try NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: false)
}
}
extension Data {
func unarchived<T: NSSecureCoding>() throws -> T? {
try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(self) as? T
}
}
struct MapLandmark: Codable {
let placemark: MKPlacemark
let id: UUID
func encode(to encoder: Encoder) throws {
var unkeyedContainer = encoder.unkeyedContainer()
try unkeyedContainer.encode(placemark.archived())
try unkeyedContainer.encode(id)
}
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
placemark = try container.decode(Data.self).unarchived()!
id = try container.decode(UUID.self)
}
}
Related
struct Person: Decodable {
let firstName: String
}
var data = """
{"firstName": "Fai"}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let parsed = try decoder.decode(Person.self, from: data)
JSONDecoder will decode the data, which is comfirmed to Decodable protocol.
So I want to know how swift implement this. But I can not get any idea in the source code: https://github.com/apple/swift/blob/main/stdlib/public/core/Codable.swift
The Decodable protocol only need to implement an init(from decoder: Decoder) function.
If I am going to do it, I will make an extension for struct:
extension struct: Decodable {
init(from decoder: Decoder) {...}
}
But when I delete the Decodable on my example, the compiler give errors:
Instance method 'decode(_:from:)' requires that 'Person' conform to 'Decodable'
So this is not the swift way to implement this. How's swift way? And where's the source code?
You can see what the compiler writes for you using -print-ast:
echo 'struct Person: Decodable {
let firstName: String
}' | swiftc -print-ast -
This will output most of the auto-generated code (it should include all of the Codable conformances, but there are a few other kinds of auto-generated code that won't include their implementation):
internal struct Person : Decodable {
internal let firstName: String
private enum CodingKeys : CodingKey {
case firstName
#_implements(Equatable, ==(_:_:)) fileprivate static func __derived_enum_equals(_ a: Person.CodingKeys, _ b: Person.CodingKeys) -> Bool {
private var index_a: Int
switch a {
case .firstName:
index_a = 0
}
private var index_b: Int
switch b {
case .firstName:
index_b = 0
}
return index_a == index_b
}
fileprivate func hash(into hasher: inout Hasher) {
private var discriminator: Int
switch self {
case .firstName:
discriminator = 0
}
hasher.combine(discriminator)
}
private init?(stringValue: String) {
switch stringValue {
case "firstName":
self = Person.CodingKeys.firstName
default:
return nil
}
}
private init?(intValue: Int) {
return nil
}
fileprivate var hashValue: Int {
get {
return _hashValue(for: self)
}
}
fileprivate var intValue: Int? {
get {
return nil
}
}
fileprivate var stringValue: String {
get {
switch self {
case .firstName:
return "firstName"
}
}
}
}
internal init(firstName: String)
internal init(from decoder: Decoder) throws {
#_hasInitialValue private let container: KeyedDecodingContainer<Person.CodingKeys> = try decoder.container(keyedBy: Person.CodingKeys.self)
self.firstName = try container.decode(String.self, forKey: Person.CodingKeys.firstName)
}
}
For the full implementation details, see DerivedConformanceCodable.cpp. Probably of most interest to your question is deriveBodyDecodable_init.
Swift recursive struct can be solved by helper struct as given below. Unfortunately, this is not Codable compliant:
class Box<T> {
let boxed: T
init(_ thingToBox: T) { boxed = thingToBox }
}
struct ContainerClass: Identifiable, Codable {
let id: UUID?
var classname: String
var parent: Box<ContainerClass>?
private enum ContainerClassKeys: String, CodingKey {
case id
case classname
case parent
}
}
Error: Type 'ContainerClass' does not conform to protocol 'Encodable'
You must make Box conform to Codable.
You can use conditional conformance to only make Box Codable when its generic type T is Codable as well.
final class Box<T> {
let boxed: T
init(_ thingToBox: T) { boxed = thingToBox }
}
struct ContainerClass: Identifiable, Codable {
let id: UUID?
var classname: String
var parent: Box<ContainerClass>?
private enum ContainerClassKeys: String, CodingKey {
case id
case classname
case parent
}
}
extension Box: Codable where T: Codable {
convenience init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let boxed = try container.decode(T.self)
self.init(boxed)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(boxed)
}
}
Alternatively, if you always want T to be Codable, you can simplify the code to
class Box<T: Codable>: Codable {
let boxed: T
init(_ thingToBox: T) { boxed = thingToBox }
}
I have the following JSON:
{
"header":{
"namespace":"Device",
"name":"Response",
"messageID":"60FA815A-DC432316",
"payloadVersion":"1"
},
"payload":{
"device":{
"firmware":"1.23W",
"name":"Device 1",
"uuid":"0ba64a0c-7a88b278-0001",
"security":{
"code":"aXdAPqd2OO9sZ6evLKjo2Q=="
}
},
"system":{
"uptime":5680126
}
}
}
I created the Swift structs using quicktype.io:
// MARK: - Welcome
struct Welcome: Codable {
let header: Header
let payload: Payload
}
// MARK: - Header
struct Header: Codable {
let namespace, name, messageID, payloadVersion: String
}
// MARK: - Payload
struct Payload: Codable {
let device: Device
let system: System
}
// MARK: - Device
struct Device: Codable {
let firmware, name, uuid: String
let security: Security
}
// MARK: - Security
struct Security: Codable {
let code: String
}
// MARK: - System
struct System: Codable {
let uptime: Int
}
However, I already have a Device type, that is a bit more minimal:
struct Device: Identifiable {
let id: UUID
let ip: String
let name: String
let firmware: String
let uptime: Double
// ...
}
How can I nicely decode the raw JSON data into my Device struct? Note that my Device is flat and has fields, that are more deeply nested in the original API response model. Do I a custom Decodable implementation?
You can create intermediate CodingKeys, but this often gets pretty tedious and unnecessary. Instead you can make a general-purpose "string-key" like:
struct AnyStringKey: CodingKey, Hashable, ExpressibleByStringLiteral {
var stringValue: String
init(stringValue: String) { self.stringValue = stringValue }
init<S: StringProtocol>(_ stringValue: S) { self.init(stringValue: String(stringValue)) }
var intValue: Int?
init?(intValue: Int) { return nil }
init(stringLiteral value: String) { self.init(value) }
}
With that, you can navigate your structure pretty easily in a single decoder init by decoding nested containers:
extension Device: Decodable {
init(from decoder: Decoder) throws {
let root = try decoder.container(keyedBy: AnyStringKey.self)
let header = try root.nestedContainer(keyedBy: AnyStringKey.self, forKey: "header")
self.name = try header.decode(String.self, forKey: "name")
let payload = try root.nestedContainer(keyedBy: AnyStringKey.self, forKey: "payload")
let device = try payload.nestedContainer(keyedBy: AnyStringKey.self, forKey: "device")
self.id = try device.decode(UUID.self, forKey: "uuid")
self.firmware = try device.decode(String.self, forKey: "firmware")
let system = try payload.nestedContainer(keyedBy: AnyStringKey.self, forKey: "system")
self.uptime = try system.decode(Double.self, forKey: "uptime")
}
}
(I skipped ip because it's not in your data, and I assumed that your UUID was just a typo since it's not valid.)
With this, you should be able to decode any part you need.
This is very straightforward and standard, but if you have a lot of things to decode it can get a little tedious. You can improve it with a helper function in that case.
extension KeyedDecodingContainer {
func decode<T>(_ type: T.Type, forPath path: String) throws -> T
where T : Decodable, Key == AnyStringKey {
let components = path.split(separator: ".")
guard !components.isEmpty else {
throw DecodingError.keyNotFound(AnyStringKey(path),
.init(codingPath: codingPath,
debugDescription: "Could not find path \(path)",
underlyingError: nil))
}
if components.count == 1 {
return try decode(type, forKey: AnyStringKey(components[0]))
} else {
let container = try nestedContainer(keyedBy: AnyStringKey.self, forKey: AnyStringKey(components[0]))
return try container.decode(type, forPath: components.dropFirst().joined(separator: "."))
}
}
}
With this, you can access values by a dotted-path syntax:
extension Device: Decodable {
init(from decoder: Decoder) throws {
let root = try decoder.container(keyedBy: AnyStringKey.self)
self.name = try root.decode(String.self, forPath: "header.name")
self.id = try root.decode(UUID.self, forPath: "payload.device.uuid")
self.firmware = try root.decode(String.self, forPath: "payload.device.firmware")
self.uptime = try root.decode(Double.self, forPath: "payload.system.uptime")
}
}
I see two quick possible solutions:
Solution 1:
Rename the Codable Device:
struct Device: Codable {
...
}
into
struct DeviceFromAPI: Codable {
...
}
And then replace
struct Payload: Codable {
let device: Device
...
}
into
struct Payload: Codable {
let device: DeviceFromAPI
...
}
Solution2:
Use nested structures.
Put everything inside Welcome (which is the default QuickType.io name by the way, might be interesting to rename it).
struct Welcome: Codable {
let header: Header
let payload: Payload
// MARK: - Header
struct Header: Codable {
let namespace, name, messageID, payloadVersion: String
}
...
}
Go even if needed to put Device in Payload.
Then, you just have to use Welcome.Payload.Device or Welcome.Device (depending on how you nested it) when you want to refer to your Codable Device, and just Device when it's your own.
Then
Then, just have a custom init() for Device with the Codable Device as a parameter.
extension Device {
init(withCodableDevice codableDevice: DeviceFromAPI) {
self.firmware = codableDevice.firmware
...
}
}
or with solution 2:
extension Device {
init(withCodableDevice codableDevice: Welcome.Payload.Device) {
self.firmware = codableDevice.firmware
...
}
}
Let's say I have decodable property wrapper:
#propertyWrapper
struct OptionalDecodable<Value: Decodable>: Decodable {
var wrappedValue: Value?
}
The compiler does synthesize init for the following
struct Model: Decodable {
#OptionalDecodable private(set) var string: String?
}
To test if this works I just try to decode empty JSON (i.e. "{}")
However, string property is not treated as optional, i.e. when there's no string key I get an error that key was not found.
Is there a work around this?
I'm not sure if this is the best way, but the issue is that wrappedValue type of the property wrapper has to match the property's type, and String is different than String?.
One approach to overcome this is to make the property wrapper generic, but constrain in such a way that would allow you to initialize the type from a String or an Int:
protocol ExpressibleByString {
init(fromString: String)
}
extension String: ExpressibleByString {
init(fromString: String) { self = fromString }
}
extension Optional: ExpressibleByString where Wrapped == String {
init(fromString: String) { self = fromString }
}
#propertyWrapper
struct IntOrString<V: ExpressibleByString & Decodable>: Decodable {
var wrappedValue: V
}
extension IntOrString {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let int = try container.decode(Int.self)
wrappedValue = .init(fromString: int.description)
} catch DecodingError.typeMismatch {
wrappedValue = try .init(fromString: container.decode(String.self))
}
}
}
extension KeyedDecodingContainer {
func decode<V: ExpressibleByNilLiteral>(_ t: IntOrString<V>.Type, forKey key: K) throws -> IntOrString<V> {
if let v = try decodeIfPresent(t, forKey: key) {
return v
}
return IntOrString(wrappedValue: nil)
}
}
Then you could use it on both optional and non-optional String:
struct Foo: Decodable {
#IntOrString
var p1: String?
#IntOrString
var p2: String
}
In SwiftUI beta 5, Apple introduced the #Published annotation. This annotation is currently blocking this class from conforming to the Codable protocols.
How can I conform to these protocols so I can encode and decode this class to JSON? You can ignore the image property for now.
class Meal: ObservableObject, Identifiable, Codable {
enum CodingKeys: String, CodingKey {
case id
case name
case ingredients
case numberOfPeople
}
var id = Globals.generateRandomId()
#Published var name: String = "" { didSet { isInputValid() } }
#Published var image = Image("addImage")
#Published var ingredients: [Ingredient] = [] { didSet { isInputValid() } }
#Published var numberOfPeople: Int = 2
#Published var validInput = false
func isInputValid() {
if name != "" && ingredients.count > 0 {
validInput = true
}
}
}
Add the init() and encode() methods to your class:
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(Int.self, forKey: .id)
name = try values.decode(String.self, forKey: .name)
ingredients = try values.decode([Ingredient].self, forKey: .ingredients)
numberOfPeople = try values.decode(Int.self, forKey: .numberOfPeople)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
try container.encode(ingredients, forKey: .ingredients)
try container.encode(numberOfPeople, forKey: .numberOfPeople)
}
After much hacking around, I managed to add Codable directly to #Published
Note I had to update this for iOS14. This illustrates the danger of digging around in undocumented types...
Just add the code below in a file and your #Published variables will be automatically Codable (provided they are based on a Codable type)
more info here
https://blog.hobbyistsoftware.com/2020/01/adding-codeable-to-published/
code here:
import Foundation
import SwiftUI
extension Published:Decodable where Value:Decodable {
public init(from decoder: Decoder) throws {
let decoded = try Value(from:decoder)
self = Published(initialValue:decoded)
}
}
extension Published:Encodable where Value:Decodable {
private var valueChild:Any? {
let mirror = Mirror(reflecting: self)
if let valueChild = mirror.descendant("value") {
return valueChild
}
//iOS 14 does things differently...
if let valueChild = mirror.descendant("storage","value") {
return valueChild
}
//iOS 14 does this too...
if let valueChild = mirror.descendant("storage","publisher","subject","currentValue") {
return valueChild
}
return nil
}
public func encode(to encoder: Encoder) throws {
guard let valueChild = valueChild else {
fatalError("Mirror Mirror on the wall - why no value y'all : \(self)")
}
if let value = valueChild.value as? Encodable {
do {
try value.encode(to: encoder)
return
} catch let error {
assertionFailure("Failed encoding: \(self) - \(error)")
}
}
else {
assertionFailure("Decodable Value not decodable. Odd \(self)")
}
}
}
A more efficient variant without Mirror
Published+Value.swift
private class PublishedWrapper<T> {
#Published private(set) var value: T
init(_ value: Published<T>) {
_value = value
}
}
extension Published {
var unofficialValue: Value {
PublishedWrapper(self).value
}
}
Published+Codable.swift
extension Published: Decodable where Value: Decodable {
public init(from decoder: Decoder) throws {
self.init(wrappedValue: try .init(from: decoder))
}
}
extension Published: Encodable where Value: Encodable {
public func encode(to encoder: Encoder) throws {
try unofficialValue.encode(to: encoder)
}
}
As for Decodable, we're all answering with the same thing here. Initialize the Published with a decoded Value.
extension Published: Decodable where Value: Decodable {
public init(from decoder: Decoder) throws {
self.init(initialValue: try .init(from: decoder))
}
}
On to Encodable…
Unlike your average property wrapper, Published does not employ wrappedValue. Instead, accessing a Published value triggers a static subscript, which allows it to call objectWillChange on the ObservableObject when set.
Behind the scenes, your Meal.validInput, for example, relies on this code:
Published[
_enclosingInstance: self,
wrapped: \.validInput,
storage: \._validInput
]
_enclosingInstance is necessary for publishing changes, when set, but all it does for get is specify how to access the Published, using this:
_enclosingInstance[keyPath: storageKeyPath]
wrapped is useless for Published.
You always need to supply the subscript with a class instance, but this "_enclosingInstance" does not need to be an ObservableObject.
As such, you can store the Published via another object, and encode its stored value like this:
public extension Published {
/// The stored value of a `Published`.
/// - Note: Only useful when not having access to the enclosing class instance.
var value: Value { Storage(self).value }
private final class Storage {
init(_ published: Published) {
self.published = published
}
var value: Value {
Published[
_enclosingInstance: self,
wrapped: \.never,
storage: \.published
]
}
/// Will never be called, but is necessary to provide a `KeyPath<Value>` for the static subscript.
private var never: Value {
get { fatalError() }
set { fatalError() }
}
/// "`var`" only because the static subscript requires a `WritableKeyPath`.
/// It will never be mutated.
private var published: Published<Value>
}
}
extension Published: Encodable where Value: Encodable {
public func encode(to encoder: Encoder) throws {
try value.encode(to: encoder)
}
}
Alternatively, you could use this for the entirety of the body of Storage. It's just not as clear about documenting how it works.
#Published private(set) var value: Value
init(_ published: Published) {
_value = published
}
Storage will not keep a reference to the ObservableObject, so it's only suitable for capturing values—which is all Encodable needs. Why Apple has not provided us with a built-in solution after all this time, I have no idea.