Can we reuse struct on Swift? Or is there any other way? - ios

So I have a user JSON structure that goes like this:
- results: {
meta: {}
users: []
},
- status:
I want to get the user so the User model I implement to get the JSON is like this:
struct Response: Decodable {
let results: Result
let status: Int
}
struct Result: Decodable {
let meta: Meta
let users: [User]
}
struct Meta: Decodable {
let total_data: Int
let total_page: Int
}
struct User: Decodable {
let avatar_url: String
let created_at: String
let updated_at: String
let email: String
let id: Int
let name: String
let username: String
}
It is working, but when I have another JSON that the structure is similar, let say like this
- results: {
meta: {}
rooms: []
},
- status:
And when I create Room model, with another struct Response on it, it will cause error because it is duplicated declaration.
Is it possible to reuse struct in Swift? Or is there any convenient way to do this?
Thanks

You could use generics.
struct Response<T: Decodable>: Decodable {
let results: Result<T>
let status: Int
}
struct Result<T: Decodable>: Decodable {
let meta: Meta
let objects: [T]
}
struct Meta: Decodable {
let total_data: Int
let total_page: Int
}
struct User: Decodable {
let avatar_url: String
let created_at: String
let updated_at: String
let email: String
let id: Int
let name: String
let username: String
}
let userResponse: Response<User>?

You can try to use generics in the following way:
struct Response<ResultType> : Decodable where ResultType : Decodable {
let results: ResultType
let status: Int
}
and then use that struct via:
struct Result: Decodable {
let something: String
}
let decoder = JSONDecoder()
let data = "{\"status\":123,\"results\":{\"something\":\"hi\"}}".data(using: .utf8)!
let value = try! decoder.decode(Response<Result>.self, from: data) // will be of type Response<Result>

You have one option is to reuse Meta and Status
If you use sm
For user You can replace name
struct UserResult: Decodable {
let meta: Meta
let users: [User]
}
struct UserResponse: Decodable {
let results: UserResult
let status: Int
}
and for Room
struct RoomResult: Decodable {
let meta: Meta
let users: [Room]
}
struct RoomResponse: Decodable {
let results: RoomResult
let status: Int
}

Depending on your needs, you can use generics to compose the whole struct.
Start with your data models
struct User: Decodable {
let avatar_url: String
let created_at: String
let updated_at: String
let email: String
let id: Int
let name: String
let username: String
}
struct Room: Decodable {
let id: Int
let name: String
}
The goal is to get nice composed types.
typealias UserResponse = Response<Result<User, Meta>>
typealias RoomResponse = Response<Result<Room, Meta>>
The generics types to build from
struct Response<ResultType: Decodable>: Decodable {
let results: ResultType
let status: Int
}
struct Result<ItemType: Decodable, MetaType: Decodable>: Decodable {
let meta: MetaType
let items: [ItemType]
}
Even Meta is a separate part of the composition.
struct Meta: Decodable {
let total_data: Int
let total_page: Int
}
Now lets say we need a custom Meta for the Room response
struct PagedMeta: Decodable {
let current_page: Int
let page_count: Int
}
Here is the new type
typealias RoomPagedResponse = Response<Result<Room, PagedMeta>>

Related

Codable: parse JSON with dynamic struct [duplicate]

