parsing json any type with Codable - json

I am sorry if this question is answered many times but am looking for my own specific json issue that's why am posting this, This is my json its an array list of notifications and in the extra object you can see it has contract object, Now this object is changing it can be campaign or feedback in the 2nd index of the json array how do I make Codable struct for this to decode this type of jsonHere is my struct, I cannot do Any type in Codable
struct Alert: Decodable {
var id: Int?
var isUnread: Bool?
var userId: Int?
var message: String?
var notificationType: String?
var extra: Any?
"value": [
{
"id": 153,
"is_unread": true,
"user_id": 3,
"message": "Contract offered from JohnWick. Click here to see the details.",
"notification_type": "Contract",
"extra": {
"contract": {
"id": 477,
"likes": 0,
"shares": 0,
"account_reach": 0.0,
"followers": 0,
"impressions": 0.0,
"description": "Fsafasd",
"budget": 0.0,
"start_date": null,
"end_date": null,
"status": "pending",
"is_accepted": false,
"brand_id": 443,
"influencer_id": 3,
"proposal_id": 947,
"created_at": "2019-11-09T17:40:57.646Z",
"updated_at": "2019-11-09T17:40:57.646Z",
"contract_fee": 435345.0,
"base_fee": 5000.0,
"transaction_fee": 43534.5,
"total_fee": 483879.5,
"infuencer_completed": false,
"brand_completed": false,
"comments": 0
}
}
},
{
"id": 152,
"is_unread": true,
"user_id": 3,
"message": "Message from JohnWick. Click here to check your inbox.",
"notification_type": "Message",
"extra": {
"message": {
"id": 495,
"body": "Uuhvh",
"read": false,
"conversation_id": 42,
"user_id": 3,
"created_at": "2019-11-08T13:44:02.055Z",
"updated_at": "2019-11-08T13:44:02.055Z"
}
}
},
]
As you can see it can be message, or campaign , or contract or feedback so how do I parse this or make model for this with CodingKeys Codable

A simple solution would be to make a struct called Extra that has four optional properties for each of the cases that you have.
struct Extra: Codable {
let contract: Contract?
let message: Message?
let feedback: Feedback?
let campaign: Campaign?
}
As long as each of message, campaign, contract, and feedback have fixed responses then you should be able to make structs for them that conform to Codable.

You don't mean Any here. As you said, extra can be "message, or campaign, or contract or feedback." It can't be a UIViewController or a CBPeripheral. It's not "anything." It's one of 4 things.
"One of X things" is an enum:
enum Extra {
case contract(Contract)
case campaign(Campaign)
case message(Message)
case feedback(Feedback)
}
To make it Decodable, we just need to look for the right key:
extension Extra: Decodable {
enum CodingKeys: CodingKey {
case contract, campaign, message, feedback
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// Try to decode each thing it could be; throw if nothing matches.
if let contract = try? container.decode(Contract.self, forKey: .contract) {
self = .contract(contract)
} else if let campaign = try? container.decode(Campaign.self, forKey: .campaign) {
self = .campaign(campaign)
} else if let message = try? container.decode(Message.self, forKey: .message) {
self = .message(message)
} else if let feedback = try? container.decode(Feedback.self, forKey: .feedback) {
self = .feedback(feedback)
} else {
throw DecodingError.valueNotFound(Self.self,
DecodingError.Context(codingPath: container.codingPath,
debugDescription: "Could not find extra"))
}
}
}

in this article, you can find a way that support any type with the codable, so it will not matter if JSON returns String or Int:
anycodableValue

It's not Any type, you got four different but predictable types.
With reference to Rob's answer you can get rid of the decoding attempts in the if - else if chain if you decode notificationType as enum and decode the subtypes according to its value
enum NotificationType : String, Decodable {
case contract = "Contract", message = "Message", campaign = "Campaign", feedback = "Feedback"
}
enum Extra {
case contract(Contract)
case campaign(Campaign)
case message(Message)
case feedback(Feedback)
}
struct Alert: Decodable {
let id: Int
let isUnread: Bool
let userId: Int
let message: String
let notificationType: NotificationType
let extra: Extra
private enum CodingKeys : String, CodingKey { case id, isUnread, userId, message, notificationType, extra}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
isUnread = try container.decode(Bool.self, forKey: .isUnread)
userId = try container.decode(Int.self, forKey: .userId)
message = try container.decode(String.self, forKey: .message)
notificationType = try container.decode(NotificationType.self, forKey: .notificationType)
switch notificationType {
case .contract:
let contractData = try container.decode([String:Contract].self, forKey: .extra)
extra = .contract(contractData[notificationType.rawValue.lowercased()]!)
case .campaign:
let campaignData = try container.decode([String:Campaign].self, forKey: .extra)
extra = .campaign(campaignData[notificationType.rawValue.lowercased()]!)
case .message:
let messageData = try container.decode([String:Message].self, forKey: .extra)
extra = .message(messageData[notificationType.rawValue.lowercased()]!)
case .feedback:
let feedbackData = try container.decode([String:Feedback].self, forKey: .extra)
extra = .feedback(feedbackData[notificationType.rawValue.lowercased()]!)
}
}
}

