Decoding Data Using 'Codable' - ios

I am trying to decode an array of my model objects(Catalog) from a JSON that looks like this after serialization of corresponding 'Data' object.
{ "id" : 5,
"catalogs" : [ {catalogKeyValue1},{catalogKeyValue2}]
}
My model object looks like this
struct Catalog : Codable{
var id : Int
var name : String
var categoryId : Int
var minProductPrice : Int
var maxProductDiscount : Int?
var shareText : String
var collageImage : String
var collageImageAspectRatio : Double?
var shipping : [String : Int]?
var description : String
}
I need to get an array of Catalogs (which is nested against 'catalogs' key in JSON) after decoding.I fully understand using nested containers and writing custom initialaizer for Catalog struct .How can I achieve this without having to write another Codable struct for outer JSOn that looks like this
struct CatalogArray: Codable {
var catalogs : [Catalog]
}
and then do something like this to get a decoded array of Catalogs
let catalogArray = try decoder.decode(CatalogArray.self, from: validData)
My problem is I dont have any need for this catalogArray struct. Is there a way of getting Catalog model objects decoded without having to create unnecessary nested structs.

You can do this instead of making a new Struct everytime: try container.decode([Catalog].self, forKey: "Catalogs")
Arrays of a type that is Codable are automatically Codable.

As per your comment
The problem with more Codable structs is that I will need to create another struct if the same array of catalogs comes with a different key in another API response.
You can create generic struct that can do the same for you. here is a example
struct GeneralResponse<T:Codable>: Codable {
let code: Int
let catalogs: T?
enum CodingKeys: String, CodingKey {
case code = "id"
case catalogs = "catalogs"
}
public init(from decoder:Decoder) throws {
let contaienr = try decoder.container(keyedBy: CodingKeys.self)
code = try contaienr.decode(Int.self, forKey: .code)
do {
let object = try contaienr.decodeIfPresent(T.self, forKey: .data)
catalogs = object
} catch {
catalogs = nil
}
}
}
Now
You can use this with different catalogs type of struct
like
GeneralResponse<[Catalogs]> or GeneralResponse<[CatalogsAnother]>
Hope it is helpful

Related

How to pass JSON MongoDB Query object from Swift?

I am passing JSON data from my iOS app to my backend MongoDB server
The JSON data consists of two parent fields userData & queryData
I pass this via POST request as req.body and retrieve the data in the server as req.body.userData and req.body.queryData
I use swift CODABLE to encode this object to jSON and send it to the server
Everything works fine. I am able to extract req.body.userData and play around with it. What I do not understand is, how do I extract a mongo query from req.body.queryData and pass it to a mongo find() method?
For example - How do I pass
{
"fieldOne": {"$in": [1, 2, 3]},
"fieldTwo": {"$ne": "Admin"}
}
The above is MongoDB syntax to filter data. How can I make my codable struct send this format? Right now I am simply declaring the struct as string. I am new to working with server side and JSONS.
Here is my struct - like I said, req.body.userData works fine
struct QueryData: Codable {
var fieldOne: String
var fieldTwo: String
}
import Foundation
let mongo = """
{
"fieldOne": {"$in": [1, 2, 3]},
"fieldTwo": {"$ne": "Admin"}
}
"""
struct FieldOne: Codable {
var `in`: [Int]
private enum CodingKeys: String, CodingKey {
case `in` = "$in"
}
}
struct FieldTwo: Codable {
var ne: String
private enum CodingKeys: String, CodingKey {
case ne = "$ne"
}
}
struct QueryData: Codable {
var fieldOne: FieldOne
var fieldTwo: FieldTwo
}
let jsonObject = try! JSONSerialization.jsonObject(with: mongo.data(using: .utf8)!, options: [])
print(jsonObject)
I try to parse you example in swift playground

Json Decode to swift class with dynamic value class/struct type

I have multiple responses which has similar pattern but one key value has always different object in response of json which i want to decode in base model where one key has variety of object type.
Response be like,
{
"status": true,
"message": "Success",
"data":[]
}
Here in data response it has any kind of array of objects or any single object
struct BaseResponseModel: Codable {
var status: Bool
var message: String
var data: DataClass
enum CodingKeys: String, CodingKey {
case message
case data
case status
}
}
what we can do here to make it single class with data type object pass,
Anyone please..!
Use Swift generics, and provide the type only at the time of decoding:
struct BaseResponseModel<DataType: Codable>: Codable {
var status: Bool
var message: String
var data: DataType
}
Usage:
let myData = try JSONDecoder().decode(BaseResponseModel<MyStruct>.self, from: data).data // For object
let myData = try JSONDecoder().decode(BaseResponseModel<[MyStruct]>.self, from: data).data // For array
Note: You don't need CodingKeys if the rawValues are the same.