I am trying to parse the below JSON response, which has multiple dynamic keys,
{
"Nagaland": {
"districtData": {
"Dimapur": {
"confirmed": 1,
"lastupdatedtime": "",
"delta": {
"confirmed": 0
}
}
}
},
"Meghalaya": {
"districtData": {
"East Khasi Hills": {
"confirmed": 1,
"lastupdatedtime": "",
"delta": {
"confirmed": 0
}
}
}
}
}
I have written my Codable struct like below,,
struct IndianStateListModel: Codable {
// MARK: Properties
let state: [String: StateData]
}
struct StateData: Codable {
// MARK: Properties
var districtData: Inner?
/// Mapping Key Enum
private enum CodingKeys: String, CodingKey {
case districtData
}
}
struct Inner: Codable {
// MARK: Properties
let districts: [String: DistrictData]
}
struct DistrictData: Codable {
// MARK: Properties
var confirmed: Int?
var lastupdatedtime: String?
var delta: DailyConfirmedData?
/// Mapping Key Enum
private enum CodingKeys: String, CodingKey {
case confirmed, lastupdatedtime, delta
}
}
struct DailyConfirmedData: Codable {
// MARK: Properties
var confirmed: Int?
/// Mapping Key Enum
private enum CodingKeys: String, CodingKey {
case confirmed
}
}
It's called as,
let summary = try JSONDecoder().decode(IndianStateListModel.self, from: data)
But its returning nil
P.S.: related question regarding decodable Swift Codable with dynamic keys
Any solution, would be great, Thanks in advance
The Codable models that you must use to parse the above JSON data should be like,
Models:
struct StateData: Codable {
var districtData: [String:DistrictData]?
}
struct DistrictData: Codable {
var confirmed: Int?
var lastupdatedtime: String?
var delta: DailyConfirmedData?
}
struct DailyConfirmedData: Codable {
var confirmed: Int?
}
Parsing:
let summary = try JSONDecoder().decode([String:StateData].self, from: data)
Note: There is no need to explicitly create enum CodingKeys if the JSON keys exactly match the properties of the Codable type.
The fundamental issue is that IndianStateListModel has a property called states. But no such key appears in your JSON. I’d suggest parsing it with singleValueContainer. E.g. perhaps:
struct States: Decodable {
typealias StateName = String
let states: [StateName: Districts]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
states = try container.decode([StateName: Districts].self)
}
}
struct Districts: Decodable {
typealias DistrictName = String
var districts: [DistrictName: DistrictData]
enum CodingKeys: String, CodingKey {
case districts = "districtData"
}
}
struct DistrictData: Decodable {
var confirmed: Int
var lastupdatedtime: String
var delta: DailyConfirmedData
}
struct DailyConfirmedData: Decodable {
var confirmed: Int?
}
And
do {
let result = try JSONDecoder().decode(States.self, from: data)
print(result)
} catch {
print(error)
}

Swift Codable to parse JSON with dynamic keys

I am trying to parse the below JSON response, which has multiple dynamic keys,
{
"Nagaland": {
"districtData": {
"Dimapur": {
"confirmed": 1,
"lastupdatedtime": "",
"delta": {
"confirmed": 0
}
}
}
},
"Meghalaya": {
"districtData": {
"East Khasi Hills": {
"confirmed": 1,
"lastupdatedtime": "",
"delta": {
"confirmed": 0
}
}
}
}
}
I have written my Codable struct like below,,
struct IndianStateListModel: Codable {
// MARK: Properties
let state: [String: StateData]
}
struct StateData: Codable {
// MARK: Properties
var districtData: Inner?
/// Mapping Key Enum
private enum CodingKeys: String, CodingKey {
case districtData
}
}
struct Inner: Codable {
// MARK: Properties
let districts: [String: DistrictData]
}
struct DistrictData: Codable {
// MARK: Properties
var confirmed: Int?
var lastupdatedtime: String?
var delta: DailyConfirmedData?
/// Mapping Key Enum
private enum CodingKeys: String, CodingKey {
case confirmed, lastupdatedtime, delta
}
}
struct DailyConfirmedData: Codable {
// MARK: Properties
var confirmed: Int?
/// Mapping Key Enum
private enum CodingKeys: String, CodingKey {
case confirmed
}
}
It's called as,
let summary = try JSONDecoder().decode(IndianStateListModel.self, from: data)
But its returning nil
P.S.: related question regarding decodable Swift Codable with dynamic keys
Any solution, would be great, Thanks in advance
The Codable models that you must use to parse the above JSON data should be like,
Models:
struct StateData: Codable {
var districtData: [String:DistrictData]?
}
struct DistrictData: Codable {
var confirmed: Int?
var lastupdatedtime: String?
var delta: DailyConfirmedData?
}
struct DailyConfirmedData: Codable {
var confirmed: Int?
}
Parsing:
let summary = try JSONDecoder().decode([String:StateData].self, from: data)
Note: There is no need to explicitly create enum CodingKeys if the JSON keys exactly match the properties of the Codable type.
The fundamental issue is that IndianStateListModel has a property called states. But no such key appears in your JSON. I’d suggest parsing it with singleValueContainer. E.g. perhaps:
struct States: Decodable {
typealias StateName = String
let states: [StateName: Districts]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
states = try container.decode([StateName: Districts].self)
}
}
struct Districts: Decodable {
typealias DistrictName = String
var districts: [DistrictName: DistrictData]
enum CodingKeys: String, CodingKey {
case districts = "districtData"
}
}
struct DistrictData: Decodable {
var confirmed: Int
var lastupdatedtime: String
var delta: DailyConfirmedData
}
struct DailyConfirmedData: Decodable {
var confirmed: Int?
}
And
do {
let result = try JSONDecoder().decode(States.self, from: data)
print(result)
} catch {
print(error)
}

