Parse JSON with JSONDecoder in Swift 5 using structs - ios

I get from a Rest API a result in json format. Now I wanna parse this JSON with the JSONDecoder but I don't really understand the structure of my response.
For that I already tried to create structs to get the "name" of "FirstUser".
{
"User":[
{
"FirstUser":{
"name":"John"
},
"Information":"XY",
"SecondUser":{
"name":"Tom"
}

Json
{
"User":[
{
"FirstUser":{
"name":"John"
},
"Information":"XY",
"SecondUser":{
"name":"Tom"
}
}
]
}
Model
// MARK: - Empty
struct Root: Codable {
let user: [User]
enum CodingKeys: String, CodingKey {
case user = "User"
}
}
// MARK: - User
struct User: Codable {
let firstUser: FirstUserClass
let information: String
let secondUser: FirstUserClass
enum CodingKeys: String, CodingKey {
case firstUser = "FirstUser"
case information = "Information"
case secondUser = "SecondUser"
}
}
// MARK: - FirstUserClass
struct FirstUserClass: Codable {
let name: String
}
Parse
do {
let res = try JSONDecoder().decode(Root.self, from: data)
print(res.first?.firstUser.name)
} catch {
print(error)
}

If I create model using previous json
Using this link [blog]: http://www.jsoncafe.com to generate Codable structure or Any Format
Model
import Foundation
struct RootClass : Codable {
let user : [Users]?
enum CodingKeys: String, CodingKey {
case user = "User"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
user = try? values?.decodeIfPresent([Users].self, forKey: .user)
}
}
struct Users : Codable {
let firstUser : FirstUser?
let information : String?
let secondUser : SecondUser?
enum CodingKeys: String, CodingKey {
case firstUser = "FirstUser"
case information = "Information"
case secondUser = "SecondUser"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
firstUser = try? FirstUser(from: decoder)
information = try? values?.decodeIfPresent(String.self, forKey: .information)
secondUser = try? SecondUser(from: decoder)
}
}
struct SecondUser : Codable {
let name : String?
enum CodingKeys: String, CodingKey {
case name = "name"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
name = try? values?.decodeIfPresent(String.self, forKey: .name)
}
}
struct FirstUser : Codable {
let name : String?
enum CodingKeys: String, CodingKey {
case name = "name"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
name = try? values?.decodeIfPresent(String.self, forKey: .name)
}
}
Parse
do {
let res = try JSONDecoder().decode(RootClass.self, from: data)
print(res?.user?.first?.firstUser?.name ?? "Yours optional value")
} catch {
print(error)
}

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)
}

Swift: getting error while decoding using CodingKey

I have created a model and I need to try to add value to one of the CodingKey's case
struct Model {
let name: String
let location: String
let descriptions: String
}
enum CodingKeys: String, CodingKey {
case name = "NAME"
case location = "LOCATION"
case descriptions = "INFO-\(NSLocalizedString("Language", comment: ""))"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
.
.
descriptions = try container.decode(String.self, forKey: .descriptions)
}
Now compiler gives me this error
🛑 Raw value for enum case must be a literal
How can I fix this error?
You don't have to use an enum for CodingKeys. You can also use a struct with static lets. It's just a bit more boilerplate:
struct Model: Decodable {
let name: String
let location: String
let descriptions: String
struct CodingKeys: CodingKey {
let intValue: Int? = nil
let stringValue: String
init(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
return nil
}
static let name = CodingKeys(stringValue: "name")
static let location = CodingKeys(stringValue: "location")
static let description = CodingKeys(stringValue: "INFO-\(NSLocalizedString("Language", comment: ""))")
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
location = try container.decode(String.self, forKey: .location)
descriptions = try container.decode(String.self, forKey: .description)
}
}

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)
}
}

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()

Swift decodable propagate userInfo

I have the following models:
struct Shop: Decodable {
let id: Int
let name: String
let products: [Product]
private enum CodingKeys: String, CodingKey {
case id
case name
case products
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)
self.products = try container.decode([Product].self, forKey: .products)
}
}
struct Product: Decodable {
let id: Int
let title: String
let productImageURL: String
private(set) var productDetails: ProductDetails
private enum CodingKeys: String, CodingKey {
case id
case title
case productImageList
case productImageDetails
case productDetails
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.title = try container.decode(String.self, forKey: .title)
self.productDetails = try container.decode(ProductDetails.self, forKey: .productDetails)
guard let serializationType = decoder.userInfo[.serializationType] as? SerializationGenericType else {
throw NetworkError.modelParsingError(message: "Product model missing serialization type")
}
switch serializationType {
case .list:
self.productImageURL = try container.decode(String.self, forKey: .productImageList)
case .details:
self.productImageURL = try container.decode(String.self, forKey: .productImageList)
self.productDetails = try container.decode(ProductDetails.self, forKey: .productDetails)
}
}
}
Of course, the models are much more complex, but this is enough to explain my problem :)
Also, please don't mind the coding keys and the image URL, it's just to show that for different serialization type it initialized differently.
When getting the product details I have:
let decoder = JSONDecoder()
decoder.userInfo[.serializationType] = SerializationGenericType.details
let product = try decoder.decode(Product.self, from: data)
But when I try to pass the serialization type when initializing the Shop in the user info of the JsonDecoder like this:
let decoder = JSONDecoder()
decoder.userInfo[.serializationType] = SerializationGenericType.list
let shop = try decoder.decode(Shop.self, from: data)
In the Product initializer the user info is empty.
My question is how to propagate the serialization type from the Shop to the Product so I can properly decode it using the correct serialization type?

Resources