JSON Parsing, issue with Nested JSON structure - ios

Let's say we have this URL, I'm using CodableAlamofire to fetch and parse the JSON response.
For the above URL response I've created following Codable classes.
struct CoinList: Codable {
let raw: Raw
let display: Display
enum CodingKeys: String, CodingKey {
case raw = "RAW"
case display = "DISPLAY"
}
}
struct Display: Codable {
let usd: [String: Usd]
enum CodingKeys: String, CodingKey {
case usd = "USD"
}
}
struct Raw: Codable {
let usd: [String: Usd]
enum CodingKeys: String, CodingKey {
case usd = "USD"
}
}
struct Usd: Codable {
let type, market, fromsymbol, tosymbol: String
let flags: String
let price: Double
let lastupdate: Int
let lastvolume, lastvolumeto: Double
let lasttradeid: String
let volumeday, volumedayto, volume24Hour, volume24Hourto: Double
let openday, highday, lowday, open24Hour: Double
let high24Hour, low24Hour: Double
let lastmarket: String
let change24Hour, changepct24Hour, changeday, changepctday: Double
let supply, mktcap, totalvolume24H, totalvolume24Hto: Double
enum CodingKeys: String, CodingKey {
case type = "TYPE"
case market = "MARKET"
case fromsymbol = "FROMSYMBOL"
case tosymbol = "TOSYMBOL"
case flags = "FLAGS"
case price = "PRICE"
case lastupdate = "LASTUPDATE"
case lastvolume = "LASTVOLUME"
case lastvolumeto = "LASTVOLUMETO"
case lasttradeid = "LASTTRADEID"
case volumeday = "VOLUMEDAY"
case volumedayto = "VOLUMEDAYTO"
case volume24Hour = "VOLUME24HOUR"
case volume24Hourto = "VOLUME24HOURTO"
case openday = "OPENDAY"
case highday = "HIGHDAY"
case lowday = "LOWDAY"
case open24Hour = "OPEN24HOUR"
case high24Hour = "HIGH24HOUR"
case low24Hour = "LOW24HOUR"
case lastmarket = "LASTMARKET"
case change24Hour = "CHANGE24HOUR"
case changepct24Hour = "CHANGEPCT24HOUR"
case changeday = "CHANGEDAY"
case changepctday = "CHANGEPCTDAY"
case supply = "SUPPLY"
case mktcap = "MKTCAP"
case totalvolume24H = "TOTALVOLUME24H"
case totalvolume24Hto = "TOTALVOLUME24HTO"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
type = try values.decode(String.self, forKey: .type)
market = try values.decode(String.self, forKey: .market)
fromsymbol = try values.decode(String.self, forKey: .fromsymbol)
tosymbol = try values.decode(String.self, forKey: .tosymbol)
flags = try values.decode(String.self, forKey: .flags)
price = try values.decode(Double.self, forKey: .price)
lastvolume = try values.decode(Double.self, forKey: .lastvolume)
lastvolumeto = try values.decode(Double.self, forKey: .lastvolumeto)
lastupdate = try values.decode(Int.self, forKey: .lastupdate)
if let value = try? values.decode(Int.self, forKey: .lasttradeid) {
lasttradeid = String(value)
} else {
lasttradeid = try values.decode(String.self, forKey: .lasttradeid)
}
volumeday = try values.decode(Double.self, forKey: .volumeday)
volumedayto = try values.decode(Double.self, forKey: .volumedayto)
volume24Hour = try values.decode(Double.self, forKey: .volume24Hour)
volume24Hourto = try values.decode(Double.self, forKey: .volume24Hourto)
openday = try values.decode(Double.self, forKey: .openday)
highday = try values.decode(Double.self, forKey: .highday)
lowday = try values.decode(Double.self, forKey: .lowday)
open24Hour = try values.decode(Double.self, forKey: .open24Hour)
high24Hour = try values.decode(Double.self, forKey: .high24Hour)
low24Hour = try values.decode(Double.self, forKey: .low24Hour)
lastmarket = try values.decode(String.self, forKey: .lastmarket)
change24Hour = try values.decode(Double.self, forKey: .change24Hour)
changepct24Hour = try values.decode(Double.self, forKey: .changepct24Hour)
changeday = try values.decode(Double.self, forKey: .changeday)
changepctday = try values.decode(Double.self, forKey: .changepctday)
supply = try values.decode(Double.self, forKey: .supply)
mktcap = try values.decode(Double.self, forKey: .mktcap)
totalvolume24H = try values.decode(Double.self, forKey: .totalvolume24H)
totalvolume24Hto = try values.decode(Double.self, forKey: .totalvolume24Hto)
}
}
After successful response, I'm unable to parse the JSON, I studied a lot on nested JSON parsing with Swift Codable but still unable to get success.
Please help me to parse the above JSON response with nested JSON structure, like say Display and Raw object have all properties of Usd.
I think there is some minor mistake I'm doing.
Any help will be appreciated.
UPDATE
I've created JSON file for the response and parsing it,
if let path = Bundle.main.path(forResource: "test", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let result = try JSONDecoder().decode(CoinList.self, from: data)
print(result)
} catch {
print(error.localizedDescription)
}
}
Error is this,
JSON structure is like this,
Please find my my web api call,
Alamofire.request(TickerRouter.PriceMultiFull(params: params))
.validate(statusCode: 200..<300)
.responseString { responseData in
let data = responseData.value?.replacingOccurrences(of: "\\/", with: "/").data(using: .utf8)
if responseData.error == nil {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ AnyKey(stringValue: $0.last!.stringValue.lowercased())!})
decoder.dateDecodingStrategy = .secondsSince1970
let result = try? decoder.decode(CoinList.self, from: data!)
success(result!)
} else {
let msg = "Something went wrong. Please try again later"
failure(msg)
}
}
result is nil here, it is working with local json file. :(

At first glance you will notice that the member types in the USD dictionary for raw and display are widely different, so a single struct for both doesn't work.
The root object is (the String keys are the BTH and XRP symbols)
struct CoinList: Codable {
let raw: [String: Raw]
let display: [String: Display]
}
The Raw and Display structs contain the usd key and the appropriate struct
struct Raw: Codable {
let usd: USDRaw
}
struct Display: Codable {
let usd: USDDisplay
}
The USDRaw and USDDisplay structs contain all data, lastupdate in USDRaw will be decoded as Date
struct USDRaw: Codable {
let type, market, flags, fromsymbol, tosymbol: String
let price : Double
let lastupdate: Date
let lastvolume, lastvolumeto: Double
let lasttradeid: String
let volumeday, volumedayto, volume24hour, volume24hourto: Double
let openday, highday, lowday, open24hour: Double
let high24hour, low24hour: Double
let lastmarket: String
let change24hour, changepct24hour, changeday, changepctday: Double
let supply, mktcap, totalvolume24h, totalvolume24hto: Double
}
struct USDDisplay: Codable {
let fromsymbol, tosymbol, market, price, lastupdate: String
let lastvolume, lastvolumeto, lasttradeid, volumeday, volumedayto, volume24hour, volume24hourto : String
let openday, highday, lowday, open24hour, high24hour, low24hour, lastmarket: String
let change24hour, changepct24hour, changeday, changepctday: String
let supply, mktcap, totalvolume24h, totalvolume24hto: String
}
To get rid of specifying all CodingKeys and make the keys lowercased create a helper struct (stolen from the documentation)
struct AnyKey: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
init?(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
}
Pass the custom keyDecodingStrategy and a suitable dateDecodingStrategy to the decoder
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ AnyKey(stringValue: $0.last!.stringValue.lowercased())!})
decoder.dateDecodingStrategy = .secondsSince1970
let coinList = try decoder.decode(CoinList.self, from: data)
print(coinList)