Omitting struct value when creating swift

rookie here, this may be a simple question but lets say I have a struct that looks like this:
struct User: Codable, Identifiable {
let id: String
let firstname: String
let lastname: String
let age: Int
}
This is the basic user model, but when i create a new user i basically need the same struct with no id:
struct UserCreate: Codable {
let firstname: String
let lastname: String
let age: Int
}
Seems quite verbose to create two structs. Is there a better pattern for this sort of thing?
Thanks
maybe this is what you want?
struct UserWithID: Codable, Identifiable {
let id: String
struct User: Codable {
let firstname: String
let lastname: String
let age: Int
}
}
You can set id as an optional and use other object in User
struct User: Codable, Identifiable {
let id: String?
let userCreate: UserCreate
}
struct UserCreate: Codable {
let firstname: String
let lastname: String
let age: Int
}
You can just set id as an optional and use this object
struct User: Codable, Identifiable {
let id: String?
let firstname: String
let lastname: String
let age: Int
}
Just give a default value for id.
let id = UUID().uuidString

Failing with the decode of JSON

I am trying to decode a JSON response from the youtube API in swift.
The JSON information is:
I made a Decodable structure:
// Build a model object to import the JSON data.
struct PlaylistInformation: Decodable {
struct Items: Decodable {
struct VideoNumber: Decodable {
struct Snippet: Decodable {
let title: String
}
let snippet: Snippet
}
let videoNumber: VideoNumber
}
let items: Items
}
And I get the error when trying to decode:
// We decode the JSON data get from the url according to the structure we declared above.
guard let playlistInformation = try? JSONDecoder().decode(PlaylistInformation.self, from: data!) else {
print("Error: could not decode data into struct") <-- HERE IS THE ERROR
return
}
// Comparing DB Versions.
let videoTitle = playlistInformation.items.videoNumber.snippet.title as NSString
print(videoTitle)
The error I get is:
Error: could not decode data into struct
I guess it has something to do with the "items" in the struct, as it is an array... but I have no idea about how to solve that.
Given that items is an array, you have to model it as an array and not a struct:
// Build a model object to import the JSON data.
struct PlaylistInformation: Decodable {
struct Item: Decodable {
struct Snippet: Decodable {
let title: String
}
let snippet: Snippet
}
let items: [Item]
}
And then access each item using its index, e.g.
let videoTitle = playlistInformation.items[0].snippet.title as NSString
print(videoTitle)
Yes, the error was coming from the "items" in the struct as it is an array.
The correct Decodable struct is:
struct PlaylistInformation: Decodable {
struct Items: Decodable {
struct Snippet: Decodable {
struct Thumbnails: Decodable {
struct High: Decodable {
let url: String
}
let high: High
}
struct ResourceId: Decodable {
let videoId: String
}
let publishedAt: String
let title: String
let thumbnails: Thumbnails
let resourceId: ResourceId
}
let snippet: Snippet
}
let items: [Items]
}
Thank you for your help.

How to Decode Nested dictionary with nested arrays using Swift Decodable property?

