Im trying to save custom data to cloud firestore in swiftUI, Im trying to construct the data i get from snapshot listener - swift

The call document.data(as: ) requires Decodable protocol, it saves the data just fine as the call to addDocument(from:) requires encodable object so I pass my custom class object, yet I don't know why the other way around requires the protocol.
How do I pass the protocol?
Here is my custom class definition :
class PersonalInfo: Codable, ObservableObject {
#Published var name: String
#Published var age: Int
#Published var Sex: String
enum CodingKeys: CodingKey{
case name, age, Sex
}
init(name: String, age:Int, Sex:String) {
self.name = name
self.age = age
self.Sex = Sex
}
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
age = try container.decode(Int.self, forKey: .age)
Sex = try container.decode(String.self, forKey: .Sex)
}
public func encode( to encoder: Encoder) throws{
var container = try encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(age, forKey: .age)
try container.encode(Sex, forKey: .Sex)
}
}
Here is the function I am trying to construct the data after getting set of data:
mutating func loadData(){
let listener = query?.addSnapshotListener{ (snapShot, error ) in
guard let snapshot = snapShot else {
print("Error listening")
return
}
let userCollection = try? snapshot.documents.map{ document -> PersonalInfo in
let decoder = JSONDecoder()
if let user = try document.data(as: PersonalInfo(from: decoder)){
return user
}
else{
fatalError("unable to initialize type")
}
}
}
}

There is no need to create a class for your data structure. A Class: ObservableObject accepts, processes, and passes (published) needed data(values) to your View according to your data structure...
A good news: Firebase supports the Swift Codable Protocol – check this link

Related

Swift Decodable: Inject value in nested generic property

I have this API response structure (from Strapi v4):
{
"data": [
{
"id": 1,
"attributes": {
"description": "test",
}
}
]
}
I have this generic code to handle API responses and to inject the ID to my child object:
struct StrapiArrayResponse<Content: StrapiDataObjectContent>: Codable {
var data: [StrapiDataObject<Content>]
}
struct StrapiDataObject<Content: StrapiDataObjectContent>: Codable {
let id: Int
var attributes: Content
init(from decoder: Decoder) throws {
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.attributes = try container.decode(Content.self, forKey: .attributes)
self.attributes.id = id
}
}
protocol StrapiDataObjectContent: Codable {
var id: Int! { get set } // I don't want this to be an Optional
}
I want my id to be a let instead of an optional var.
Is there a better way to inject the ID to my child objects (StrapiDataObjectContent)?
Here is a solution for the problem but it isn't so straightforward and requires some work.
Since you want id to be a constant we need a way to initialise Content with it so one way then is to add an init to the protocol.
protocol StrapiDataObjectContent: Codable {
var id: Int { get } //also removed 'set'
init(id: Int, copy: Self)
}
As you see this init takes an already existing object as parameter so this is kind of a copy method
So an implementation (based on the json in the question) could then be
init(id: Int, copy: Test) {
self.id = id
self.description = copy.description
}
We then need to change init(from:) in StrapiDataObject to
init(from decoder: Decoder) throws {
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
let attributes = try container.decode(Content.self, forKey: .attributes)
self.attributes = Content(id: id, copy: attributes)
}
Now this compiles but we will get a runtime error since id is expected by the decoder for Content but doesn't exists in the json.
So this leads to the major drawback of this solution, every type conforming to StrapiDataObjectContent needs to implement a custom init(from:) just to avoid decoding the id property
To demonstrate here is a full example (based on the json in the question)
struct Test: StrapiDataObjectContent {
let id: Int
let description: String
init(from decoder: Decoder) throws {
id = 0
let container = try decoder.container(keyedBy: CodingKeys.self)
description = try container.decode(String.self, forKey: .description)
}
init(id: Int, copy: Test) {
self.id = id
self.description = copy.description
}
}

Access property of parent struct in a nested Codable struct when decoding the child