Related

Decoding json with optional fields

I am trying to parse a json string in which some of the keys are not fixed. There are some keys which are related to error and either it will be error or the result data. Followings are the two examples:
{
"ok": true,
"result": {
"code": "694kyH",
"short_link": "shrtco.de\/694kyH",
"full_short_link": "https:\/\/shrtco.de\/694kyH",
"short_link2": "9qr.de\/694kyH",
"full_short_link2": "https:\/\/9qr.de\/694kyH",
"short_link3": "shiny.link\/694kyH",
"full_short_link3": "https:\/\/shiny.link\/694kyH",
"share_link": "shrtco.de\/share\/694kyH",
"full_share_link": "https:\/\/shrtco.de\/share\/694kyH",
"original_link": "http:\/\/google.com"
}
}
{
"ok": false,
"error_code": 2,
"error": "This is not a valid URL, for more infos see shrtco.de\/docs"
}
How will I parse this JSON. I have tried to build my class like following but it is not working:
struct ShortLinkData: Codable {
let ok: Bool
let result: Result?
let errorCode: Int?
let error: String?
private enum CodingKeys : String, CodingKey { case ok, result, errorCode = "error_code", error }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
ok = try container.decode(Bool.self, forKey: .ok)
result = try container.decode(Result.self, forKey: .result)
errorCode = try container.decodeIfPresent(Int.self, forKey: .errorCode)
error = try container.decodeIfPresent(String.self, forKey: .error)
}
}
// MARK: - Result
struct Result: Codable {
let code, shortLink: String
let fullShortLink: String
let shortLink2: String
let fullShortLink2: String
let shortLink3: String
let fullShortLink3: String
let shareLink: String
let fullShareLink: String
let originalLink: String
enum CodingKeys: String, CodingKey {
case code
case shortLink = "short_link"
case fullShortLink = "full_short_link"
case shortLink2 = "short_link2"
case fullShortLink2 = "full_short_link2"
case shortLink3 = "short_link3"
case fullShortLink3 = "full_short_link3"
case shareLink = "share_link"
case fullShareLink = "full_share_link"
case originalLink = "original_link"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
code = try container.decode(String.self, forKey: .code)
shortLink = try container.decode(String.self, forKey: .shortLink)
fullShortLink = try container.decode(String.self, forKey: .fullShortLink)
shortLink2 = try container.decode(String.self, forKey: .shortLink2)
fullShortLink2 = try container.decode(String.self, forKey: .fullShortLink2)
shortLink3 = try container.decode(String.self, forKey: .shortLink3)
fullShortLink3 = try container.decode(String.self, forKey: .fullShortLink3)
shareLink = try container.decode(String.self, forKey: .shareLink)
fullShareLink = try container.decode(String.self, forKey: .fullShareLink)
originalLink = try container.decode(String.self, forKey: .originalLink)
}
}
My parsing code:
let str = String(decoding: data, as: UTF8.self)
print(str)
let shortURL = try? JSONDecoder().decode(ShortLinkData.self, from: data)
return shortURL!
I am always getting nil in shortURL object.
You should split this into several steps in order to avoid to handle all these optionals in your model.
First create a struct that has only those properties that are guaranteed to be there. ok in your case:
struct OKResult: Codable{
let ok: Bool
}
then create one for your error state and one for your success state:
struct ErrorResult: Codable{
let ok: Bool
let errorCode: Int
let error: String
private enum CodingKeys: String, CodingKey{
case ok, errorCode = "error_code", error
}
}
struct ShortLinkData: Codable {
let ok: Bool
let result: Result
}
struct Result: Codable {
let code, shortLink: String
let fullShortLink: String
let shortLink2: String
let fullShortLink2: String
let shortLink3: String
let fullShortLink3: String
let shareLink: String
let fullShareLink: String
let originalLink: String
enum CodingKeys: String, CodingKey {
case code
case shortLink = "short_link"
case fullShortLink = "full_short_link"
case shortLink2 = "short_link2"
case fullShortLink2 = "full_short_link2"
case shortLink3 = "short_link3"
case fullShortLink3 = "full_short_link3"
case shareLink = "share_link"
case fullShareLink = "full_share_link"
case originalLink = "original_link"
}
}
Then you can decode the data:
guard try JSONDecoder().decode(OKResult.self, from: data).ok else{
let errorResponse = try JSONDecoder().decode(ErrorResult.self, from: data)
//handle error scenario
fatalError(errorResponse.error) // or throw custom error or return nil etc...
}
let shortlinkData = try JSONDecoder().decode(ShortLinkData.self, from: data)
Remarks:
Your inits are not necessary.
Never use try? this will hide all errors from you
you would need to wrap this either in a do catch block or make your function throwing and handle errors further up the tree.
Actually there are no optional fields. The server sends two different but distinct JSON strings.
A suitable way to decode both JSON strings is an enum with associated values. It decodes the ok key, then it decodes either the result dictionary or errorCode and error
enum Response : Decodable {
case success(ShortLinkData), failure(Int, String)
private enum CodingKeys : String, CodingKey { case ok, result, errorCode, error }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let ok = try container.decode(Bool.self, forKey: .ok)
if ok {
let result = try container.decode(ShortLinkData.self, forKey: .result)
self = .success(result)
} else {
let errorCode = try container.decode(Int.self, forKey: .errorCode)
let error = try container.decode(String.self, forKey: .error)
self = .failure(errorCode, error)
}
}
}
In ShortLinkData the init method and the CodingKeys are redundant if you specify the convertFromSnakeCase key decoding strategy
struct ShortLinkData: Decodable {
let code, shortLink: String
let fullShortLink: String
let shortLink2, fullShortLink2: String
let shortLink3, fullShortLink3: String
let shareLink, fullShareLink: String
let originalLink: String
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Response.self, from: data)
switch result {
case .success(let linkData): print(linkData)
case .failure(let code, let message): print("An error occurred with code \(code) and message \(message)")
}
} catch {
print(error)
}

