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.
2
Answers
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:then create one for your error state and one for your success state:
Then you can decode the data:
Remarks:
init
s are not necessary.try?
this will hide all errors from youdo catch
block or make your functionthrowing
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 theresult
dictionary orerrorCode
anderror
In
ShortLinkData
theinit
method and theCodingKeys
are redundant if you specify theconvertFromSnakeCase
key decoding strategy