How to encode Realm's List<> type - swift

I am trying to encode my Realm database to JSON. Everything is working except the List<> encoding. So my question is, how would you encode List<>? Because the List doesn't conform to Encodable neighter Decodable protocol.
Right now I am doing this:
#objcMembers class User: Object, Codable{
dynamic var name: String = ""
let dogs = List<Dog>()
private enum UserCodingKeys: String, CodingKey {
case name
case dogs
}
convenience init(name: String) {
self.init()
self.name = name
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: UserCodingKeys.self)
try container.encode(name, forKey: .name)
}
#objcMembers class Dog: Object, Codable{
dynamic var name: String = ""
dynamic var user: User? = nil
private enum DogCodingKeys: String, CodingKey {
case name
}
convenience init(name: String) {
self.init()
name = name
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: DogCodingKeys.self)
try container.encode(name, forKey: .name)
}
}
and like this I am trying to do it:
var json: Any?
let user = RealmService.shared.getUsers()
var usersArray = [User]()
for user in users{
usersArray.append(user)
}
let jsonEncoder = JSONEncoder()
let jsonDecoder = JSONDecoder()
let encodedJson = try? jsonEncoder.encode(portfoliosArray)
if let data = encodedJson {
json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
if let json = json {
print(String(describing: json))
}
}
So the question is how I am able to encode the List<Dog>?

To make a Realm object model class with a property of type List conform to Encodable, you can simply convert the List to an Array in the encode(to:) method, which can be encoded automatically.
extension User: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.username, forKey: .username)
let dogsArray = Array(self.dogs)
try container.encode(dogsArray, forKey: .dogs)
}
}
Test classes I used (slightly different from the ones in your question, but I already had these on hand and the methods in question will be almost identical regardless of the variable names):
class Dog: Object,Codable {
#objc dynamic var id:Int = 0
#objc dynamic var name:String = ""
}
class User: Object, Decodable {
#objc dynamic var id:Int = 0
#objc dynamic var username:String = ""
#objc dynamic var email:String = ""
let dogs = List<Dog>()
private enum CodingKeys: String, CodingKey {
case id, username, email, dogs
}
required convenience init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
username = try container.decode(String.self, forKey: .username)
email = try container.decode(String.self, forKey: .email)
let dogsArray = try container.decode([Dog].self, forKey: .dogs)
dogs.append(objectsIn: dogsArray)
}
}
Test the encoding/decoding:
let userJSON = """
{
"id":1,
"username":"John",
"email":"example#ex.com",
"dogs":[
{"id":2,"name":"King"},
{"id":3,"name":"Kong"}
]
}
"""
do {
let decodedUser = try JSONDecoder().decode(User.self, from: userJSON.data(using: .utf8)!)
let encodedUser = try JSONEncoder().encode(decodedUser)
print(String(data: encodedUser, encoding: .utf8)!)
} catch {
print(error)
}
Output:
{"username":"John","dogs":[{"id":2,"name":"King"},{"id":3,"name":"Kong"}]}

You could resort to a mini-hack by making List conform to Encodable:
extension List: Encodable {
public func encode(to coder: Encoder) throws {
// by default List is not encodable, throw an exception
throw NSError(domain: "SomeDomain", code: -1, userInfo: nil)
}
}
// let's ask it to nicely encode when Element is Encodable
extension List where Element: Encodable {
public func encode(to coder: Encoder) throws {
var container = coder.unkeyedContainer()
try container.encode(contentsOf: self)
}
}
Two extensions are needed as you can't add protocol conformance and where clauses at the same time.
Also note that this approach doesn't provide compile-time checks - e.g. a List<Cat> will throw an exception an runtime if Cat is not encodable, instead of a nice compile time error.
The upside is lot of boilerplate code no longer needed:
#objcMembers class User: Object, Encodable {
dynamic var name: String = ""
let dogs = List<Dog>()
convenience init(name: String) {
self.init()
self.name = name
}
}
#objcMembers class Dog: Object, Encodable {
dynamic var name: String = ""
dynamic var user: User? = nil
convenience init(name: String) {
self.init()
name = name
}
}
This is also scalable, as adding new classes don't require any encoding code, but with the mentioned downside of not being fully type safe at compile time.

Related