Saving codable struct in Userdefaults returns nil values upon fetching

Saving data in defaults:
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(user) {
let defaults = UserDefaults.standard
defaults.set(encoded, forKey: DefaultKeys.user)
defaults.synchronize()
}
fetching data but getting nil keys of struct.
let defaults = UserDefaults.standard
if let savedPerson = defaults.object(forKey: DefaultKeys.user) as? Data {
let decoder = JSONDecoder()
if let loadedPerson = try? decoder.decode(UserModel.self, from: savedPerson) {
return loadedPerson
}
}
I tried to google the approaches everyone suggest the same to save custom object in userdefaults, but the approach is not working for me.Given below is my struct model which conform Codable as well.
struct UserModel : Codable {
var userDetails : UserDetail?
var status : Bool?
var message : String?
enum CodingKeys: String, CodingKey {
case userDetails = "userDetails"
case status = "status"
case message = "message"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
userDetails = try UserDetail(from: decoder)
status = try values.decodeIfPresent(Bool.self, forKey: .status)
message = try values.decodeIfPresent(String.self, forKey: .message)
}
}

Why are my json response objects returning nil?

I have some json:
{
"wallets":[
{
"fundingProviderName":"tns_cof",
"authLimit":75,
"pinRequired":true,
"wallets":[
{
"authLimit":75,
"isCardSupported":true,
"paymentProcessorId":6,
"mainDisplayText":"...0013",
"imageUrl":"https://az755244.vo.msecnd.net/paymentimages/visa2.png",
"userPaymentSourceId":9756,
"secondaryDisplayText":"12/30",
"expDate":"2030-12-31T23:59:59Z",
"cardIssuer":"Visa"
},
{
"authLimit":75,
"isCardSupported":true,
"paymentProcessorId":7,
"mainDisplayText":"...1020",
"imageUrl":"https://az755244.vo.msecnd.net/paymentimages/mastercard2.png",
"userPaymentSourceId":9757,
"secondaryDisplayText":"12/25",
"expDate":"2025-12-31T23:59:59Z",
"cardIssuer":"Mastercard"
},
{
"authLimit":75,
"isCardSupported":true,
"paymentProcessorId":8,
"mainDisplayText":"...3025",
"imageUrl":"https://az755244.vo.msecnd.net/paymentimages/amex.png",
"userPaymentSourceId":9758,
"secondaryDisplayText":"12/27",
"expDate":"2027-12-31T23:59:59Z",
"cardIssuer":"Amex"
}
],
"isSupported":true
}
]
}
my struct looks like this:
struct CreditCard: Codable {
var authLimit: Int?
var isCardSupported: Bool?
var paymentProcessorId: Int?
var mainDisplayText: String?
var imageUrl: String?
var userPaymentSourceId: Int?
var secondaryDisplayText: String?
var expDate: String?
var cardIssuer: String?
enum CodingKeys: String, CodingKey {
case cardIssuer = "cardIssuer"
case authLimit = "authLimit"
case isCardSupported = "isCardSupported"
case paymentProcessorId = "paymentProcessorId"
case mainDisplayText = "mainDisplayText"
case imageUrl = "imageUrl"
case userPaymentSourceId = "userPaymentSourceId"
case secondaryDisplayText = "secondaryDisplayText"
case expDate = "expDate"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
authLimit = try values.decodeIfPresent(Int.self, forKey: .authLimit)
isCardSupported = try values.decodeIfPresent(Bool.self, forKey: .isCardSupported)
paymentProcessorId = try values.decodeIfPresent(Int.self, forKey: .paymentProcessorId)
mainDisplayText = try values.decodeIfPresent(String.self, forKey: .mainDisplayText)
imageUrl = try values.decodeIfPresent(String.self, forKey: .imageUrl)
userPaymentSourceId = try values.decodeIfPresent(Int.self, forKey: .userPaymentSourceId)
secondaryDisplayText = try values.decodeIfPresent(String.self, forKey: .secondaryDisplayText)
expDate = try values.decodeIfPresent(String.self, forKey: .expDate)
cardIssuer = try values.decodeIfPresent(String.self, forKey: .cardIssuer)
}
}
my json decoder code looks like this:
do {
if let jsonData = response?.responseData {
let jsonDecoder = JSONDecoder()
let creditCards = try jsonDecoder.decode(CreditCard.self, from: jsonData)
print("credit cards \(creditCards)")
completion(nil, creditCards)
}
} catch {
print(error)
}
I'm sure there's probably an obvious oversight to why the model is still all nil. Any help would be really appreciated. Thanks!
Either decode from the top of the JSON (as #Rob answered) or traverse through the JSON and get the wallets key and then decode it.
if let jsonData = response?.responseData as? Dictionary<String,Any> {
if let walletData = jsonData["wallet"] as? [Dictionary<String,Any>] {
if let mainWalletData = walletData[0]["wallets"] as? [Dictionary<String,Any>] {
do {
let jsonDecoder = JSONDecoder()
let creditCards = try jsonDecoder.decode([CreditCard].self, from: mainWalletData)
print("credit cards \(creditCards)")
completion(nil, creditCards)
}
} catch {
print(error)
}
}
}
}
struct Wallet: Decodable {
let wallets: [WalletDetails]
}
struct WalletDetails: Decodable {
let fundingProviderName: String
let authLimit: Int
let pinRequired: Bool
let wallets: [CreditCard]
}
struct CreditCard: Codable {
var authLimit: Int?
var isCardSupported: Bool?
var paymentProcessorId: Int?
var mainDisplayText: String?
var imageUrl: String?
var userPaymentSourceId: Int?
var secondaryDisplayText: String?
var expDate: String?
var cardIssuer: String?
enum CodingKeys: String, CodingKey {
case cardIssuer = "cardIssuer"
case authLimit = "authLimit"
case isCardSupported = "isCardSupported"
case paymentProcessorId = "paymentProcessorId"
case mainDisplayText = "mainDisplayText"
case imageUrl = "imageUrl"
case userPaymentSourceId = "userPaymentSourceId"
case secondaryDisplayText = "secondaryDisplayText"
case expDate = "expDate"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
authLimit = try values.decodeIfPresent(Int.self, forKey: .authLimit)
isCardSupported = try values.decodeIfPresent(Bool.self, forKey: .isCardSupported)
paymentProcessorId = try values.decodeIfPresent(Int.self, forKey: .paymentProcessorId)
mainDisplayText = try values.decodeIfPresent(String.self, forKey: .mainDisplayText)
imageUrl = try values.decodeIfPresent(String.self, forKey: .imageUrl)
userPaymentSourceId = try values.decodeIfPresent(Int.self, forKey: .userPaymentSourceId)
secondaryDisplayText = try values.decodeIfPresent(String.self, forKey: .secondaryDisplayText)
expDate = try values.decodeIfPresent(String.self, forKey: .expDate)
cardIssuer = try values.decodeIfPresent(String.self, forKey: .cardIssuer)
}
}
You have not added the top level keys and that is why it is not working. Try this:
do {
if let jsonData = response?.responseData {
let jsonDecoder = JSONDecoder()
let creditCards = try jsonDecoder.decode(Wallet.self, from: jsonData)
print("credit cards \(creditCards)")
completion(nil, creditCards)
}
} catch {
print(error)
}
top level key wallets is missing in your Codable, and you don't need to implement
init(from decoder: Decoder) throws

