I have a custom model in swift and it has a property of type DocumentReference
I'm getting some data from a cloud function and then I'm trying to decode it back to my model.
DocumentReference doesn't conform to Decodable by itself so I'm trying to write an extension for it.
I can not make it work since I keep getting this error:
Initializer requirement 'init(from:)' can only be satisfied by a 'required' initializer in the definition of non-final class 'DocumentReference'
Any idea how to make this work?
My extension:
import Firebase
extension DocumentReference: Decodable {
public convenience init(from decoder: Decoder) throws {
//
}
}
Existing model:
struct Notification: Identifiable, CollectionProtocol, DocumentProtocol {
var id = UUID()
var documentReference: DocumentReference
var receiverID: String
var type: String
var createdAt: Date
var requestReference: DocumentReference?
var request: Request?
init(document: DocumentSnapshot) {
self.documentReference = document.reference
self.requestReference = document["requestReference"] as? DocumentReference ?? Firestore.firestore().document("")
self.receiverID = document["receiverID"] as? String ?? ""
self.type = document["type"] as? String ?? ""
self.createdAt = (document["createdAt"] as? Timestamp)?.dateValue() ?? Date()
}
}
NS_SWIFT_NAME(DocumentReference)
#interface FIRDocumentReference : NSObject
/** :nodoc: */
- (instancetype)init
__attribute__((unavailable("FIRDocumentReference cannot be created directly.")));
DocumentReference (FIRDocumentReference) cannot be instantiated directly; it has no available initializers. To implement the required init, you'd have to do that in the declaration of the class itself.
If you want to conveniently instantiate custom objects from Firestore data, consider a failable initializer that takes a [String: Any] argument. If you just want to encode/decode the reference itself, consider encoding/decoding the String value of the location itself (the collection and document names) and then using that to reconstruct the DocumentReference.
The class DocumentReference is not final which means that it could possibly be sub-classed but without the sub class conforming to Codable. So this would leave to an unknown state if init(from decoder: Decoder) in your extension was called.
One workaround could be to create a wrapper struct with its own init(from decoder: Decoder) and then do your decoding of DocumentReference there.
Example:
struct Wrapped: Decodable {
let docRef: DocumentReference
public init(from decoder: Decoder) throws {
let container = try decoder....
// add the decoding code here for DocumentReference
}
}
Related
I am trying to write a generic function which accepts Generic T.Type as function argument, but it is not accepting T.Type as array
public protocol Mappable {
}
class User: Mappable {
var name: String?
}
class MyClass {
func request<T: Mappable>(responseType: T.Type) -> T {
let objT = .....
return objT
}
}
let myCls = MyClass()
myCls.request(responseType: User.self) // Works as expected
myCls.request(responseType: [User].self) // Error: Instance method 'request(responseType:)' requires that '[User]' conform to 'Mappable'
Experiment:
If I replace Mappable with Decodable in above code, it works fine. but I want to use my Protocol and not Decodable
I want to achieve similar to
struct Car: Decodable {
var name: String?
}
let data = "{\"name\": \"Lamborghini\"}".data(using: .utf8)
let dataArray = "[{\"name\": \"Lamborghini\"}, {\"name\": \"Ferrari\"}]".data(using: .utf8)
let car = try! JSONDecoder().decode(Car.self, from: data!) // Will return single object of Car
let cars = try! JSONDecoder().decode([Car].self, from: dataArray!) // Will return Array of Car
I am not able to figure out why it works with Decodable and not with my Mappable Protocol
it's because the array is decodable if it's element type is decodable.
extension Array: Decodable where Element: Decodable {
/// Creates a new array by decoding from the given decoder.
///
/// This initializer throws an error if reading from the decoder fails, or
/// if the data read is corrupted or otherwise invalid.
///
/// - Parameter decoder: The decoder to read data from.
public init(from decoder: Decoder) throws {
self.init()
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
let element = try container.decode(Element.self)
self.append(element)
}
}
}
you can refer the implementation from https://github.com/apple/swift/blob/master/stdlib/public/core/Codable.swift
I think your function call myCls.request(responseType: [User].self) is failed because T is [User], it's not a Mappable.
I'm working on JSON encode and decode, but some problems are very annoying and I don't know how to use CodingKeys in inherit classes.
I have two classes ResponseBean and ResponseWithObjectBean<T>.
Here is the response class definition:
public class ResponseBean: Codable
{
//This is only sample, I define `CodingKeys` because the property in json is in different name.
private enum CodingKeys: String, CodingKey
{
case intA
case intB
}
public var intA: Int32 = 0
public var intB: Int32 = 0
}
public class ResponseWithObjectBean<T: Codable> : ResponseBean
{
/*
Here I don't know how to define an enum to confirm protocl CondingKey.
I defined an enum named CodingKeys or whatever, they just don't work and
the testMessage and obj are still nil.
But if I implement the init(from decoder: Decoder) construction and manually
pass the coding keys which I defined to the decode function, all works fine.
*/
public var testMessage: String? = nil
public var obj: T? = nil
}
and I will get a user from the response:
public class User: Codable
{
private enum CodingKeys: String, CodingKey
{
case name
case age
}
public var name: String? = nil
public var age: Int32? = nil
}
Here is the test json:
var testJson = """
{
"intA": 10,
"intB": 20,
"testMessage": "This is a test json",
"obj":{
"name": "LiHong",
"age": 11
}
}
"""
The following is how I run:
do{
var responseData = testJson.data(using: .utf8)
var decoder = JSONDecoder()
var response: ResponseWithObjectBean<User> = try decoder.decode(ResponseWithObjectBean<User>.self, from: responseData)
}catch let e{
}
I don't know how to define CodingKeys in ResponseWithObjectBean class, and even I did, it dosen't work at all. But if I implement init(from decoder: Decoder) throws construction and manully pass coding keys I defined in ResponseWithObjectBean, I can get all properties.
This is pretty simple, you just have to do the coding and decoding by hand, in the child class:
public class ResponseWithObjectBean<T: Codable> : ResponseBean {
public var testMessage: String? = nil
public var obj: T? = nil
// Create another CodingKey compliant enum with another name for the new keys
private enum CustomCodingKeys: String, CodingKey {
case testMessage
case obj
}
// Override the decoder
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
let container = try decoder.container(keyedBy: CustomCodingKeys.self)
testMessage = try container.decode(String?.self, forKey: .testMessage)
obj = try container.decode(T?.self, forKey: .obj)
}
// And the coder
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CustomCodingKeys.self)
try container.encode(testMessage, forKey: .testMessage)
try container.encode(obj, forKey: .obj)
}
}
This way you can decode and encode the way you want:
let decoder = JSONDecoder()
let response = try decoder.decode(ResponseWithObjectBean<User>.self, from: responseData)
let data = try JSONEncoder().encode(response)
print(String(data: data, encoding: .utf8))
EDIT: In order to prevent you from writing manually all this boilerplate, you can use generation tools like Sourcery: https://github.com/krzysztofzablocki/Sourcery
I am working on mix and match iOS source code. I have implemented codable for swift data model class which reduces the burden of writing parser logic. I tried to conform my objective c class to codable protcol which in turn thrown an error "Cannot find protocol declaration for 'Codable'". Is there any way to use this swift protocol into objective c class? Or Is there any other objective c api that provides the same capability as Codable? The idea is to make the parsing logic same across swift and objective c classes.
Yes, you can use Codable together with Obj-C. The tricky part is that because Obj-C can't see Decoder, so you will need to create a helper class method when you need it to be allocated from Obj-C side.
public class MyCodableItem: NSObject, Codable {
private let id: String
private let label: String?
enum CodingKeys: String, CodingKey {
case id
case label
}
#objc public class func create(from url: URL) -> MyCodableItem {
let decoder = JSONDecoder()
let item = try! decoder.decode(MyCodableItem.self, from: try! Data(contentsOf: url))
return item
}
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
label = try? container.decode(String.self, forKey: .label)
super.init()
}
required init(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class LoginDataModel: NSObject ,Codable {
#objc var stepCompleted: String?
#objc var userID: Int?
#objc var authkey: String?
#objc var type, status: String?
enum CodingKeys: String, CodingKey {
case stepCompleted = "step_completed"
case userID = "user_id"
case authkey
case type, status
}
}
I am fetching user's data as json from a service and decoding into Codable User struct. I can access that property where I've fetched the response but I want to access that User struct property somewhere else, let's say in another class, function, etc.
I'm new to this and I'm thinking the ideal approach is to "at the time of fetching, store that data into Core Data or User Defaults and then update my views accordingly.
Please suggest what's the best and appropriate approach or wether there is any way to access codable struct values directly.
Here is my codable struct -
struct User: Codable {
let name : String?
enum CodingKeys: String, CodingKey {
case name = "Name"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decodeIfPresent(String.self, forKey: .name)
}
}
The wrong way I'm accessing struct in some function is -
UserInfo.CodingKeys.name.rawValue
//Output is the key string - 'Name'
I think static can help you
struct User: Codable {
let name : String?
private enum CodingKeys: String, CodingKey {
case name = "Name"
}
}
assume fetching data here
class FetchingClass{
static var user: User?
func fetchData(){
//After Url request success
do {
//assign directly to static varibale user
FetchingClass.user = try JSONDecoder().decode(User.self, from: data)
} catch _ {
}
}
}
use like this wherever you want without using coreData or UserDefaults
class AnotherClass{
func anotherClassFunc(){
//use if let or guard let
if let user = FetchingClass.user{
print(user.name)
}
//or
if let FetchingClass.user != nil {
print(FetchingClass.name)
}
}
}
You can try using a singleton reference
struct User: Codable {
let name : String?
enum CodingKeys: String, CodingKey {
case name = "Name"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decodeIfPresent(String.self, forKey: .name)
User.shared = self
}
}
Create another class which hold the ref. of User
final class AppGlobalManager {
static let sharedInstance = AppGlobalManager()
var currentUser: User? // Initialise it when user logged in
private init()
}
Then you can access any of the user data as
AppGlobalManager.sharedInstance.currentUser.name
I have a Codable class with a variable that holds a dictionary with String keys and values that may be Strings, Ints or custom structs conforming to Codable. My question is:
How do I define a dictionary with values that are Codable?
I hoped it would be enough to say
var data: [String: Codable] = [:]
but apparently that makes my class no longer conform to Codable. I think the problem here is the same as I had here, where I am passing a protocol rather than an object constrained by the protocol
Using JSON Encoder to encode a variable with Codable as type
So presumably I would need a constrained generic type, something like AnyObject<Codable> but that's not possible.
EDIT: Answer
So since this can't be done as per the accepted answer, I am resorting to a struct with dictionaries for each data type
struct CodableData {
var ints: [String: Int]
var strings: [String: String]
//...
init() {
ints = [:]
strings = [:]
}
}
var data = CodableData()
A Swift collection contains just one type, and for it to be Codable, that type would need to be Codable — not Codable the type, but an adopter of the Codable protocol. You need the type here to be some one specific Codable adopter.
Therefore the only way to do what you're describing would be basically to invent some new type, StringOrIntOrStruct, that wraps a String or an Int or a struct and itself adopts Codable.
But I don't think you're doing to do that, as the effort seems hardly worth it. Basically, you should stop trying to use a heterogeneous Swift collection in the first place; it's completely against the spirit of the language. It's a Bad Smell from the get-go. Rethink your goals.
I think the compiler is simply not smart enough to infer the Codable implementation automatically. So one possible solution is to implement Codable by hand:
class Foo: Codable {
var data: [String:Codable] = [:]
func encode(to encoder: Encoder) throws {
// ...
}
required init(from decoder: Decoder) throws {
// ...
}
}
I don’t know whether you can change the data type to help the compiler do the work for you. At least you can pull the hand-coding to the problematic type:
enum Option: Codable {
case number(Int)
case string(String)
func encode(to encoder: Encoder) throws {
// ...
}
init(from decoder: Decoder) throws {
if let num = try? Int(from: decoder) {
self = .number(num)
} else if let str = try? String(from: decoder) {
self = .string(str)
} else {
throw something
}
}
}
class Foo: Codable {
var data: [String:Option] = [:]
}
Have any of you tried Generics in Swift?
Look:
public struct Response<T> where T : Codable {
let ContentEncoding : String?
let ContentType : String?
let JsonRequestBehavior : Int?
let MaxJsonLength : Int?
let RecursionLimit : Int?
let data : Data?
public struct Data : Codable {
let response : String?
let status : String?
let details : [T]?
}
}