How to replace/update dictionary in Array of Dictionary based on Type

I'm getting below JSON response from server, and displaying phone number on screen.
Now user can change/update any of phone number, so we have to update particular mobile number in same object and send it to server.
"phone_numbers": [
{
"type": "MOBILE",
"number": "8091212121"
},
{
"type": "HOME",
"number": "4161212943"
},
{
"type": "BUSINESS",
"number": "8091212344"
}
]
My model class is looks like this:
public struct Contact: Decodable {
public let phone_numbers: [Phone]?
}
public struct Phone: Decodable {
public let type: PhoneType?
public let number: String?
}
I'm struggling to update this JSON object for particular phone number.
For example, if I want to update BUSINESS number only in above array, What's best way to do it.
I'm using XCode 11 and Swift 5.
Because all your properties are defined as constants (let), nothing can be updated. You have to initialize and return a new Contact object with the updated phone numbers.
If you change the properties to var, then you can update:
public enum PhoneType: String, Decodable {
case mobile = "MOBILE"
case home = "HOME"
case business = "BUSINESS"
}
public struct Contact: Decodable {
public var phone_numbers: [Phone]?
mutating func update(phoneNumber: String, for type: PhoneType) {
guard let phone_numbers = self.phone_numbers else { return }
for (i, number) in phone_numbers.enumerated() {
if number.type == type {
self.phone_numbers![i].number = phoneNumber
}
}
}
}
public struct Phone: Decodable {
public var type: PhoneType?
public var number: String?
}
var contact = try! JSONDecoder().decode(Contact.self, from: jsonData)
contact.update(phoneNumber: "123456", for: .business)
I'm struggling to update this JSON object for particular phone number.
It shouldn't be a JSON object when you update it. Think of JSON as just a format for transferring data. Once transferred, you should parse it into something that you can work with, like an array of dictionaries or whatever. If you've done that, then more specific questions you might ask are:
How can I find a specific entry in an array?
How can I modify the fields of a struct?
How can I replace one entry in an array with another?
After looking at the definitions of your structures, I think the problem you're having probably has to do with how you've declared them:
public struct Phone: Decodable {
public let type: PhoneType?
public let number: String?
}
Because you used let to declare type and number, those fields cannot be changed after initialization. If you want the fields of a Phone struct to be modifiable, you need to declare them with var instead of let.
The same thing is true for your Contact struct:
public struct Contact: Decodable {
public let phone_numbers: [Phone]?
}
You've declared phone_numbers as an immutable array because you used let instead of var. If you want to be able to add, remove, or modify the array in phone_numbers, you need to use var instead.
The struct declarations you have right now work fine for reading the data from JSON because all the components of the JSON data are constructed using the values from the JSON. But again, you'll need to make those structs modifiable by switching to var declarations if you want to be able to make changes.
There are a couple of ways to approach this (I'm assuming PhoneType is an enum you have somewhere)
You can iterate over the array and guard for only business numbers, like so
for phone in phone_numbers{
guard phone.type == .MOBILE else { continue }
// Code that modifies phone
}
You can filter and iterate over the array, like so
phone_numbers.filter {$0.type == .BUSINESS }.forEach { phone in
// Modify phone here
}
You can then modify the right value in the array with it's index, like this
for (phoneIndex, phone) in phone_numbers.enumerated() {
guard phone.type == .BUSINESS else { continue }
phone_numbers[phoneIndex].type = ANOTHER_TYPE
}
Some can argue that the second is preferred over the first, because it is an higher order function, but in my day to day activities, I tend to use both and believe that this is a matter of taste

Set capacity to array of model in Decodable protocol

I want to set a model array max capacity if the user has not logged in even JSON response contains more records.
public class AlertsInbox: Codable {
public var messages: [Messages]?
public init(messages: [Messages]) {
self.messages = messages
if !GlobalVar.isLoggedIn,
let capacity = GlobalVar.messageCapacity {
self.messages?.reserveCapacity(capacity)
}
}
}
Decode json response:
let responseMessage = try JSONDecoder().decode(AlertsInbox.self, from: data)
Using reserveCapacity in init of model but it gets overridden with the number of records received in response.
How to restrict while decoding?
Thanks
You should not conditionally strip/drop out elements from an array in the Type's initializer. This consideration is against the responsibility role of Model object. Instead it's a job for the controller object.
But you can have an access point in the model type. Let's say, you have a property messages in your AlertsInbox object. You can have another access point with another property, let's say, limitedMessages that will be a computed property.
Look at the below code. I intentionally changed the type's semantics from Class to Struct. But you are free to use as your need.
struct AlertsInbox: Codable {
let messages: [Message]
var limitedMessages: [Message] {
// here you will use the value of your limit
// if the limit exceeds the size of the array, whole array will be returned
return Array(messages.prefix(5))
}
struct Message: Codable {
let title: String
}
}
Now you will use this, just like any other property, when you need the stripped out version of your actual messages. Like alertInbox.limitedMessages instead of the messages property.
But if you are not satisfied with the above and wanna stick to your plan, you can do that as well. See:
struct AlertsInbox: Codable {
let messages: [Message]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let messages = try container.decode([Message].self, forKey: .messages)
// the idea of limiting the element numbers is same as the above
self.messages = Array(messages.prefix(5))
}
struct Message: Codable {
let title: String
}
}