How to parse this type of json format in swift 4 with decodable?

{
"type": "Success",
"message": "",
"fitting": {
"fitterID": "96ba096c-f0aa-11e7-a67a-76478bc72e4d",
"fitID": "09d399c0-7d74-4578-a138-5f4b02ba2e80",
"leftNotesJSON": "[{\"class\":\"FitNote\",\"text\":\"Saddle Down\",\"leftfoot\":false},{\"class\":\"FitNote\",\"text\":\"Saddle Down\",\"leftfoot\":false},{\"class\":\"FootBottomNote\",\"leftfoot\":false}]",
"rightNotesJSON": "[{\"s3Bucket\":\"8190ba10-d310-11e3-9c1a-0800200c9a66\",\"angle\":0,\"leftfoot\":false,\"shoulderAngle\":0,\"hipAngle\":0,\"s3Key\":\"FD0F5AE6-8193-4980-AD11-C42FEF064B8B\",\"class\":\"AngleNote\",\"kneeAngle\":0}]"
}
}
The json in your question is not valid. I'll be using the following json in my answer:
let json = """
{
"type": "Success",
"message": "",
"fitting": {
"fitterID": "96ba096c-f0aa-11e7-a67a-76478bc72e4d",
"fitID": "09d399c0-7d74-4578-a138-5f4b02ba2e80",
"leftNotesJSON": [{"class":"FitNote","text":"Saddle Down","leftfoot":false},{"class":"FitNote","text":"Saddle Down","leftfoot":false},{"class":"FootBottomNote","leftfoot":false}],
"rightNotesJSON": [{"s3Bucket":"8190ba10-d310-11e3-9c1a-0800200c9a66","angle":0,"leftfoot":false,"shoulderAngle":0,"hipAngle":0,"s3Key":"FD0F5AE6-8193-4980-AD11-C42FEF064B8B","class":"AngleNote","kneeAngle":0}]
}
}
"""
Let's define decodable structs:
struct Response: Codable {
let type, message: String
let fitting: Fitting
}
struct Fitting: Codable {
let fitterID, fitID: String
let leftNotesJSON: [LeftNotesJSON]
let rightNotesJSON: [RightNotesJSON]
}
struct LeftNotesJSON: Codable {
let leftNotesJSONClass: String
let text: String?
let leftfoot: Bool
//Define the coding keys since the json contains "class" as a key
enum CodingKeys: String, CodingKey {
case leftNotesJSONClass = "class"
case text, leftfoot
}
}
struct RightNotesJSON: Codable {
let s3Bucket: String
let angle: Int
let leftfoot: Bool
let shoulderAngle, hipAngle: Int
let s3Key, rightNotesJSONClass: String
let kneeAngle: Int
//Define the coding keys since the json contains "class" as a key
enum CodingKeys: String, CodingKey {
case s3Bucket, angle, leftfoot, shoulderAngle, hipAngle, s3Key
case rightNotesJSONClass = "class"
case kneeAngle
}
}
Let's get the data out of the json:
guard let data = json.data(using: .utf8) else {
fatalError("Couldn't get data from json")
}
And then decode it
do {
let response = try JSONDecoder().decode(Response.self, from: data)
//Here and now you can use the properties of the response
print(response.type)
print(response.message)
print(response.fitting.fitID)
print(response.fitting.fitterID)
print(response.fitting.leftNotesJSON.map {$0.leftfoot})
print(response.fitting.rightNotesJSON.map{$0.kneeAngle})
} catch {
print(error)
}
First, you may have to create models, which confirms to Codable Protocol. You can use any online tools for formatting JSON and creating models. In given code response Model will get parsed data.
import Foundation
struct Base : Codable {
let type : String?
let message : String?
let fitting : Fitting?
enum CodingKeys: String, CodingKey {
case type = "type"
case message = "message"
case fitting = "fitting"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
type = try values.decodeIfPresent(String.self, forKey: .type)
message = try values.decodeIfPresent(String.self, forKey: .message)
fitting = try values.decodeIfPresent(Fitting.self, forKey: .fitting)
}
}
import Foundation
struct Fitting : Codable {
let fitterID : String?
let fitID : String?
let leftNotesJSON : String?
let rightNotesJSON : String?
enum CodingKeys: String, CodingKey {
case fitterID = "fitterID"
case fitID = "fitID"
case leftNotesJSON = "leftNotesJSON"
case rightNotesJSON = "rightNotesJSON"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
fitterID = try values.decodeIfPresent(String.self, forKey: .fitterID)
fitID = try values.decodeIfPresent(String.self, forKey: .fitID)
leftNotesJSON = try values.decodeIfPresent(String.self, forKey: .leftNotesJSON)
rightNotesJSON = try values.decodeIfPresent(String.self, forKey: .rightNotesJSON)
}
}
let task = URLSession.shared.dataTask(with: <YOUR URL>) { (data, response, error) in
if let data = data {
let jsonDecoder = JSONDecoder()
let responseModel = try jsonDecoder.decode(Base.self, from: data)
}
}
task.resume()