Swift struct with custom encoder and decoder cannot conform to 'Encodable'

[Edited to provide a minimal reproducible example ]
This is the complete struct without non relevant vars and functions
InstrumentSet.swift
import Foundation
struct InstrumentsSet: Identifiable, Codable {
private enum CodingKeys: String, CodingKey {
case name = "setName"
case tracks = "instrumentsConfig"
}
var id: String { name }
var name: String
var tracks: [Track]
}
Track.swift
import Foundation
extension InstrumentsSet {
struct Track: Identifiable, Encodable {
private enum TrackKeys: String, CodingKey {
case id = "trackId"
case effects
}
let id: String
var effects: [Effect]?
}
}
extension InstrumentsSet.Track: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: TrackKeys.self)
id = try container.decode(String.self, forKey: .id)
effects = try container.decodeIfPresent([Effect].self, forKey: .effects)
}
}
Effect.swift
import Foundation
import AudioKit
import SoundpipeAudioKit
extension InstrumentsSet.Track {
enum Effect: Decodable {
private enum EffectKeys: String, CodingKey {
case effectType = "effectName"
case cutoffFrequency
case resonance
}
case lowPassFilter(LowPassFilterEffect)
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: EffectKeys.self)
let effectType = try container.decode(Effect.EffectType.self, forKey: .effectType)
switch effectType {
case .lowPassFilter:
let cutOffFrequency = try container.decode(ValueAndRange.self, forKey: .cutoffFrequency)
let resonance = try container.decode(ValueAndRange.self, forKey: .resonance)
self = .lowPassFilter(LowPassFilterEffect(cutOffFrequency: cutOffFrequency, resonance: resonance))
default:
fatalError("Not implemented!")
}
}
}
}
extension InstrumentsSet.Track.Effect {
enum EffectType: String, Decodable {
case lowPassFilter
}
}
extension InstrumentsSet.Track.Effect: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: EffectKeys.self)
//FIXME: This is the location of the error: Type 'ValueAndRange.Type' cannot conform to 'Encodable'
try container.encode(ValueAndRange.self, forKey: .cutoffFrequency)
}
}
The problem is ValueAndRange.self not not conforming to Encodable
I've followed multiple examples to get to this implementation:
import Foundation
import AudioKit
struct ValueAndRange: Encodable {
private enum ValueRangeKeys: String, CodingKey {
case value
case range
}
static var zero: ValueAndRange { .init(value: 0, range: [0, 0]) }
var value: AUValue
var range: [Double]
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: ValueRangeKeys.self)
try container.encode(value, forKey: .value)
try container.encode(range, forKey: .range)
}
}
extension ValueAndRange: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ValueRangeKeys.self)
value = try container.decode(AUValue.self, forKey: .value)
range = try container.decode([Double].self, forKey: .range)
}
}
I cannot see why this struct should not conform to Encodable. Maybe any of you got betters eyes (and brains) then I got?
Your encode function is incorrectly trying to encode a type, ValueAndRange.self, rather than a value.
Looking at the init(from:) method I think your encode function should look something like this
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: EffectKeys.self)
switch self {
case .lowPassFilter(let effect):
try container.encode(effect.cutOffFrequency, forKey: .cutoffFrequency)
try container.encode(effect.resonance, forKey: .resonance)
}
}
I didn't include .effectType in this code since I am uncertain of its usage (isn't it always the same hard coded string?).

Store custom data type in #AppStorage with optional initializer?