Related

Complex JSON in Swift. How to get the data correctly. Different structures

Faced a difficult problem for me, when receiving data I don’t know how to decompose data in one array.
The responsible variable contains different types of data.
Do I get it right? I think in the initializer to go through the possible options and substitute the desired one? What type should the variable of this array be?
[
{
"id": 42,
"created_at": "2021-09-08T08:55:58.000000Z",
"updated_at": "2021-09-08T08:55:58.000000Z",
"link": "u4986",
"type": "u",
"responsible": {
"id": 4986,
"type": "management_company",
"email": "X#X.com",
"phone": "+0000000000",
"comment": null,
"first_name": "Alex",
"second_name": "Hook"
}
},
{
"id": 43,
"created_at": "2021-09-08T08:55:58.000000Z",
"updated_at": "2021-09-08T08:55:58.000000Z",
"link": "r14",
"type": "r",
"responsible": {
"id": 14,
"name": "manager",
"guard_name": "api",
"created_at": "2021-06-15T19:20:20.000000Z",
"updated_at": "2021-06-15T19:20:20.000000Z"
}
}
]
How to make an initializer for MyJson
struct MyJson: Codable {
let id: Int
let createdAt: String
let updatedAt: String
let link: String
let type: String
let responsible: Any
}
// MARK: - Responsible
struct User: Codable {
let id: Int
let type, email, phone, comment: String
let firstName, secondName: String
}
struct UserCategory: Codable {
let id: Int
let name, guardName, createdAt, updatedAt: String
}
In swift JSON parsing is pretty straightforward, build an object that reflects your JSON (I've just built an example based on your JSON here):
struct JsonExample: Decodable {
let id: Int
let responsible: [Responsible]
struct Responsible: Decodable {
let id: Int
let email: String
let guard_name: String
}
}
and then just decode it
let jsonData = "json_string".data(using: .utf8)!
do {
let decoded = try JSONDecoder().decode([JsonExample].self, from: jsonData)
} catch let error {
print(error.localizedDescription)
}
If you want to distinguish between nested objects you can use init and use a property inside your JSON to get the job done
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
let type = try container.decode(String.self, forKey: .type)
if type == "a" {
let data = try container.decode([ResponsibleA].self, forKey: . responsible)
responsible = .responsibleA(data)
} else { // add better error handling
let data = try container.decode([ResponsibleB].self, forKey: . responsible)
responsible = .responsibleB(data)
}
}
Completed the task for a very long time. Finally I solved the problem. If you are facing this problem. You will find the solution here Indeterminate Types with Codable in Swift

How to fix "Expected to decode Dictionary<String, Any> but found an array instead"?