Swift 4 Codable: Cannot exclude a property

I'm developing a simple music sequencer app. This kind of app tends to have a complex data structure which has to be saved/loaded, so the introduction of Codable protocol in Swift4 is totally a good news for me.
My problem is this:
I have to have a non-Codable property. It doesn't have to be coded because it's a temporary variable only kept alive while the app is active.
So I've just tried to exclude by implementing CodingKey, but the compiler still give me the error "Type 'Song' does not conform to protocol 'Decodable'".
Specifically I want to exclude "musicSequence" in the code below.
class Song : Codable { //Type 'Song' does not conform to protocol 'Decodable'
var songName : String = "";
var tempo : Double = 120;
// Musical structure
var tracks : [Track] = [] // "Track" is my custom class, which conforms Codable as well
// Tones
var tones = [Int : ToneSettings] (); // ToneSettings is also my custom Codable class
var musicSequence : MusicSequence? = nil; // I get the error because of this line
private enum CodingKeys: String, CodingKey {
case songName
case tempo
case tracks
case tones
}
func createMIDISequence () {
// Create MIDI sequence based on "tracks" above
// and keep it as instance variable "musicSequence"
}
}
Does anybody have any ideas?
(See below for a strange turn of events.)
Your use of CodingKeys is already taking care of you encoding. You still get that for free. But you'll need to tell the system how to handle decoding by hand:
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
songName = try values.decode(String.self, forKey: .songName)
tempo = try values.decode(Double.self, forKey: .tempo)
tracks = try values.decode([Track].self, forKey: .tracks)
tones = try values.decode([Int: ToneSettings].self, forKey: .tones)
}
It's not quite smart enough to figure out that musicSequence can and should default to nil (and maybe that would be too smart anyway).
It's probably worth opening a defect at bugs.swift.org to ask for this Decodable to be automatic. It should be able to figure it out in cases where you provide CodingKeys and there is a default value.
EDIT: When I first answered this, I exactly duplicated your error. But when I tried to do it again, copying your code fresh, the error doesn't show up. The following code compiles and runs in a playground:
import Foundation
struct Track: Codable {}
struct ToneSettings: Codable {}
struct MusicSequence {}
class Song : Codable { //Type 'Song' does not conform to protocol 'Decodable'
var songName : String = "";
var tempo : Double = 120;
// Musical structure
var tracks : [Track] = [] // "Track" is my custom class, which conforms Codable as well
// Tones
var tones = [Int : ToneSettings] (); // ToneSettings is also my custom Codable class
var musicSequence : MusicSequence? = nil; // I get the error because of this line
private enum CodingKeys: String, CodingKey {
case songName
case tempo
case tracks
case tones
}
func createMIDISequence () {
// Create MIDI sequence based on "tracks" above
// and keep it as instance variable "musicSequence"
}
}
let song = Song()
let data = try JSONEncoder().encode(song)
String(data: data, encoding: .utf8)
let song2 = try JSONDecoder().decode(Song.self, from: data)
I'm wondering if there's a compiler bug here; make sure to test this with the new beta.

Resources