I'm trying to store a custom data type into AppStorage. To do so, the model conforms to RawRepresentable (followed this tutorial). It's working fine, but when I initialize the #AppStorage variable, it requires an initial UserModel value. I want to make the variable optional, so it can be nil if the user is signed out. Is this possible?
Within a class / view, I can init like this:
#AppStorage("user_model") private(set) var user: UserModel = UserModel(id: "", name: "", email: "")
But I want to init like this:
#AppStorage("user_model") private(set) var user: UserModel?
Model:
struct UserModel: Codable {
let id: String
let name: String
let email: String
enum CodingKeys: String, CodingKey {
case id
case name
case email
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
do {
id = try String(values.decode(Int.self, forKey: .id))
} catch DecodingError.typeMismatch {
id = try String(values.decode(String.self, forKey: .id))
}
self.name = try values.decode(String.self, forKey: .name)
self.email = try values.decode(String.self, forKey: .email)
}
init(id: String, name: String, email: String) {
self.id = id
self.name = name
self.email = email
}
}
// MARK: RAW REPRESENTABLE
extension UserModel: RawRepresentable {
// RawRepresentable allows a UserModel to be store in AppStorage directly.
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode(UserModel.self, from: data)
else {
return nil
}
self = result
}
var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return "[]"
}
return result
}
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(email, forKey: .email)
}
}
The code below works because you added a conformance UserModel: RawRepresentable:
#AppStorage("user_model") private(set) var user: UserModel = UserModel(id: "", name: "", email: "")
You need to do the same for UserModel? if you want the following to work:
#AppStorage("user_model") private(set) var user: UserModel? = nil
Here is a possible solution:
extension Optional: RawRepresentable where Wrapped == UserModel {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode(UserModel.self, from: data)
else {
return nil
}
self = result
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return "[]"
}
return result
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: UserModel.CodingKeys.self)
try container.encode(self?.id, forKey: .id)
try container.encode(self?.name, forKey: .name)
try container.encode(self?.email, forKey: .email)
}
}
Note: I reused the implementation you already had for UserModel: RawRepresentable - it might need some corrections for this case.
Also because you conform Optional: RawRepresentable you need to make
UserModel public as well.
A possible generic approach for any optional Codable:
extension Optional: RawRepresentable where Wrapped: Codable {
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let json = String(data: data, encoding: .utf8)
else {
return "{}"
}
return json
}
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let value = try? JSONDecoder().decode(Self.self, from: data)
else {
return nil
}
self = value
}
}
With that in place, any Codable can now be persisted in app storage:
#AppStorage("user_model") var user: UserModel? = nil

Use encoding function only in specific cases

I have struct that conforms to the Codable-protocol. I implemented a custom encode-func so that I have control over the encoding process. But I don't need this custom encoding in all cases, sometimes I want to rely on the encoding of Foundation itself. Is it possible to tell the JSONEncoder what kind of encoding I want (like creating an encoding strategy)?
This is a simplified version of my code:
struct User: Codable {
var name: String
var age: Int
var phone: PhoneNumber
struct PhoneNumber: Codable {
var countryCode: Int
var number: Int
enum CodingKeys: CodingKey {
case countryCode, number
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
// I would like to have control over the next line, so that either
// the countryCode is encoded or not, like:
// if conditon {
try container.encode(self.countryCode, forKey: .countryCode)
// }
try container.encode(self.number, forKey: .number)
}
}
}
let user = User(name: "John", age: 12, phone: User.PhoneNumber(countryCode: 49, number: 1234567))
let jsonData = try! JSONEncoder().encode(user)
print(String(data: jsonData, encoding: .utf8)!)
UPDATE
To answer one of the comments: It's not really about including or excluding one property, it's more about changing the type or content of a property:
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
// if conditon {
try container.encode("\(self.countryCode)".data(using: .utf8), forKey: .countryCode)
// }
try container.encode(self.number, forKey: .number)
}

Swift - Decode/encode an array of generics with different types