I am trying to decode JSON like this.
this is my data model
struct ResponseData: Decodable {
var AppointMentStatusId:Int!
var FromTime:String!
var ToTime:String!
var AppointmentDate: String!
var AppointMentStatus:String!
var DoctorFirstName:String!
var AppointmentId:Int!
var Rating:Float
var DoctorImage:String
enum CodingKeys: String, CodingKey {
case AppointMentStatusId
case FromTime
case ToTime
case AppointmentDate
case AppointMentStatus
case DoctorFirstName
case AppointmentId
case Rating
case DoctorImage
}
}
I also set init in ResponseData
struct Response:Decodable {
var Code:Int!
var Data2:String?
var Message:String!
var NoOfItems:String!
var Status:Bool!
var Data:[ResponseData]
enum CodingKeys: String, CodingKey {
case Code
case Data2
case Message
case NoOfItems
case Status
case Data
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
I have got problem with this code
if let app = try container.decodeIfPresent([ResponseData].self, forKey: .Data) {// Problem is here
self.Data = app
}else{
self.Data = []
}
if let code = try container.decodeIfPresent(Int.self, forKey: .Code) {
self.Code = code
}else{
self.Code = 0
}
I have set try for all keys
I am trying to handle the nil value of Data key.
When I got Data key is nil it's working fine
When I got data in Data key it's show error.
typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "Data", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))
It's my JSON Data
{
"Code": 0,
"Status": true,
"Message": "Success",
"Data": [
{
"PatientUserID": 196,
"FirstName": "gaurav",
"MiddleName": "",
"LastName": "",
"PatientName": "gaurav ",
"MobileNo": "8585959585",
"EmailId": "gaurav.sainini#gmail.com",
"DOB": "2005-06-14T00:00:00",
"Gender": "Male",
"RegistrationId": 121,
"VisitId": null,
"FromTime": "8:00AM",
"ToTime": "8:15AM",
"AppointmentDateForComparison": "2019-07-20T00:00:00",
"AppointmentDate": "20-07-2019",
"AppointMentStatusId": 1233,
"AppointMentStatus": "Booked",
"DoctorId": 1,
"DoctorFirstName": "Dr.Doctor",
"Rating": 3.5,
"DoctorImage": "http://23.88.103.43:83/Assets/Icons/DoctorIcon/DefaultMale.png",
"AppointmentId": 250,
"MemberID": 126
}
],
"Data2": null,
"NoOfItems": null
}
Please help me on how to handle this error?
Thanks
Remove the init altogether and make your Data property optional
var Data:[ResponseData]?
Actually, when I try your init method it works fine. How do you decode? This is how I did it.
let decoder = JSONDecoder()
do {
let result = try decoder.decode(Response.self, from: data)
print(result.Data)
} catch {
print(error)
}
It isn't necessary to use the override init(from decoder: Decoder) method and write enum CodingKeys. If the keys are the same, it should be decoding automatically.

Swift 4 decode complex nested JSON