When using a decoder in a nested Codable struct, is there any way to access a property of a parent struct?
The only way I can think of that might work (haven't tested yet) is to use a manual decoder in the parent struct too, set the property in the userInfo dictionary, and then access userInfo in the child struct. But that would result in a lot of boilerplate code. I'm hoping there's a simpler solution.
struct Item: Decodable, Identifiable {
let id: String
let title: String
let images: Images
struct Images: Decodable {
struct Image: Decodable, Identifiable {
let id: String
let width: Int
let height: Int
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
width = try container.decode(Int.self, forKey: .width)
height = try container.decode(Int.self, forKey: .height)
// How do I get `parent.parent.id` (`Item#id`) here?
id = "\(parent.parent.id)\(width)\(height)"
}
}
let original: Image
let small: Image
// …
}
}
In the above example, the item ID coming from the server is only defined in the top-level properties in the JSON, but I need them in the children too, so I can also make them Identifiable.
I managed it using Itai Ferber's suggestion as mentioned by #New Dev in the following way:
Create a new reference type whose only purpose is to contain a
mutable value that can be passed between parent and child.
Assign an instance of that type to the JSONDecoder's userInfo dictionary.
Retrieve that instance when decoding the parent and assign to it the id that you're interested in passing.
Whilst decoding the child, retrieve that id from the instance stored in the userInfo earlier.
I've modified your example above as follows:
struct Item: Decodable, Identifiable {
enum CodingKeys: String, CodingKey {
case id
case title
case images
}
let id: String
let title: String
let images: Images
struct Images: Decodable {
struct Image: Decodable, Identifiable {
let id: String
let width: Int
let height: Int
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
width = try container.decode(Int.self, forKey: .width)
height = try container.decode(Int.self, forKey: .height)
if let referenceTypeUsedOnlyToContainAChangeableIdentifier = decoder.userInfo[.referenceTypeUsedOnlyToContainAChangeableIdentifier] as? ReferenceTypeUsedOnlyToContainAChangeableIdentifier {
self.id = referenceTypeUsedOnlyToContainAChangeableIdentifier.changeableIdentifier
} else {
self.id = "something went wrong"
}
}
}
let original: Image
let small: Image
// …
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
if let referenceTypeUsedOnlyToContainAChangeableIdentifier = decoder.userInfo[.referenceTypeUsedOnlyToContainAChangeableIdentifier] as? ReferenceTypeUsedOnlyToContainAChangeableIdentifier {
referenceTypeUsedOnlyToContainAChangeableIdentifier.changeableIdentifier = id
}
}
}
}
// Use this reference type to just store an id that's retrieved later.
class ReferenceTypeUsedOnlyToContainAChangeableIdentifier {
var changeableIdentifier: String?
}
// Convenience extension.
extension CodingUserInfoKey {
static let referenceTypeUsedOnlyToContainAChangeableIdentifier = CodingUserInfoKey(rawValue: "\(ReferenceTypeUsedOnlyToContainAChangeableIdentifier.self)")!
}
let decoder = JSONDecoder()
// Assign the reference type here to be used later during the decoding process first to assign the id in `Item` and then
// later to retrieve that value in `Images`
decoder.userInfo[.referenceTypeUsedOnlyToContainAChangeableIdentifier] = ReferenceTypeUsedOnlyToContainAChangeableIdentifier()

How to encode/decode a dictionary with Codable values for storage in UserDefaults?