How can I decode/encode an array of different generic types?
I have a data structure, which has properties, that conform to a protocol Connection, thus I use generics:
// Data structure which saves two objects, which conform to the Connection protocol
struct Configuration<F: Connection, T: Connection>: Codable {
var from: F
var to: T
private var id: String = UUID.init().uuidString
enum CodingKeys: String, CodingKey {
case from, to, id
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.from = try container.decode(F.self, forKey: .from)
self.to = try container.decode(T.self, forKey: .to)
self.id = try container.decode(String.self, forKey: .id)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(from, forKey: .from)
try container.encode(to, forKey: .to)
try container.encode(id, forKey: .id)
}
}
protocol Connection: Codable {
var path: String { get set }
}
// Two implementations of the Connection protocol
struct SFTPConnection: Connection, Codable {
var path: String
var user: String
var sshKey: String
}
struct FTPConnection: Connection, Codable {
var path: String
var user: String
var password: String
}
This works fine when I know of what type the connections F and T are. But I have cases, where I want to load a configuration, not knowing which type F and T are.
public static func load<F: Connection, T: Connection>(for key: String) throws -> Configuration<F, T>? {
// Load from UserDefaults
guard let configurationData = defaults.object(forKey: key) as? Data else {
return nil
}
// Decode
guard let configuration = try? PropertyListDecoder().decode(Configuration<F, T>.self, from: configurationData) else {
return nil
}
return configuration
}
// OR
func loadAll<F:Connection, T: Connection>() -> [String: Configuration<F, T>]? {
return UserDefaults.standard.dictionaryRepresentation() as? [String: Configuration<F, T>]
}
In the above cases F and T could be of any unknown type, that conforms to the Connection protocol. So the above functions wouldn't work, since I would need to specify a specific type for F and T when calling the function, which I don't know.
In the second function, F alone could actually be of different types. That's where it gets difficult. I figured I need to somehow store the types of F and T in the User Defaults as well and then use them in the decode and encode function (thus discarding the generics). But I have no idea how I would elegantly do that.
I would appreciate any ideas on how to solve this problem!
The following solutions resolves all the issues, that I had with generics and not knowing the specific type of Connection. The key to the solution was
saving the type of a Connection implementation in the implementation itself and
Using superEncoder and superDecoder to encode/decode the from and to properties.
This is the solution:
import Foundation
protocol Connection: Codable {
var type: ConnectionType { get }
var path: String { get set }
}
struct LocalConnection: Connection {
let type: ConnectionType = ConnectionType.local
var path: String
}
struct SFTPConnection : Connection {
let type: ConnectionType = ConnectionType.sftp
var path: String
var user: String
var sshKey: String
init(path: String, user: String, sshKey: String) {
self.path = path
self.user = user
self.sshKey = sshKey
}
}
struct FTPConnection: Connection {
let type: ConnectionType = ConnectionType.ftp
var path: String
var user: String
var password: String
}
struct TFTPConnection: Connection {
let type: ConnectionType = ConnectionType.tftp
var path: String
}
enum ConnectionType : Int, Codable {
case local
case sftp
case ftp
case tftp
func getType() -> Connection.Type {
switch self {
case .local: return LocalConnection.self
case .sftp: return SFTPConnection.self
case .ftp: return FTPConnection.self
case .tftp: return TFTPConnection.self
}
}
}
struct Configuration {
var from : Connection
var to : Connection
private var id = UUID.init().uuidString
var fromType : ConnectionType { return from.type }
var toType : ConnectionType { return to.type }
init(from: Connection, to: Connection) {
self.from = from
self.to = to
}
}
extension Configuration : Codable {
enum CodingKeys: String, CodingKey {
case id, from, to, fromType, toType
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
var type : ConnectionType
type = try container.decode(ConnectionType.self, forKey: .fromType)
let fromDecoder = try container.superDecoder(forKey: .from)
self.from = try type.getType().init(from: fromDecoder)
type = try container.decode(ConnectionType.self, forKey: .toType)
let toDecoder = try container.superDecoder(forKey: .to)
self.to = try type.getType().init(from: toDecoder)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: .id)
try container.encode(self.fromType, forKey: .fromType)
let fromContainer = container.superEncoder(forKey: .from)
try from.encode(to: fromContainer)
try container.encode(self.toType, forKey: .toType)
let toContainer = container.superEncoder(forKey: .to)
try to.encode(to: toContainer)
}
}

How to implement Codable in a custom subclass (Swift 4) [duplicate]