I'm trying to create a cleaner codable structure in which I can access the "description" by just typing day.description rather than day.weather.description
The description value is nested in an array "weather" which only contains a single object. I would like to extract the description from the index 0 and assign it to description in my struct.
Here is the JSON that I'm working with:
{
"dt": 1558321200,
"main": {
"temp": 11.88,
"temp_min": 11.88,
"temp_max": 11.88,
"pressure": 1013.3,
"sea_level": 1013.3,
"grnd_level": 1003.36,
"humidity": 77,
"temp_kf": 0
},
"weather": [{
"id": 800,
"main": "Clear",
"description": "clear sky",
"icon": "01n"
}],
"clouds": {
"all": 0
},
"wind": {
"speed": 5.58,
"deg": 275.601
},
"sys": {
"pod": "n"
},
"dt_txt": "2019-05-20 03:00:00"
}
and the code I have so far:
struct Weather: Codable {
let days: [Day]
enum CodingKeys: String, CodingKey {
case days = "list"
}
}
struct Day: Codable {
let date: String
let main: Main
let wind: Wind
let description: String
enum CodingKeys: String, CodingKey {
case date = "dt_txt"
case main
case wind
case weather
case description
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
date = try container.decode(String.self, forKey: .date)
main = try container.decode(Main.self, forKey: .main)
wind = try container.decode(Wind.self, forKey: .wind)
let weather = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .weather)
description = try weather.decode(String.self, forKey: .description)
}
}
As you've been told, the simplest approach is just a computed property that references the desired value. However, for the sake of completeness we might as well discuss how to do the thing you actually asked to do. Let's illustrate with a reduced version of your JSON:
{
"dt": 1558321200,
"weather": [{
"id": 800,
"main": "Clear",
"description": "clear sky",
"icon": "01n"
}]
}
So the question is, how can we parse this into, say, a struct Result with a description property such that we fetch out the "description" key from the first item in the "weather" array? Here's one approach:
struct Result : Decodable {
let description : String
enum Keys : CodingKey {
case weather
}
struct Weather : Decodable {
let description : String
}
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: Keys.self)
var arr = try! con.nestedUnkeyedContainer(forKey: .weather) // weather array
let oneWeather = try! arr.decode(Weather.self) // decode first element
self.description = oneWeather.description
}
}
Basically the idea here is that nestedUnkeyedContainer gives us our array, and subsequent calls to decode on that array automatically tackle each element in turn. We have only one element so we need only one decode call. How we dispose of the resulting string is up to us, so now we can slot it into our top-level description property.
But here's another approach. We don't even really need the secondary Weather struct; we can just dive right into the "weather" array and grab the first dictionary element and access its "description" key without saying any more about what's in that inner dictionary, like this:
struct Result : Decodable {
let description : String
enum Keys : CodingKey {
case weather
case description
}
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: Keys.self)
var arr = try! con.nestedUnkeyedContainer(forKey: .weather)
let con2 = try! arr.nestedContainer(keyedBy: Keys.self)
let desc = try! con2.decode(String.self, forKey: .description)
self.description = desc
}
}
Your question was not very complete (you didn't show your real JSON, just an excerpt), so I can't give any more precise advice, but I'm sure you can see how adapt this technique to your needs.

Struct Codable. Unable to Encode nested json

I have checked severa other questions asked on SO, even though they look similar they did not address my needs and challenges I currently have in an implementation. The questions and answer I have seen so far addresses more of Decodable. However I am currently having issue wit Encodable
I have nested JSON structure Like the below. I am able to successfully decoable and pass the json
"data": {
"id": "XXXXXXXXXX",
"tKey": "XXXXXXXX",
"tID": "XXXXX",
"type": "XXXXX",
"hasEvent": true,
"location": "XXXXXXXXXXXXX",
"cover": {
"name": "XXXXXXX",
"parentId": "XXXXXXX",
"parentName": "XXXXXX",
"pop": {
"logo": "*********",
}
},
"men": [
{
"id": "XXXXXXXXXXX",
"title": "XXXXXXX"
},
{
"id": "XXXXXX",
"title": "XXXXXX"
},
{
"id": "XXXXXX",
"title": "XXXXXX"
}
],
"homes": [],
"homeTypes": [{
"id": "XXXXXX",
"subMenuId": "XXXXXXXX",
"type": "Flat",
"data": {
"latitude": 29.767,
"longitude": 0,0000,
},
"datas": null,
"component": null
},
{
"id": "XXXXXXXXX",
"subMenuId": "XXXXX",
"type": "Bubgalow",
"data": {
"id": "XXXXXXXXX,
"title": "XXXXXXXXX",
"summary": "Hello;",
}
}
]
}
The decodable works fine as I am able to pass in response from the server and populate tableview. However on trying to encode using encodable protocol it does not seem to work. Please find the code snippet below that I have tried:
Struct DataObject: Codable {
var id: String = ""
var location: String = ""
var tKey: String = ""
var tId: String = ""
var cover: cover!
var homeTypes: [Type] = [TYpe]()
var flat = Flat()
var bungalow = Bungalow()
var flat = Flat()
var bungalow = Bungalow()
enum CodingKeys: String, CodingKey {
case id = "id"
case type = "type"
case location = "location"
case tKey = "tKey"
case tId = "tId"
case cover = "cover"
case homeTypes = "homeTypes"
}
enum ComponentCodingKeys: String, CodingKey {
case id = "id"
case type = "type"
case component = "component"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
if container.contains(.tKey) {
tKey = try container.decodeIfPresent(String.self, forKey: .tKey) ?? ""
}
if container.contains(.tId) {
tId = try container.decodeIfPresent(String.self, forKey: .tId) ?? ""
}
let typeRawValue = try container.decode(String.self, forKey: .type)
if let tempType = SomeType(rawValue: typeRawValue) {
type = tempType
}
if container.contains(.location) {
location = try container.decodeIfPresent(String.self, forKey: .location) ?? ""
}
cover = try container.decode(Cover.self, forKey: .cover)
homeTypes = try container.decode([HomeTypes].self, forKey: .component)
for homeType in homeTypes {
switch homeType.type {
case .flat:
flat = homeType.flat
}
case .bungalow:
bungalow = homeType.bungalow
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(location, forKey: .location)
try container.encode(tKey, forKey: .tKey)
try container.encode(tId, forKey: .tId)
try container.encode(cover, forKey: .cover)
//let rawValueType = try container.encode(type, forKey: .type)
var homes = container.nestedUnkeyedContainer(forKey: .component)
try homeTypes.forEach {
try homes.encode($0)
}
}
}
}
There are several issues with your code. Tidy these up first and then you might have a clearer idea what's going on
Use let rather than var. If a value doesn't exist, use nil rather than an empty string.
struct DataObject: Codable {
let id: String
let location: String?
let tKey: String?
let tId: String?
let cover: Cover
let homeTypes: [Type]
}
You don't need to give your CodingKeys a string value if they match the json keys. So just…
enum CodingKeys: CodingKey {
case id, type, location, tKey, tId, cover, homeTypes
}
No need to test if a key exists if you're using decodeIfPresent
tKey = try container.decodeIfPresent(String.self, forKey: .tKey)
Make Type: Codable, then
try container.encode(homeTypes, forKey: .homeTypes)

How to decode variable from json when key is changing according to user input?

I am trying to parse some JSON response coming from CoinmarketCap using the JSONDecoder() in Swift 4. But the problem is that the response from json is changing according to user input. e.g if user wants the price in eur, the output is following:
[
{
"price_eur": "9022.9695444"
}
]
but if user wants the price in gbp:
[
{
"price_gbp": "7906.8032145"
}
]
So the question is how should I make the struct that inherits from Decodable if the variable(json key) name is changing?
You can decode the dynamic key by creating a custom init(from:) method for your struct, then using two set of coding keys, an enum containing all keys that are known at compile time and another struct that you initialize using the dynamic keys that are generated using user input (contain the name of the currency).
In your custom init(from:) method you just need to decode each property using their respective keys.
let chosenCurrency = "gbp"
struct CurrencyResponse: Decodable {
let name:String
let symbol:String
let price:String
private static var priceKey:String {
return "price_\(chosenCurrency)"
}
private enum SimpleCodingKeys: String, CodingKey {
case name, symbol
}
private struct PriceCodingKey : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
init(from decoder:Decoder) throws {
let values = try decoder.container(keyedBy: SimpleCodingKeys.self)
name = try values.decode(String.self, forKey: .name)
symbol = try values.decode(String.self, forKey: .symbol)
let priceValue = try decoder.container(keyedBy: PriceCodingKey.self)
price = try priceValue.decode(String.self, forKey: PriceCodingKey(stringValue:CurrencyResponse.priceKey)!)
}
}
do {
let cryptoCurrencies = try JSONDecoder().decode([CurrencyResponse].self, from: priceJSON.data(using: .utf8)!)
} catch {
print(error)
}
Test JSON:
let priceJSON = """
[
{
"id": "bitcoin",
"name": "Bitcoin",
"symbol": "BTC",
"rank": "1",
"price_\(chosenCurrency)": "573.137",
"price_btc": "1.0",
"24h_volume_\(chosenCurrency)": "72855700.0",
"market_cap_\(chosenCurrency)": "9080883500.0",
"available_supply": "15844176.0",
"total_supply": "15844176.0",
"percent_change_1h": "0.04",
"percent_change_24h": "-0.3",
"percent_change_7d": "-0.57",
"last_updated": "1472762067"
},
{
"id": "ethereum",
"name": "Ethereum",
"symbol": "ETH",
"rank": "2",
"price_\(chosenCurrency)": "12.1844",
"price_btc": "0.021262",
"24h_volume_\(chosenCurrency)": "24085900.0",
"market_cap_\(chosenCurrency)": "1018098455.0",
"available_supply": "83557537.0",
"total_supply": "83557537.0",
"percent_change_1h": "-0.58",
"percent_change_24h": "6.34",
"percent_change_7d": "8.59",
"last_updated": "1472762062"
}
]
"""
If you have a small number of possible keys, you can do the following
struct Price: Decodable {
var value: String
enum CodingKeys: String, CodingKey {
case price_eur
case price_gbp
case price_usd
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
value = try container.decode(String.self, forKey: .price_eur)
} catch {
do {
value = try container.decode(String.self, forKey: .price_gbp)
} catch {
value = try container.decode(String.self, forKey: .price_usd)
}
}
}
}
let data = try! JSONSerialization.data(withJSONObject: ["price_gbp": "10.12"], options: [])
let price = try JSONDecoder().decode(Price.self, from: data)
Otherwise, you will need to parse data manually.