Swift Codable - decode nested dictionary - swift

Lets say I have a dictionary like this:
{"user_data":{"id":3,"name":"Damian D","email":"aaa#aaa.pl"},"status":true}
How can I use Codable protocol to decode just user_data into such struct:
struct User: Codable {
private enum CodingKeys: String, CodingKey {
case id
case username = "name"
case email
}
let id: Int
let username: String
let email: String
}
Do I need to convert this sub dictionary into Data, or is there a easier way?

If you create nested Coding Keys you will accomplish decoding the response using only one data model.
Given the following JSON response:
let data = """
{
"user_data": {
"id":3,
"name":"Damian D",
"email":"aaa#aaa.pl"
},
"status":true
}
""".data(using: .utf8, allowLossyConversion: false)!
and the following data model:
public struct User: Decodable {
var id: Int
var name: String
var email: String
// MARK: - Codable
private enum RootCodingKeys: String, CodingKey {
case userData = "user_data"
enum NestedCodingKeys: String, CodingKey {
case id
case name
case email
}
}
required public init(from decoder: Decoder) throws {
let rootContainer = try decoder.container(keyedBy: RootCodingKeys.self)
let userDataContainer = try rootContainer.nestedContainer(keyedBy: RootCodingKeys.NestedCodingKeys.self, forKey: .userData)
self.id = try userDataContainer.decode(Int.self, forKey: .id)
self.name = try userDataContainer.decode(String.self, forKey: .name)
self.email = try userDataContainer.decode(String.self, forKey: .email)
}
}
You can decode your response into a single object:
let decoder = JSONDecoder()
let user = try? decoder.decode(User.self, from: data)

Create a new struct that has a userData member of type User.
struct Response: Codable {
private enum CodingKeys: String, CodingKey {
case userData = "user_data"
case status
}
let userData: User
let status: Bool
}

Related

Swift decodable with programatically provided coding keys

This is a simplified model that is decoded from JSON:
struct Info: Decodable {
var text: String
var num: Int
}
struct Root: Decodable {
let info: Info
}
Sometimes I need to decode Info.text or Info.num only but sometimes both of them and to support all options I've made similar structs for decoding e.g:
// For text only
struct InfoText: Decodable {
var text: String
}
struct RootText: Decodable {
let info: InfoText
}
// For num only
struct InfoNum: Decodable {
var num: Int
}
struct RootNum: Decodable {
let info: InfoNum
}
This approach produces much cloned code and runtime checks to process the structs so is it possible to decode provided coding keys only with the single struct?
It's possible to provide any contextual information to the decoder with userInfo property and in this case we can pass an array of coding keys and use this info in the decoding process:
struct Info: Decodable {
var text: String?
var num: Int?
static var keys = CodingUserInfoKey(rawValue: "keys")!
enum CodingKeys: String, CodingKey {
case text, num
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
guard let keys = decoder.userInfo[Self.keys] as? [CodingKeys] else {
return
}
if keys.contains(.text) {
text = try container.decode(String.self, forKey: .text)
}
if keys.contains(.num) {
num = try container.decode(Int.self, forKey: .num)
}
}
}
struct Root: Decodable {
let info: Info
}
let json = #"{ "info" : { "text": "Hello", "num": 20 } }"#.data(using: .utf8)!
let decoder = JSONDecoder()
let keys: [Info.CodingKeys] = [.text]
decoder.userInfo[Info.keys] = keys
let root = try decoder.decode(Root.self, from: json)
print(root)
// Outputs:
Root(info: Info(text: Optional("Hello"), num: nil))

How to create model for this json with codable

I have the below json and I want to create model for the json with codable.
{
id = 1;
name = "abc";
empDetails = {
data = [{
address = "xyz";
ratings = 2;
"empId" = 6;
"empName" = "def";
}];
};
}
Model
struct Root: Codable {
let id: Int
let name: String
let empDetails:[Emp]
struct Emp: Codable {
let address: String
let ratings: Int
let empId: Int
let empName: String
}
}
I don't need the key data. I want to set the value of data to empDetails property
How can I do this with init(from decoder: Decoder) throws method?
Simply create enum CodingKeys and implement init(from:) in struct Root to get that working.
struct Root: Decodable {
let id: Int
let name: String
let empDetails: [Emp]
enum CodingKeys: String, CodingKey {
case id, name, empDetails, data
}
struct Emp: Codable {
let address: String
let ratings: Int
let empId: Int
let empName: String
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
let details = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .empDetails)
empDetails = try details.decode([Emp].self, forKey: .data)
}
}

Failable Initializers with Codable

I'm attempting to parse the following json schema of array of items, itemID may not be empty. How do I make an item nil id itemID does not exist in the JSON?
[{
"itemID": "123",
"itemTitle": "Hello"
},
{},
...
]
My decodable classes are as follows:
public struct Item: : NSObject, Codable {
let itemID: String
let itemTitle: String?
}
private enum CodingKeys: String, CodingKey {
case itemID
case itemTitle
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
itemID = try container.decode(String.self, forKey: .itemID)
itemTitle = try container.decodeIfPresent(String.self, forKey: .itemTitle)
super.init()
}
}
First of all, itemID is an Int and not String in your JSON response. So the struct Item looks like,
public struct Item: Codable {
let itemID: Int?
let itemTitle: String?
}
Parse the JSON like,
if let data = data {
do {
let items = try JSONDecoder().decode([Item].self, from: data).filter({$0.itemID == nil})
print(items)
} catch {
print(error)
}
}
In the above code you can simply filter out the items with itemID == nil.