I am trying to store a dictionary of company names (string) mapped to Company objects (from a struct Company) in iOS UserDefaults. I have created the Company struct and made it conform to Codable. I have one example a friend helped me with in my project where we created a class Account and stored it in UserDefaults by making a Defaults struct (will include example code). I have read in the swift docs that dictionaries conform to Codable and in order to stay Codable, must contain Codable objects. That is why I made struct Company conform to Codable.
I have created a struct for Company that conforms to Codable. I have tried using model code to create a new struct CompanyDefaults to handle the getting and setting of the Company dictionary from/to UserDefaults. I feel I have some beginner misconceptions about what needs to happen and about how it should be implemented (with good design in mind).
The dictionary I wish to store looks like [String:Company]
where company name will be String and a Company object for Company
I used conform to Codable as I did some research and it seemed like a newer method for completing similar tasks.
Company struct
struct Company: Codable {
var name:String?
var initials:String? = nil
var logoURL:URL? = nil
var brandColor:String? = nil // Change to UIColor
enum CodingKeys:String, CodingKey {
case name = "name"
case initials = "initials"
case logoURL = "logoURL"
case brandColor = "brandColor"
}
init(name:String?, initials:String?, logoURL:URL?, brandColor:String?) {
self.name = name
self.initials = initials
self.logoURL = logoURL
self.brandColor = brandColor
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
initials = try values.decode(String.self, forKey: .initials)
logoURL = try values.decode(URL.self, forKey: .logoURL)
brandColor = try values.decode(String.self, forKey: .brandColor)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(initials, forKey: .initials)
try container.encode(logoURL, forKey: .logoURL)
try container.encode(brandColor, forKey: .brandColor)
}
}
Defaults struct to control storage
struct CompanyDefaults {
static private let companiesKey = "companiesKey"
static var companies: [String:Company] = {
guard let data = UserDefaults.standard.data(forKey: companiesKey) else { return [:] }
let companies = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [String : Company] ?? [:]
return companies!
}() {
didSet {
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: companies, requiringSecureCoding: false) else {
return
}
UserDefaults.standard.set(data, forKey: companiesKey)
}
}
}
I should be able to reference the stored dictionary throughout my code like CompanyDefaults.companies.count
For reference, a friend helped me do a similar task for an array of Account classes stored in user defaults. The code that works perfectly for that is below. The reason I tried a different way is that I had a different data structure (dictionary) and made the decision to use structs.
class Account: NSObject, NSCoding {
let service: String
var username: String
var password: String
func encode(with aCoder: NSCoder) {
aCoder.encode(service)
aCoder.encode(username)
aCoder.encode(password)
}
required init?(coder aDecoder: NSCoder) {
guard let service = aDecoder.decodeObject() as? String,
var username = aDecoder.decodeObject() as? String,
var password = aDecoder.decodeObject() as? String else {
return nil
}
self.service = service
self.username = username
self.password = password
}
init(service: String, username: String, password: String) {
self.service = service
self.username = username
self.password = password
}
}
struct Defaults {
static private let accountsKey = "accountsKey"
static var accounts: [Account] = {
guard let data = UserDefaults.standard.data(forKey: accountsKey) else { return [] }
let accounts = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Account] ?? []
return accounts
}() {
didSet {
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: accounts, requiringSecureCoding: false) else {
return
}
UserDefaults.standard.set(data, forKey: accountsKey)
}
}
}
You are mixing up NSCoding and Codable. The former requires a subclass of NSObject, the latter can encode the structs and classes directly with JSONEncoder or ProperListEncoder without any Keyedarchiver which also belongs to NSCoding.
Your struct can be reduced to
struct Company: Codable {
var name : String
var initials : String
var logoURL : URL?
var brandColor : String?
}
That's all, the CodingKeys and the other methods are synthesized. I would at least declare name and initials as non-optional.
To read and save the data is pretty straightforward. The corresponding CompanyDefaults struct is
struct CompanyDefaults {
static private let companiesKey = "companiesKey"
static var companies: [String:Company] = {
guard let data = UserDefaults.standard.data(forKey: companiesKey) else { return [:] }
return try? JSONDecoder.decode([String:Company].self, from: data) ?? [:]
}() {
didSet {
guard let data = try? JSONEncoder().encode(companies) else { return }
UserDefaults.standard.set(data, forKey: companiesKey)
}
}
}

How to save custom type in CoreData with Codable Protocol Swift

