I know the basic concept of class and struct but which is more effective to create models for API to fetch data and tell me with pros and cons.
Previously i don't use optional for models. Instead i give it some value. ie
class CompanyInfo : Codable {
var NameEn : String = ""
var CityEn : String = ""
var Website : String = ""
var Email : String = ""
var Phone : String = ""
var Fax : String = ""
}
but when it get some null value from API. ie "Fax": null then App get crashed because it can't parse data with following line
let data = try JSONDecoder().decode(dataModel.self, from: dataSet)
what is the best way to deffine a model so i don't need to unwrap optional or give it default value.
You can implement a custom decoder with default values:
class CompanyInfo : Codable {
var NameEn: String
var CityEn: String
var Website: String
var Email: String
var Phone: String
var Fax: String
required init(from decoder: Decoder) throws {
do {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.NameEn = try container.decodeIfPresent(String.self, forKey: .NameEn) ?? "Default"
self.CityEn = try container.decodeIfPresent(String.self, forKey: .CityEn) ?? "Default"
self.Website = try container.decodeIfPresent(String.self, forKey: .Website) ?? "Default"
self.Email = try container.decodeIfPresent(String.self, forKey: .Email) ?? "Default"
self.Phone = try container.decodeIfPresent(String.self, forKey: .Phone) ?? "Default"
self.Fax = try container.decodeIfPresent(String.self, forKey: .Fax) ?? "Default"
}
}
}
Unrelated to question, but important Note:
In Swift, only Types names should start with a capital letter. If you continue naming variables like this, you will have a serious refactoring issue one day if you decide to use CoreData or working with other Swift developers.
Any future colleague will thank you if the data model reflects the JSON response of the API ("Don't make me think"). Furthermore, for now you don't want to have optional values - in 3 weeks you perhaps need them - then you have some ugly checks:
if companyInfo.fax == "default" {
// Hey it's the default value but this indicates that the value is optional and nil
}
However, it's doable:
https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types
By the way - also have reading on Swift naming conventions regarding property names.
https://swift.org/documentation/api-design-guidelines/
There is no such answer for more efficient use of class or struct. that's depends on your need, app's demand and it's coding structure.
If you have to deal with the optionals at runtime this can be the best approach according to me.
I would prefer to use struct on this
struct YOUR_MODEL_NAME : Codable {
var NameEn : String?
var CityEn : String?
var Website : String?
var Email : String?
var Phone : String?
var Fax : String?
enum CodingKeys: String, CodingKey {
case NameEn = "YOUR_KEY_FOR_NameEn"
case CityEn = "YOUR_KEY_FOR_CityEn"
case Website = "YOUR_KEY_FOR_Website"
case Email = "YOUR_KEY_FOR_Email"
case Phone = "YOUR_KEY_FOR_Phone"
case Fax = "YOUR_KEY_FOR_Fax"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
NameEn = try values.decodeIfPresent(String.self, forKey: .NameEn)
CityEn = try values.decodeIfPresent(String.self, forKey: .CityEn)
Website = try values.decodeIfPresent(String.self, forKey: .Website)
Email = try values.decodeIfPresent(String.self, forKey: .Email)
Phone = try values.decodeIfPresent(String.self, forKey: .Phone)
Fax = try values.decodeIfPresent(String.self, forKey: .Fax)
}
}
Related
I'm trying to filter out empty and null values from an api in a json format in swift(UIKit).
The full data returns look like below but sometimes can contain null or empty values in the characteristic key. There is always going to be the same amount of keys.
//Cat
{
"breedname": "Persian",
"picture": "https://catimage.random.png",
"characteristic1": "Shy",
"characteristic2": "Hungry all the time"
"characteristic3": "Likes apples"
"characteristic4": "Grey color"
"characteristic5": "likes chin scratches"
}
{
"breedname": "Bengal",
"picture": "https://catimage.random.png",
"characteristic1": "Active",
"characteristic2": "Adventurous"
"characteristic3": ""
"characteristic4": ""
"characteristic5": ""
}
{
"breedname": "ragdoll",
"picture": "https://catimage.random.png",
"characteristic1": "Fiestey",
"characteristic2": "sharp claws"
"characteristic3": null
"characteristic4": null
"characteristic5": null
}
In order to filter null and empty values before showing in the UI, I have a Decodable class like below and a custom init class with the decodeifPresent method which changes null values to nill. However for empty values I just created a method which converts empty string values to nill. I'm not sure if there are better ways to handle empty and null data and filtering them out? I refer to all the Decodable keys in the UI so I cannot simply delete the keys themselves.
struct Cat: Decodable {
let breedName: String
let picture: String
let characteristic1 : String?
let characteristic2 : String?
let characteristic3 : String?
let characteristic4 : String?
let characteristic5 : String?
enum CodingKeys: String, CodingKey {
case breedName
case picture
case characteristic1
case characteristic2
case characteristic3
case characteristic4
case characteristic5
}
func checkEmpty(s: String?) -> String? {
if s == "" {
return nil
}
return s
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.breedName= try container.decode(String.self, forKey: .breedName)
self.picture = try container.decode(String.self, forKey: .picture)
self.characteristic1 = try container.decodeIfPresent(String.self, forKey: .characteristic1)
self.characteristic2 = try container.decodeIfPresent(String.self, forKey: .characteristic2)
self.characteristic3 = try container.decodeIfPresent(String.self, forKey: .characteristic3)
self.characteristic4 = try container.decodeIfPresent(String.self, forKey: .characteristic4)
self.characteristic5 = try container.decodeIfPresent(String.self, forKey: .characteristic5)
self.characteristic1 = checkEmpty(s: self.characteristic1)
self.characteristic2 = checkEmpty(s: self.characteristic2)
self.characteristic3 = checkEmpty(s: self.characteristic3)
self.characteristic4 = checkEmpty(s: self.characteristic4)
self.characteristic5 = checkEmpty(s: self.characteristic5)
One solution is to check for empty in a function defined in an extension to String
extension String {
func emptyAsNil() -> String? {
self.isEmpty ? nil : self
}
}
Then you could do all in one step in the init
self.characteristic1 = try container.decodeIfPresent(String.self, forKey: .characteristic1)?.emptyAsNil()
But perhaps a better solution is to gather all those properties in a collection like an array or a dictionary. Here I have chosen an array
struct Cat: Decodable {
let breedName: String
let picture: String
var characteristics: [String]
}
and then in the init we add only non-nil, non-empty values to the array
if let value = try container.decodeIfPresent(String.self, forKey: .characteristic1), !value.isEmpty {
characteristics.append(value)
}
or another way is to loop over the keys
let keys: [CodingKeys] = [.characteristic1,
.characteristic2,
.characteristic3,
.characteristic4,
.characteristic5]
for key in keys {
if let value = try container.decodeIfPresent(String.self, forKey: key), !value.isEmpty {
characteristics.append(value)
}
}
I was going through couchbase-lite to use it in my next iOS app. I have created a model named Surah for now. Definitely, I will have more model classes later.
Basically I have four questions here.
How do I add _id as my primary key in couchbase-lite?
As I will be having more classes how will I handle those? As I am creating
MutableDocument, How will that differentiate each my classes?
As I can see I have to iterate through each of my items to batch insert, won't that become slow for the large datasets?
How do i convert results from a query with large data to a array of Model Class. (in this case of array of Surah)
class Surah: Decodable {
enum Keys: String, CodingKey {
case _id
case index
case englishName
case englishMeaning
case name
case place
case count
}
var _id = ""
var index = 1
var page = 1
var numberOfAyahs = 1
var englishName = ""
var englishMeaning = ""
var name = ""
var place = ""
var isFavorite = false
var dictionary: [String: Any] {
return ["_id": _id, "index": index, "page": page]
}
required init() {}
required init(_id: String, index: Int, name: String, englishName: String, englishMeaning: String, place: String, count: Int) {
self._id = _id
self.index = index
self.name = name
self.englishName = englishName
self.englishMeaning = englishMeaning
self.place = place
self.numberOfAyahs = count
}
required convenience init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self) // defining our (keyed) container
let _id: String = try container.decode(String.self, forKey: ._id)
let index: Int = try container.decode(Int.self, forKey: .index)
let name: String = try container.decode(String.self, forKey: .name)
let englishName: String = try container.decode(String.self, forKey: .englishName)
let englishMeaning: String = try container.decode(String.self, forKey: .englishMeaning)
let place: String = try container.decode(String.self, forKey: .place)
let count: Int = try container.decode(Int.self, forKey: .count)
self.init(_id: _id, index: index, name: name, englishName: englishName, englishMeaning: englishMeaning, place: place, count: count)
}}
Code for Database Queries
let surahs = try JSONDecoder().decode([Surah].self, from: data!)
DispatchQueue.global(qos: .background).async {
//background code
do {
if let db = App.shared.database {
try db.inBatch {
for item in surahs {
let doc = MutableDocument(data: item.dictionary)
doc.setString("users", forKey: "type")
doc.setValue(Keys._id, forKey: item._id)
// doc.setValue(Keys.englishName, forKey: item.englishName)
try db.saveDocument(doc)
let index = IndexBuilder.valueIndex(items:
ValueIndexItem.expression(Expression.property("_id")), ValueIndexItem.expression(Expression.property("type")))
try db.createIndex(index, withName: "TypeNameIndex")
print("saved user document \(doc.string(forKey: "englishName"))")
}
}
}
} catch let error {
DispatchQueue.main.async {
seal.reject(error)
}
}
DispatchQueue.main.async {
seal.fulfill(surahs)
}
}
Not sure what you mean by "primary key". You can always find a doc
by its id. The name of the field that contains it is Meta.id.
The field's value is mutableDoc.getId(). As you've noticed, you
can also explicitly set the id at creation
Couchbase doesn't store classes, it stores JSON documents. If you
have documents of different types (different internal structures,
analogous to different SQL tables), give them a type field and use
it in your query
Use Database.inBatch()
The same way you would convert any JSON document to a corresponding
class: gson, Jackson, Moshi, etc
Swift 4 added the new Codable protocol. When I use JSONDecoder it seems to require all the non-optional properties of my Codable class to have keys in the JSON or it throws an error.
Making every property of my class optional seems like an unnecessary hassle since what I really want is to use the value in the json or a default value. (I don't want the property to be nil.)
Is there a way to do this?
class MyCodable: Codable {
var name: String = "Default Appleseed"
}
func load(input: String) {
do {
if let data = input.data(using: .utf8) {
let result = try JSONDecoder().decode(MyCodable.self, from: data)
print("name: \(result.name)")
}
} catch {
print("error: \(error)")
// `Error message: "Key not found when expecting non-optional type
// String for coding key \"name\""`
}
}
let goodInput = "{\"name\": \"Jonny Appleseed\" }"
let badInput = "{}"
load(input: goodInput) // works, `name` is Jonny Applessed
load(input: badInput) // breaks, `name` required since property is non-optional
You can implement the init(from decoder: Decoder) method in your type instead of using the default implementation:
class MyCodable: Codable {
var name: String = "Default Appleseed"
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let name = try container.decodeIfPresent(String.self, forKey: .name) {
self.name = name
}
}
}
You can also make name a constant property (if you want to):
class MyCodable: Codable {
let name: String
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let name = try container.decodeIfPresent(String.self, forKey: .name) {
self.name = name
} else {
self.name = "Default Appleseed"
}
}
}
or
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Default Appleseed"
}
Re your comment: With a custom extension
extension KeyedDecodingContainer {
func decodeWrapper<T>(key: K, defaultValue: T) throws -> T
where T : Decodable {
return try decodeIfPresent(T.self, forKey: key) ?? defaultValue
}
}
you could implement the init method as
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decodeWrapper(key: .name, defaultValue: "Default Appleseed")
}
but that is not much shorter than
self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Default Appleseed"
You can use a computed property that defaults to the desired value if the JSON key is not found.
class MyCodable: Decodable {
var name: String { return _name ?? "Default Appleseed" }
var age: Int?
// this is the property that gets actually decoded/encoded
private var _name: String?
enum CodingKeys: String, CodingKey {
case _name = "name"
case age
}
}
If you want to have the property read-write, you can also implement the setter:
var name: String {
get { _name ?? "Default Appleseed" }
set { _name = newValue }
}
This adds a little extra verbosity as you'll need to declare another property, and will require adding the CodingKeys enum (if not already there). The advantage is that you don't need to write custom decoding/encoding code, which can become tedious at some point.
Note that this solution only works if the value for the JSON key either holds a string or is not present. If the JSON might have the value under another form (e.g. it's an int), then you can try this solution.
Approach that I prefer is using so called DTOs - data transfer object.
It is a struct, that conforms to Codable and represents the desired object.
struct MyClassDTO: Codable {
let items: [String]?
let otherVar: Int?
}
Then you simply init the object that you want to use in the app with that DTO.
class MyClass {
let items: [String]
var otherVar = 3
init(_ dto: MyClassDTO) {
items = dto.items ?? [String]()
otherVar = dto.otherVar ?? 3
}
var dto: MyClassDTO {
return MyClassDTO(items: items, otherVar: otherVar)
}
}
This approach is also good since you can rename and change final object however you wish to.
It is clear and requires less code than manual decoding.
Moreover, with this approach you can separate networking layer from other app.
You can implement.
struct Source : Codable {
let id : String?
let name : String?
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(String.self, forKey: .id) ?? ""
name = try values.decodeIfPresent(String.self, forKey: .name)
}
}
I came across this question looking for the exact same thing. The answers I found were not very satisfying even though I was afraid that the solutions here would be the only option.
In my case, creating a custom decoder would require a ton of boilerplate that would be hard to maintain so I kept searching for other answers.
I ran into this article that shows an interesting way to overcome this in simple cases using a #propertyWrapper. The most important thing for me, was that it was reusable and required minimal refactoring of existing code.
The article assumes a case where you'd want a missing boolean property to default to false without failing but also shows other different variants.
You can read it in more detail but I'll show what I did for my use case.
In my case, I had an array that I wanted to be initialized as empty if the key was missing.
So, I declared the following #propertyWrapper and additional extensions:
#propertyWrapper
struct DefaultEmptyArray<T:Codable> {
var wrappedValue: [T] = []
}
//codable extension to encode/decode the wrapped value
extension DefaultEmptyArray: Codable {
func encode(to encoder: Encoder) throws {
try wrappedValue.encode(to: encoder)
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode([T].self)
}
}
extension KeyedDecodingContainer {
func decode<T:Decodable>(_ type: DefaultEmptyArray<T>.Type,
forKey key: Key) throws -> DefaultEmptyArray<T> {
try decodeIfPresent(type, forKey: key) ?? .init()
}
}
The advantage of this method is that you can easily overcome the issue in existing code by simply adding the #propertyWrapper to the property. In my case:
#DefaultEmptyArray var items: [String] = []
Hope this helps someone dealing with the same issue.
UPDATE:
After posting this answer while continuing to look into the matter I found this other article but most importantly the respective library that contains some common easy to use #propertyWrappers for these kind of cases:
https://github.com/marksands/BetterCodable
If you don't want to implement your encoding and decoding methods, there is somewhat dirty solution around default values.
You can declare your new field as implicitly unwrapped optional and check if it's nil after decoding and set a default value.
I tested this only with PropertyListEncoder, but I think JSONDecoder works the same way.
If you think that writing your own version of init(from decoder: Decoder) is overwhelming, I would advice you to implement a method which will check the input before sending it to decoder. That way you'll have a place where you can check for fields absence and set your own default values.
For example:
final class CodableModel: Codable
{
static func customDecode(_ obj: [String: Any]) -> CodableModel?
{
var validatedDict = obj
let someField = validatedDict[CodingKeys.someField.stringValue] ?? false
validatedDict[CodingKeys.someField.stringValue] = someField
guard
let data = try? JSONSerialization.data(withJSONObject: validatedDict, options: .prettyPrinted),
let model = try? CodableModel.decoder.decode(CodableModel.self, from: data) else {
return nil
}
return model
}
//your coding keys, properties, etc.
}
And in order to init an object from json, instead of:
do {
let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
let model = try CodableModel.decoder.decode(CodableModel.self, from: data)
} catch {
assertionFailure(error.localizedDescription)
}
Init will look like this:
if let vuvVideoFile = PublicVideoFile.customDecode($0) {
videos.append(vuvVideoFile)
}
In this particular situation I prefer to deal with optionals but if you have a different opinion, you can make your customDecode(:) method throwable
From the server I have a big JSON returned that looks something like this:
{
"id": "123",
"status": "ok",
"person": {
"administration": {
"name": "John"
}
},
"company": {
"name": "Test"
}
}
I have a struct:
struct Info: Decodable, Object {
let id: String
let status: String
let personName: String
let companyName: String
}
It conforms to Decodable protocol and also is a Object (Realm entity).
My question is: Am I able somehow to decode the name of the person in personName? Something like person.administration.name.
I want the end Realm Object, to be a flat one and mostly all of the fields are strings.
Should I create separate structs for Person/Company without being Realm Objects and in decode method to set the corresponding value to "personName"?
let personName: String = try container.decode((Person.Administration.name).self, forKey: .personName)
You can simply use containers to decode nested data with Decodable, i.e.
struct Info: Decodable {
let id: String
let status: String
let personName: String
let companyName: String
enum CodingKeys: String, CodingKey {
case id, status
case person, administration
case company
case name
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(String.self, forKey: .id)
status = try values.decode(String.self, forKey: .status)
//Decoding personName
let person = try values.nestedContainer(keyedBy: CodingKeys.self, forKey: .person)
let administration = try person.nestedContainer(keyedBy: CodingKeys.self, forKey: .administration)
personName = try administration.decode(String.self, forKey: .name)
//Decoding companyName
let company = try values.nestedContainer(keyedBy: CodingKeys.self, forKey: .company)
companyName = try company.decode(String.self, forKey: .name)
}
}
Example:
I've decoded the JSON you provided above, i.e.
if let data = json.data(using: .utf8) {
let info = try? JSONDecoder().decode(Info.self, from: data)
print(info)
}
The output it gives is:
(id: "123", status: "ok", personName: "John", companyName: "Test")
You can separate out the CodingKeys for all the different levels as per your wish. I kept them at the same level for simplicity.
Suggestion: Try using the optional types with Codable. This is because the API response can be unexpected. And if you don't get any expected key-value pair, you might end up getting a nil while creating the object.
It is best practice to separate transport types you're parsing your JSON into and types to represent object in the storage.
But if you want to use this combined types you should do something like this:
struct Info: Decodable {
let id: String
let status: String
let personName: String
let companyName: String
// JSON root keys
private enum RootKeys: String, CodingKey {
case id, status, person, company
}
// Keys for "person" nested "object"
private enum PersonKeys: String, CodingKey {
case administration
}
// Keys for "administration" and "company"
private enum NamedKeys: String, CodingKey {
case name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RootKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.status = try container.decode(String.self, forKey: .status)
let personContainer = try container.nestedContainer(keyedBy: PersonKeys.self, forKey: .person)
let administrationContainer = try personContainer.nestedContainer(keyedBy: NamedKeys.self, forKey: .administration)
self.personName = try administrationContainer.decode(String.self, forKey: .name)
let companyContainer = try container.nestedContainer(keyedBy: NamedKeys.self, forKey: .company)
self.companyName = try companyContainer.decode(String.self, forKey: .name)
}
}
I separated keys into three different CodingKey types for some type safety, and to prevent accidental mixup.
(This is a follow-up from this question: Using Decodable protocol with multiples keys.)
I have the following Swift code:
let additionalInfo = try values.nestedContainer(keyedBy: UserInfoKeys.self, forKey: .age)
age = try additionalInfo.decodeIfPresent(Int.self, forKey: .realage)
I know that if I use decodeIfPresent and don't have the property it will still handle it correctly if it's an optional variable.
For example the following JSON works to parse it using the code above.
{
"firstname": "Test",
"lastname": "User",
"age": {"realage": 29}
}
And the following JSON works as well.
{
"firstname": "Test",
"lastname": "User",
"age": {"notrealage": 30}
}
But the following doesn't work.
{
"firstname": "Test",
"lastname": "User"
}
How can I make all 3 examples work? Is there something similar to decodeIfPresent for nestedContainer?
You can use the following KeyedDecodingContainer function:
func contains(_ key: KeyedDecodingContainer.Key) -> Bool
Returns a Bool value indicating whether the decoder contains a value associated with the given key. The value associated with the given key may be a null value as appropriate for the data format.
For instance, to check if the "age" key exists before requesting the corresponding nested container:
struct Person: Decodable {
let firstName, lastName: String
let age: Int?
enum CodingKeys: String, CodingKey {
case firstName = "firstname"
case lastName = "lastname"
case age
}
enum AgeKeys: String, CodingKey {
case realAge = "realage"
case fakeAge = "fakeage"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.firstName = try values.decode(String.self, forKey: .firstName)
self.lastName = try values.decode(String.self, forKey: .lastName)
if values.contains(.age) {
let age = try values.nestedContainer(keyedBy: AgeKeys.self, forKey: .age)
self.age = try age.decodeIfPresent(Int.self, forKey: .realAge)
} else {
self.age = nil
}
}
}
I had this issue and I found this solution, just in case is helpful to somebody else:
let ageContainer = try? values.nestedContainer(keyedBy: AgeKeys.self, forKey: .age)
self.age = try ageContainer?.decodeIfPresent(Int.self, forKey: .realAge)
If you have an optional container, using try? values.nestedContainer(keyedBy:forKey) you don't need to check if the container exist using contains(.
Can you try pasting your sample JSON into quicktype to see what types it infers? Based on your question, I pasted your samples and got:
struct UserInfo: Codable {
let firstname: String
let age: Age?
let lastname: String
}
struct Age: Codable {
let realage: Int?
}
Making UserInfo.age and Age.realage optionals works, if that's what you're trying to accomplish.