Decodable multiple DateDecodingStrategies

Is there a possibility to add multiple JSONDecoder.DateDecodingStrategy to a same JSONDecoder?
I got a decodable:
struct Movie: Decodable {
enum CodingKeys: String, CodingKey {
case title = "display_title"
case mpaaRating = "mpaa_rating"
case criticsPick = "critics_pick"
case byline
case headline
case summaryShort = "summary_short"
case publicationDate = "publication_date"
case openingDate = "opening_date"
case dateUpdated = "date_updated"
case link
case multimedia
}
let title: String
let mpaaRating: String
let criticsPick: Int
let byline: String
let headline: String
let summaryShort: String
let publicationDate: Date
let openingDate: Date?
let updatedDate: Date
let link: MovieLink
let multimedia: MovieMultimedia
var image: UIImage?
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
try title = values.decode(String.self, forKey: .title)
try mpaaRating = values.decode(String.self, forKey: .mpaaRating)
try criticsPick = values.decode(Int.self, forKey: .criticsPick)
try byline = values.decode(String.self, forKey: .byline)
try headline = values.decode(String.self, forKey: .headline)
try summaryShort = values.decode(String.self, forKey: .summaryShort)
try openingDate = values.decodeIfPresent(Date.self, forKey: .openingDate)
try publicationDate = values.decode(Date.self, forKey: .publicationDate)
try updatedDate = values.decode(Date.self, forKey: .dateUpdated)
try link = values.decode(MovieLink.self, forKey: .link)
try multimedia = values.decode(MovieMultimedia.self, forKey: .multimedia)
}
mutating func loadImage(completion: #escaping (UIImage?, Error?) -> ()) {
URLSession.shared.dataTask(with: self.multimedia.src) { data, _, error in
DispatchQueue.main.async {
if let data = data {
let image = UIImage(data: data)
completion(image, error)
}
}
}.resume()
}
}
struct MovieLink: Decodable {
enum CodingKeys: String, CodingKey {
case type
case url
case suggestedLinkText = "suggested_link_text"
}
let type: String
let url: URL
let suggestedLinkText: String
}
struct MovieMultimedia: Decodable {
let type: String
let src: URL
let height: Int
let width: Int
}
Now I got a problem because the API delivers different date formats. For pulicaion_date, opening_date the following pattern is used 2017-10-10.
But for the date_updated they send the data formatted like 2017-10-10 12:21:02.
When i set the DateDecodingStrategy of the JSONDecoder to .formatted(myDateFormatter) it crashed for the date_updated
The decoder called this way
let decoder = JSONDecoder()
let dateformatter = DateFormatter()
dateformatter.dateFormat = "yyyy-MM-dd"
decoder.dateDecodingStrategy = .formatted(dateformatter)
let movieList = try! decoder.decode(MovieList.self, from: data!)
And crashes:
fatal error: 'try!' expression unexpectedly raised an error:
Swift.DecodingError.dataCorrupted(
Swift.DecodingError.Context(
codingPath: [
Test_App.MovieList.CodingKeys.movies,
Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0)),
Test_App.Movie.CodingKeys.dateUpdated
],
debugDescription: "Date string does not match format expected by formatter.",
underlyingError: nil)
)
You can use the DateDecodingStrategy.custom option for that. A short example:
let shortFormatter = DateFormatter()
let longFormatter = DateFormatter()
shortFormatter.dateFormat = "yyyy-MM-dd"
longFormatter.dateFormat = "yyyy-MM-dd hh:mm:ss"
func customDateFormatter(_ decoder: Decoder) throws -> Date {
let dateString = try decoder.singleValueContainer().decode(String.self)
let dateKey = decoder.codingPath.last as! Movie.CodingKeys
switch dateKey {
case .shortDate :
return shortFormatter.date(from: dateString)!
case .longDate :
return longFormatter.date(from: dateString)!
default:
fatalError("Unexpected date coding key: \(dateKey)")
}
}
I created both DateFormatter instances outside the function merely as an optimization. As such, each call won’t need to recreate/configure them for each decoded date.
Finally, set the dateDecodingStrategy using the function we created above:
let json =
"""
{
"name": "A Clockwork Orange",
"short_date": "2017-10-10",
"long_date": "2017-10-10 12:21:02"
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom(customDateFormatter)
let movie = try! decoder.decode(Movie.self, from: json)

Resources