I am trying to save the custom object of type codable, In which I am able to store Int16 type. But for [Movie] type in Coredata its NSObject, Entity I have an attribute movie is of type Transformable.
Error: No 'decodeIfPresent' candidates produce the expected contextual
result type 'NSObject?'
How can save this custom type Array with Transformable type
class MovieResults: Results, Codable {
required convenience public init(from decoder: Decoder) throws {
guard let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.context,
let managedObjectContext = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext,
let entity = NSEntityDescription.entity(forEntityName: "Results", in: managedObjectContext) else {
fatalError("Failed to retrieve managed object context")
}
self.init(entity: entity, insertInto: managedObjectContext)
let container = try decoder.container(keyedBy: CodingKeys.self)
self.page = try container.decodeIfPresent(Int16.self, forKey: .page) ?? 0
self.numberOfResults = try container.decodeIfPresent(Int16.self, forKey: .numberOfResults) ?? 0
self.numberOfPages = try container.decodeIfPresent(Int16.self, forKey: .numberOfPages) ?? 0
self.movies = try container.decodeIfPresent([Movie].self, forKey: .movies) ?? nil
}
// MARK: - Encodable
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(page, forKey: .page)
try container.encode(numberOfResults, forKey: .numberOfResults)
try container.encode(numberOfPages, forKey: .numberOfPages)
try container.encode(movies, forKey: .movies)
}
private enum CodingKeys: String, CodingKey {
case page
case numberOfResults = "total_results"
case numberOfPages = "total_pages"
case movies = "results"
}
}
Movie Array is an custom attribute of type Transformable in CoreData
class Movies: Movie, Codable {
public func encode(to encoder: Encoder) throws {
}
required convenience init(from decoder: Decoder) throws {
guard let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.context,
let managedObjectContext = decoder.userInfo[codingUserInfoKeyManagedObjectContext] as? NSManagedObjectContext,
let entity = NSEntityDescription.entity(forEntityName: "Movie", in: managedObjectContext) else {
fatalError("Failed to decode User")
}
self.init(entity: entity, insertInto: managedObjectContext)
let container = try decoder.container(keyedBy: CodingKeys.self)
self.identifier = try container.decodeIfPresent(Int16.self, forKey: .identifier) ?? 0
self.posterPath = try container.decodeIfPresent(String.self, forKey: .identifier)
self.backdrop = try container.decodeIfPresent(String.self, forKey: .identifier)
self.title = try container.decodeIfPresent(String.self, forKey: .identifier)
self.releaseDate = try container.decodeIfPresent(String.self, forKey: .identifier)
self.rating = try container.decodeIfPresent(Int16.self, forKey: .rating) ?? 0
self.overview = try container.decodeIfPresent(String.self, forKey: .identifier)
}
enum CodingKeys: String, CodingKey {
case identifier
case posterPath = "poster_path"
case backdrop = "backdrop_path"
case title
case releaseDate = "release_date"
case rating = "vote_average"
case overview
}
}
With this, it's working fine.
self.movies = try container.decodeIfPresent([Movies].self, forKey: .movies)! as NSObject
I am Inheriting NSManagedObject Class Is this Correct way. I tried
using the extension but it throws an error for initializers.?
public convenience init(from decoder: Decoder) throws
Initializer requirement 'init(from:)' can only be satisfied by a
'required' initializer in the definition of non-final class
'MovieResult'
I think this could be done in a more simpler way. Here is how you can give a try. Here is MovieResult class [It's good to have the name of the class in singular].
public class MovieResult: Codable {
public var stringVariable: String
public var intVariable: Int
public init(stringVariable: String, intVariable: Int) {
self.stringVariable = stringVariable
self.intVariable = intVariable
}
}
Here you can persist MovieResult in UserDefaults.
public var savedMovieResult: MovieResult? {
get {
let data = UserDefaults.standard.object(forKey: "savedMovieResultKey") as? Data
var movieResult: MovieResult?
if let data = data {
do {
movieResult = try PropertyListDecoder().decode(MovieResult.self, from: data)
} catch {
print(error)
}
}
return movieResult
} set {
do {
try UserDefaults.standard.set(PropertyListEncoder().encode(newValue), forKey: "savedMovieResultKey")
UserDefaults.standard.synchronize()
} catch {
print(error)
}
}
}

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)
}
}