Trying to show all driver names in a JSON Api with codable protocol in swift 4

I am working with an F1 API and want to show all driver names in a swift project. I am using the codable protocol and I understand how I would access one name or property from the JSON API. It is just if I wanted to access all a selected properties from that JSON API. I have tried countless different ways to achieve this but with little success. Below is the code I have.
struct MRData: Codable {
let xmlns: String
let series: String
let url: String
let limit, offset, total: String
let standingsTable: StandingsTable
enum CodingKeys: String, CodingKey {
case xmlns, series, url, limit, offset, total
case standingsTable = "StandingsTable"
}
}
struct StandingsTable: Codable {
let season: String
let standingsLists: [StandingsList]
enum CodingKeys: String, CodingKey {
case season
case standingsLists = "StandingsLists"
}
}
struct StandingsList: Codable {
let season, round: String
let driverStandings: [DriverStanding]
enum CodingKeys: String, CodingKey {
case season, round
case driverStandings = "DriverStandings"
}
}
struct DriverStanding: Codable {
let position, positionText, points, wins: String
let driver: Driver
let constructors: [Constructor]
enum CodingKeys: String, CodingKey {
case position, positionText, points, wins
case driver = "Driver"
case constructors = "Constructors"
}
}
struct Constructor: Codable {
let constructorId: String
let url: String
let name: String
let nationality: String
}
struct Driver: Codable {
let driverId: String
let url: String
let givenName, familyName, dateOfBirth, nationality: String
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let jsonUrlString = "https://ergast.com/api/f1/1981/driverstandings.json"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let f1Data = try JSONDecoder().decode(F1Data.self, from: data)
let season = f1Data.mrData.standingsTable.season
print(f1Data.mrData.standingsTable.standingsLists[0].
driverStandings[1].driver.familyName)
print(season)
} catch {
print(error)
}
}.resume()
}
Any help would be appreciated.
Add a for loop.
let firstDriver = f1Data.mrData.standingsTable.standingsLists[0].driverStandings
for driver in firstDriver {
//print(firstDriver)
print(driver.driver.familyName)
}

How to handle dynamic keys from json response using Swift 4 coding keys

In my app i am getting the response like this how i will handle this kind of response which has this which has dynamic keys in every data array
{
"code": 200,
"message": "Top Likers info.",
"error": null,
"data": {
"manishamohapatra11": {
"id": "5591322611",
"username": "manishamohapatra11",
"full_name": ":-)Sunshine :-)",
"profile_pic_url": "https://scontent-atl3-1.cdninstagram.com/vp/d2ba431ac70ec067d5d6def73a721250/5B553C8E/t51.2885-19/s150x150/27877706_168319037141873_8387886458379173888_n.jpg",
"followed_by_viewer": true,
"requested_by_viewer": false,
"likeCount": 7
},
"chrysxnthemxm": {
"id": "5658970660",
"username": "chrysxnthemxm",
"full_name": "Quotes and Sayings",
"profile_pic_url": "https://scontent-atl3-1.cdninstagram.com/vp/f1fb37c94c181d49d9997e24b5d70068/5B40EF01/t51.2885-19/s150x150/20478547_331538517270771_7021810425566068736_a.jpg",
"followed_by_viewer": true,
"requested_by_viewer": false,
"likeCount": 4
}
}
}
i have created structure response
// A struct that conforms to the CodingKey protocol
// It defines no key by itself, hence the name "Generic"
struct GenericCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
}
struct Response: Decodable {
// Define the inner model as usual
struct User: Decodable {
var id: String
var userName: String
var fullName: String
var profilePicURL: URL
var followedByViewer: Bool
var requestedByViewer: Bool
var likeCount: Int
private enum CodingKeys: String, CodingKey {
case id
case userName = "username"
case fullName = "full_name"
case profilePicURL = "profile_pic_url"
case followedByViewer = "followed_by_viewer"
case requestedByViewer = "requested_by_viewer"
case likeCount
}
}
var code: Int
var message: String
var error: String?
var users: [User]
private enum CodingKeys: String, CodingKey {
case code, message, error, data
}
// You must decode the JSON manually
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.code = try container.decode(Int.self, forKey: .code)
self.message = try container.decode(String.self, forKey: .message)
self.error = try container.decodeIfPresent(String.self, forKey: .error)
self.users = [User]()
let subContainer = try container.nestedContainer(keyedBy: GenericCodingKeys.self, forKey: .data)
for key in subContainer.allKeys {
let user = try subContainer.decode(User.self, forKey: key)
self.users.append(user)
}
}
}
You can try this type...
let arrTemp:NSArray = data.allKeys as NSArray
print(arrTemp)
let strData:NSDictionary = param.value(forKey: arrTemp.object(at: index) as! String) as! NSDictionary
print(strData)