The actual JSON,that I need to parse in swift4 is,
{
"class": {
"semester1": [
{
"name": "Kal"
},
{
"name": "Jack"
},
{
"name": "Igor"
}
],
"subjects": [
"English",
"Maths"
]
},
"location": {
"Dept": [
"EnglishDept",
],
"BlockNo": 1000
},
"statusTracker": {
"googleFormsURL": "beacon.datazoom.io",
"totalCount": 3000
}
}
The code that I'd tried but failed to execute is,
struct Class: Decodable {
let semester: [internalComponents]
let location: [location]
let statusTracker: [statusTracker]
enum CodingKeys: String, CodingKey {
case semester = "semester1"
case location = "location"
case statusTracker = "statusTracker"
}
}
struct location: Decodable {
let Dept: [typesSubIn]
let BlockNo: Int
}
struct statusTracker: Decodable {
let googleFormsURL: URL
let totalCount: Int
}
struct internalComponents: Decodable {
let semester1: [semsIn]
let subjects: [subjectsIn]
}
struct semsIn: Decodable {
let nameIn: String
}
struct subjectsIn: Decodable {
let subjects: String
}
struct Dept: Decodable {
let Depts: String
}
I know it's completely wrong can someone give the actual format? I'm actually confused with the format for "subjects".It's not compiling as a whole too.
There are many issues.
You are making a common mistake by ignoring the root object partially.
Please take a look at the JSON: On the top level there are 3 keys class, location and statusTracker. The values for all 3 keys are dictionaries, there are no arrays.
Since class (lowercase) is a reserved word, I'm using components. By the way please conform to the naming convention that struct names start with a capital letter.
struct Root : Decodable {
let components : Class
let location: Location
let statusTracker: StatusTracker
enum CodingKeys: String, CodingKey { case components = "class", location, statusTracker }
}
There are many other problems. Here a consolidated version of the other structs
struct Class: Decodable {
let semester1: [SemsIn]
let subjects : [String]
}
struct Location: Decodable {
let dept : [String]
let blockNo : Int
enum CodingKeys: String, CodingKey { case dept = "Dept", blockNo = "BlockNo" }
}
struct SemsIn: Decodable {
let name: String
}
struct StatusTracker: Decodable {
let googleFormsURL: String // URL is no benefit
let totalCount: Int
}
Now decode Root
do {
let result = try decoder.decode(Root.self, from: data)
} catch { print(error) }
It looks like you sterilize Class object in wrong way. It should looks like:
struct Class: Decodable {
let class: [internalComponents]
let location: [location]
let statusTracker: [statusTracker]
}
There are a few things here causing your issue.
You have no top level item, I added Response struct
Location, class and statusTracker are both at the same level, not under class.
In your class struct, your items are set as arrays but they aren't arrays
To debug these types of issues, wrap your decode in a do catch block and print out the error. it will tell you the reason it failed to parse
Try this code from my playground:
let jsonData = """
{
"class": {
"semester1": [{
"name": "Kal"
}, {
"name": "Jack"
}, {
"name": "Igor"
}],
"subjects": [
"English",
"Maths"
]
},
"location": {
"Dept": [
"EnglishDept"
],
"BlockNo": 1000
},
"statusTracker": {
"googleFormsURL": "beacon.datazoom.io",
"totalCount": 3000
}
}
""".data(using: .utf8)!
struct Response: Decodable {
let cls: Class
let location: Location
let statusTracker: statusTracker
enum CodingKeys: String, CodingKey {
case cls = "class"
case location
case statusTracker
}
}
struct Class: Decodable {
let semester: [SemesterStudents]
let subjects: [String]
enum CodingKeys: String, CodingKey {
case semester = "semester1"
case subjects
}
}
struct Location: Decodable {
let dept: [String]
let blockNo: Int
enum CodingKeys: String, CodingKey {
case dept = "Dept"
case blockNo = "BlockNo"
}
}
struct statusTracker: Decodable {
let googleFormsURL: URL
let totalCount: Int
}
struct SemesterStudents: Decodable {
let name: String
}
struct Dept: Decodable {
let Depts: String
}
do {
let result = try JSONDecoder().decode(Response.self, from: jsonData)
print(result)
} catch let error {
print(error)
}
Another approach is to create an intermediate model that closely matches the JSON, let Swift generate the methods to decode it, and then pick off the pieces that you want in your final data model:
// snake_case to match the JSON
fileprivate struct RawServerResponse: Decodable {
struct User: Decodable {
var user_name: String
var real_info: UserRealInfo
}
struct UserRealInfo: Decodable {
var full_name: String
}
struct Review: Decodable {
var count: Int
}
var id: Int
var user: User
var reviews_count: [Review]
}
struct ServerResponse: Decodable {
var id: String
var username: String
var fullName: String
var reviewCount: Int
init(from decoder: Decoder) throws {
let rawResponse = try RawServerResponse(from: decoder)
// Now you can pick items that are important to your data model,
// conveniently decoded into a Swift structure
id = String(rawResponse.id)
username = rawResponse.user.user_name
fullName = rawResponse.user.real_info.full_name
reviewCount = rawResponse.reviews_count.first!.count
}
}

Resources