Should the use of class inheritance break the Decodability of class. For example, the following code
class Server : Codable {
var id : Int?
}
class Development : Server {
var name : String?
var userId : Int?
}
var json = "{\"id\" : 1,\"name\" : \"Large Building Development\"}"
let jsonDecoder = JSONDecoder()
let item = try jsonDecoder.decode(Development.self, from:json.data(using: .utf8)!) as Development
print(item.id ?? "id is nil")
print(item.name ?? "name is nil") here
output is:
1
name is nil
Now if I reverse this, name decodes but id does not.
class Server {
var id : Int?
}
class Development : Server, Codable {
var name : String?
var userId : Int?
}
var json = "{\"id\" : 1,\"name\" : \"Large Building Development\"}"
let jsonDecoder = JSONDecoder()
let item = try jsonDecoder.decode(Development.self, from:json.data(using: .utf8)!) as Development
print(item.id ?? "id is nil")
print(item.name ?? "name is nil")
output is:
id is nil
Large Building Development
And you can't express Codable in both classes.
I believe in the case of inheritance you must implement Coding yourself. That is, you must specify CodingKeys and implement init(from:) and encode(to:) in both superclass and subclass. Per the WWDC video (around 49:28, pictured below), you must call super with the super encoder/decoder.
required init(from decoder: Decoder) throws {
// Get our container for this subclass' coding keys
let container = try decoder.container(keyedBy: CodingKeys.self)
myVar = try container.decode(MyType.self, forKey: .myVar)
// otherVar = ...
// Get superDecoder for superclass and call super.init(from:) with it
let superDecoder = try container.superDecoder()
try super.init(from: superDecoder)
}
The video seems to stop short of showing the encoding side (but it's container.superEncoder() for the encode(to:) side) but it works in much the same way in your encode(to:) implementation. I can confirm this works in this simple case (see playground code below).
I'm still struggling with some odd behavior myself with a much more complex model I'm converting from NSCoding, which has lots of newly-nested types (including struct and enum) that's exhibiting this unexpected nil behavior and "shouldn't be". Just be aware there may be edge cases that involve nested types.
Edit: Nested types seem to work fine in my test playground; I now suspect something wrong with self-referencing classes (think children of tree nodes) with a collection of itself that also contains instances of that class' various subclasses. A test of a simple self-referencing class decodes fine (that is, no subclasses) so I'm now focusing my efforts on why the subclasses case fails.
Update June 25 '17: I ended up filing a bug with Apple about this. rdar://32911973 - Unfortunately an encode/decode cycle of an array of Superclass that contains Subclass: Superclass elements will result in all elements in the array being decoded as Superclass (the subclass' init(from:) is never called, resulting in data loss or worse).
//: Fully-Implemented Inheritance
class FullSuper: Codable {
var id: UUID?
init() {}
private enum CodingKeys: String, CodingKey { case id }
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(UUID.self, forKey: .id)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
}
}
class FullSub: FullSuper {
var string: String?
private enum CodingKeys: String, CodingKey { case string }
override init() { super.init() }
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let superdecoder = try container.superDecoder()
try super.init(from: superdecoder)
string = try container.decode(String.self, forKey: .string)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(string, forKey: .string)
let superencoder = container.superEncoder()
try super.encode(to: superencoder)
}
}
let fullSub = FullSub()
fullSub.id = UUID()
fullSub.string = "FullSub"
let fullEncoder = PropertyListEncoder()
let fullData = try fullEncoder.encode(fullSub)
let fullDecoder = PropertyListDecoder()
let fullSubDecoded: FullSub = try fullDecoder.decode(FullSub.self, from: fullData)
Both the super- and subclass properties are restored in fullSubDecoded.
Found This Link - Go down to inheritance section
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(employeeID, forKey: .employeeID)
}
For Decoding I did this:
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
let values = try decoder.container(keyedBy: CodingKeys.self)
total = try values.decode(Int.self, forKey: .total)
}
private enum CodingKeys: String, CodingKey
{
case total
}
🚀 Swift introduced Property Wrappers in 5.1 I implemented a library called SerializedSwift that uses the power of property wrappers to Decode and Encode JSON data to objects.
One of my main goals was, to make inherited object to decode out of the box, without additonal init(from decoder: Decoder) overrides.
import SerializedSwift
class User: Serializable {
#Serialized
var name: String
#Serialized("globalId")
var id: String?
#Serialized(alternateKey: "mobileNumber")
var phoneNumber: String?
#Serialized(default: 0)
var score: Int
required init() {}
}
// Inherited object
class PowerUser: User {
#Serialized
var powerName: String?
#Serialized(default: 0)
var credit: Int
}
It also supports custom coding keys, alternate keys, default values, custom transformation classes and many more features to be included in the future.
Available on GitHub (SerializedSwift).
I was able to make it work by making my base class and subclasses conform to Decodable instead of Codable. If I used Codable it would crash in odd ways, such as getting a EXC_BAD_ACCESS when accessing a field of the subclass, yet the debugger could display all the subclass values with no problem.
Additionally, passing the superDecoder to the base class in super.init() didn't work. I just passed the decoder from the subclass to the base class.
How about using the following way?
protocol Parent: Codable {
var inheritedProp: Int? {get set}
}
struct Child: Parent {
var inheritedProp: Int?
var title: String?
enum CodingKeys: String, CodingKey {
case inheritedProp = "inherited_prop"
case title = "short_title"
}
}
Additional info on composition: http://mikebuss.com/2016/01/10/interfaces-vs-inheritance/
Here is a library TypePreservingCodingAdapter to do just that (can be installed with Cocoapods or SwiftPackageManager).
The code below compiles and works just fine with Swift 4.2. Unfortunately for every subclass you'll need to implement encoding and decoding of properties on your own.
import TypePreservingCodingAdapter
import Foundation
// redeclared your types with initializers
class Server: Codable {
var id: Int?
init(id: Int?) {
self.id = id
}
}
class Development: Server {
var name: String?
var userId: Int?
private enum CodingKeys: String, CodingKey {
case name
case userId
}
init(id: Int?, name: String?, userId: Int?) {
self.name = name
self.userId = userId
super.init(id: id)
}
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decodeIfPresent(String.self, forKey: .name)
userId = try container.decodeIfPresent(Int.self, forKey: .userId)
}
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(userId, forKey: .userId)
}
}
// create and adapter
let adapter = TypePreservingCodingAdapter()
let encoder = JSONEncoder()
let decoder = JSONDecoder()
// inject it into encoder and decoder
encoder.userInfo[.typePreservingAdapter] = adapter
decoder.userInfo[.typePreservingAdapter] = adapter
// register your types with adapter
adapter.register(type: Server.self).register(type: Development.self)
let server = Server(id: 1)
let development = Development(id: 2, name: "dev", userId: 42)
let servers: [Server] = [server, development]
// wrap specific object with Wrap helper object
let data = try! encoder.encode(servers.map { Wrap(wrapped: $0) })
// decode object back and unwrap them force casting to a common ancestor type
let decodedServers = try! decoder.decode([Wrap].self, from: data).map { $0.wrapped as! Server }
// check that decoded object are of correct types
print(decodedServers.first is Server) // prints true
print(decodedServers.last is Development) // prints true
Swift 5
The compiler synthesises decodable code only for a type that directly adopts Codable protocol so that you observe decoding for a single of your type in inheritance.
But you can try next generic approach with KeyValueCoding package (https://github.com/ikhvorost/KeyValueCoding) and this package provides access to all properties metadata and allows to get/set any property for pure swift types dynamically. The idea is to make a base Coding class which adopts KeyValueCoding and implements decoding of all available properties in init(from: Decoder):
class Coding: KeyValueCoding, Decodable {
typealias DecodeFunc = (KeyedDecodingContainer<_CodingKey>, _CodingKey) throws -> Any?
struct _CodingKey: CodingKey {
let stringValue: String
let intValue: Int?
init(stringValue: String) {
self.stringValue = stringValue
self.intValue = Int(stringValue)
}
init(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
}
static func decodeType<T: Decodable>(_: T.Type) -> (type: T.Type, f: DecodeFunc) {
(T.self, { try $0.decode(T.self, forKey: $1) })
}
static var decodeTypes: [(Any.Type, DecodeFunc)] = [
decodeType(Int.self),
decodeType(Int?.self),
decodeType(String.self),
decodeType(String?.self),
// Other types to support...
]
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: _CodingKey.self)
try container.allKeys.forEach { codingKey in
let key = codingKey.stringValue
guard let property = (properties.first { $0.name == key }),
let item = (Self.decodeTypes.first { property.type == $0.0 })
else {
return
}
var this = self
this[key] = try item.1(container, codingKey)
}
}
}
It is important to provide all supported types to decode in decodeTypes variable.
How to use:
class Server: Coding {
var id: Int?
}
class Development : Server {
var name: String = ""
}
class User: Development {
var userId: Int = 0
}
func decode() {
let json = "{\"id\": 1, \"name\": \"Large Building Development\", \"userId\": 123}"
do {
let user = try JSONDecoder().decode(User.self, from:json.data(using: .utf8)!)
print(user.id, user.name, user.userId) // Optional(1) Large Building Development 123
}
catch {
print(error.localizedDescription)
}
}