Swift: Help For Json parsing codeable/decodable - swift

I am new to iOS, and want to parse the JSON using Decodable but cant get through this, how should I work this out?
The view controller where I am trying to parse the data
class ViewController: UIViewController {
var servers = [Server]()
let apiUrl = "https://someurl/api/dashboard"
override func viewDidLoad() {
super.viewDidLoad()
guard let url = URL(string: self.apiUrl) else { return }
getDataFrom(url)
}
fileprivate func getDataFrom(_ url: URL) {
URLSession.shared.dataTask(with: url){ (data, response, error) in
guard let data = data else { return }
do {
let apiResponse = try JSONDecoder().decode(Server.self, from: data)
print(apiResponse)
} catch let jsonError {
print(jsonError)
}
}.resume()
}
}
The Server.swift file where I am confirming to the decodable protocol
struct Server: Decodable {
let current_page: Int
let data: [ServerData]
let first_page_url: String
}
struct ServerData: Decodable {
let hostname: String
let ipaddress: String
let customer: [Customer]
let latest_value: [LatestValue]
}
struct Customer: Decodable {
let name: String
let contact_person :String
let email: String
}
struct LatestValue: Decodable {
let systemuptime: String
let memtotal: Float
let memfree: Double
let loadaverage: Float
}
No value associated with key CodingKeys I get this error,
The response from the server
{
"servers": {
"current_page": 1,
"data": [
{
"hostname": "johndoes",
"ipaddress": "10.0.2.99",
"id": 7,
"latest_value_id": 1130238,
"customers": [
{
"name": "Jane Doe",
"contact_person": "John Doe",
"id": 2,
"email": "john.#example.com",
"pivot": {
"server_id": 7,
"customer_id": 2
}
}
],
"latest_value": {
"id": 1130238,
"server_id": 7,
"systemuptime": "80days:10hours:23minutes",
"memtotal": 3.7,
"memfree": 1.6400000000000001,
"loadaverage": 2.25,
"disktotal": {
"dev-mapper-centos-root_disktotal": "38",
"dev-mapper-proquote-xfs-lvm_disktotal": "200"
},
"diskused": "{\"dev-mapper-centos-root_diskused\":\"16\",\"dev-mapper-proquote-xfs-lvm_diskused\":\"188\"}",
"custom_field": "[]",
"additional_attributes": {
"fathom": {
"name": "fathom",
"status": 1
},
"trenddb": {
"name": "trenddb",
"status": 1
},
"trendwi": {
"name": "trendwi",
"status": 1
},
"appsrv": {
"name": "appsrv",
"status": 1
}
},
"created_at": "2019-06-15 02:25:02",
"updated_at": "2019-06-15 02:25:02"
}
}
]
},
"message": "Success"
}

You seem to have few different errors in your data structure.
First of all, you are trying to decode Server while your json has servers inside a dict {"servers": ... }, So use a parent root object for it.
Your latest_value inside ServerData is defined as array, while it should be LatestValue struct not [LatestValue].
There is no first_page_url element in your json, but your Server struct has the property, make it optional, so that JSONDecoder decodes it only if it is present.
Here is your refined data models.
struct Response: Decodable {
let servers: Server
}
struct Server: Decodable {
let current_page: Int
let data: [ServerData]
let first_page_url: String?
}
struct ServerData: Decodable {
let hostname: String
let ipaddress: String
let customers: [Customer]
let latest_value: LatestValue
}
struct Customer: Decodable {
let name: String
let contact_person :String
let email: String
}
struct LatestValue: Decodable {
let systemuptime: String
let memtotal: Float
let memfree: Double
let loadaverage: Float
}
And decode Response instead of decoding Server, like so,
do {
let apiResponse = try JSONDecoder().decode(Response.self, from: data)
let server = apiResponse.server // Here is your server struct.
print(server)
} catch let jsonError {
print(jsonError)
}

Related

How do I make a struct with nested json?

I have a JSON response from my api that returns this:
[
{
"id": 1,
"chapter": 5,
"amount": 28,
"texts": [
{
"lyric": "lorem ipsum",
"number": 1
},
{
"lyric": "lorem foo bar",
"number": 2
}
],
"book": 1
}
]
I tried
struct Chapter: Decodable, Identifiable {
var id: Int
var chapter: Int
var amount: Int
struct Lyrics: Codable {
var lyricText: String
var lyricNumber: Int
}
enum Codingkeys: String, CodingKey {
case lyricText = "lyric"
case lyricNumber = "number"
}
}
But I get the following error upon making the call
dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.})))
My API call looks like this:
...
#Published var chapters = [Chapter]()
func fetchBookDetails() {
if let url = URL(string: url) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error == nil {
if let safeData = data {
do {
let response = try JSONDecoder().decode([Chapter].self, from: safeData)
DispatchQueue.main.async {
self.chapters = response
}
} catch {
print(error)
}
}
}
}
task.resume()
}
}
The struct looks fine I guess, but the api call is complaining - any idea what it could be? Or is it the struct that is done incorrectly
texts is a sub structure (an array of properties), so you need to define a second container for it, for example
struct Text: Codable {
let lyric: String
let number: Int
}
Then you can update Chapter to reference the sub structure something like...
struct Chapter: Decodable {
let id: Int
let chapter: Int
let amount: Int
let book: Int
let texts: [Text]
}
And finally, load it...
let chapters = try JSONDecoder().decode([Chapter].self, from: jsonData)
But what about the error message?
dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.})))
Oh, right, but the error message is telling there is something wrong with what you've downloaded. I like to, in these cases, convert the data to String and print it if possible, that way, you know what is been returned to you.
For example:
let actualText = String(data: safeData, encoding: .utf8)
The print this and see what you're actually getting
The Playground test code
import UIKit
let jsonText = """
[
{
"id": 1,
"chapter": 5,
"amount": 28,
"texts": [
{
"lyric": "lorem ipsum",
"number": 1
},
{
"lyric": "lorem foo bar",
"number": 2
},
],
"book": 1
}
]
"""
struct Text: Codable {
let lyric: String
let number: Int
}
struct Chapter: Decodable {
let id: Int
let chapter: Int
let amount: Int
let book: Int
let texts: [Text]
}
let jsonData = jsonText.data(using: .utf8)!
do {
let chapters = try JSONDecoder().decode([Chapter].self, from: jsonData)
} catch let error {
error
}

Getting key from Dictionary into neasted Decodable model

Let's say we have a JSON like that:
{
"id1": {
"name": "hello"
},
"id2": {
"name": "world"
}
}
A model:
struct Model: Decodable {
var id: String
var name: String
}
How is it possible to make an array of Model from the JSON above?
You could do something like this
let data = """
{
"id1": {
"name": "hello"
},
"id2": {
"name": "world"
}
}
""".data(using: .utf8)!
struct Name: Decodable {
let name: String
}
struct Model {
let id: String
let name: String
}
do {
let json = try JSONDecoder().decode([String: Name].self, from: data)
let result = json.map { Model(id: $0.key, name: $0.value.name) }
print(result)
} catch {
print(error)
}
We decode the data as [String, Name]. We could decode it as [String: [String:String]] but this will mean that we will have to handle optional values so it is easier to create a Name struct to handle that part.
Once we have the dictionary we map over it converting it into the model object, leaving an array of [Model]

Swift - Expected to decode Array<Any> but found a dictionary instead.”, underlyingError: nil

I'm trying to decode some json for my application and I usually do this.
My struct;
struct RequestTypes: Codable {
let MerchRequestTypeID: Int?
let TypeName: String?
let LayoutID: Int?
private enum CodingKeys: Int, CodingKey {
case MerchRequestTypeID
case TypeName
case LayoutID
}
}
And decoding;
func downloadRequestTypesJson(){
guard let gitUrl = URL(string: "URL") else { return }
URLSession.shared.dataTask(with: gitUrl) { (data, response
, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let RequestData = try decoder.decode(Array<RequestTypes>.self, from: data)
DispatchQueue.main.sync {
print(RequestData[0].MerchRequestTypeID)
print(RequestData[1].MerchRequestTypeID)
print(RequestData[2].MerchRequestTypeID)
}
} catch let err {
print("Err", err)
}
}.resume()
}
This works fine for below json;
[
{
"MerchRequestTypeID": 1,
"TypeName": "Stok",
"LayoutID": 1
},
{
"MerchRequestTypeID": 2,
"TypeName": "Stand",
"LayoutID": 2
},
{
"MerchRequestTypeID": 3,
"TypeName": "Eğitim",
"LayoutID": 2
}
]
But now I need to decode this json and im getting Expected to decode Array but found a dictionary instead. error;
{
"RequestTypes": [
{
"MerchRequestTypeID": 1,
"TypeName": "Stock",
"LayoutID": 1
},
{
"MerchRequestTypeID": 2,
"TypeName": "Stand",
"LayoutID": 2
},
{
"MerchRequestTypeID": 3,
"TypeName": "Education",
"LayoutID": 2
}
]
}
Couldn't be able to find proper way to do this. Any help is appreciated.
Edit: I am beginner on Swift. I want to know how to decode second json and how to reach its elements.
That's because you are trying to decode a JSON object with a "RequestTypes" property and not an array. One solution is to create a new struct for this data structure and use that to decode your JSON:
struct RequestTypesContainer: Codable {
let RequestTypes : [RequestTypes]
private enum CodingKeys: String, CodingKey {
case RequestTypes
}
}
And then:
let RequestData = try decoder.decode(RequestTypesContainer.self, from: data)

Deserialize JSON array based on nested type attribute

Consider this example JSON:
{
"sections": [{
"title": "Sign up",
"rows": [
{
"type": "image",
"imageURL": "https://example.com/image.jpg"
},
{
"type": "textField",
"value": "",
"placeholder": "Username"
},
{
"type": "textField",
"placeholder": "password"
},
{
"type": "textField",
"placeholder": "confirmPassword"
},
{
"type": "button",
"placeholder": "Register!"
}
]
}]
}
Let's say I wanted to parse the JSON above into the following models (I know it doesn't compile due to the Row protocol not corresponding to Decodable):
enum RowType: String, Codable {
case textField
case image
case button
}
protocol Row: Codable {
var type: RowType { get }
}
struct TextFieldRow: Row {
let type: RowType
let placeholder: String
let value: String
enum CodingKey: String {
case type
case placeholder
case value
}
}
struct ImageRow: Row {
let type: RowType
let imageURL: URL
enum CodingKey: String {
case type
case imageURL
}
}
struct ButtonRow: Row {
let type: RowType
let title: String
enum CodingKey: String {
case type
case title
}
}
struct Section: Codable {
let rows: [Row]
let title: String
enum CodingKey: String {
case rows
case title
}
}
struct Response: Codable {
let sections: [Section]
enum CodingKey: String {
case sections
}
}
// Parsing the response using the Foundation JSONDecoder
let data: Data // From network
let decoder = JSONDecoder()
do {
let response = try decoder.decode(Response.self, from: data)
} catch {
print("error: \(error)")
}
Is there a way to make the Swift code above Codable compliant?
I know you can manually solve this by first grabbing each Row's type string and then creating the right type of Row model as well as changing them from structs to classes and letting the Row protocol be a superclass instead. But is there a way that requires less manual labour?
Using an enum with associated value is the best option:
Consider this enum:
enum Row: Decodable {
case textField(TextFieldRow)
case image(ImageRow)
// and other cases
case unknown
enum CodingKeys: String, CodingKey {
case type
}
public init(from decoder: Decoder) throws {
do {
let selfContainer = try decoder.singleValueContainer()
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
let type = try typeContainer.decode(String.self, forKey: .type)
switch type {
case "textField": self = .textField( try selfContainer.decode(TextFieldRow.self) )
case "Image": self = .image( try selfContainer.decode(ImageRow.self) )
// and other cases
default: self = .unknown
}
}
}
}
With these changes:
struct TextFieldRow: Decodable {
let placeholder: String?
let value: String?
}
struct ImageRow: Decodable {
let imageURL: URL
}
// and so on
Now this will decode like a charm:
// Minmal testing JSON
let json = """
[
{
"type": "image",
"imageURL": "https://example.com/image.jpg"
},
{
"type": "textField",
"value": "",
"placeholder": "Username"
},
{
"type": "textField",
"placeholder": "password"
}
]
""".data(using: .utf8)!
let decoder = JSONDecoder()
print( try! decoder.decode([Row].self, from: json) )
You can now add any other case you need to the decoder to build your application builder app.

Swift 4 decoding json using Codable

Can someone tell me what I'm doing wrong? I've looked at all the questions on here like from here How to decode a nested JSON struct with Swift Decodable protocol? and I've found one that seems exactly what I need Swift 4 Codable decoding json.
{
"success": true,
"message": "got the locations!",
"data": {
"LocationList": [
{
"LocID": 1,
"LocName": "Downtown"
},
{
"LocID": 2,
"LocName": "Uptown"
},
{
"LocID": 3,
"LocName": "Midtown"
}
]
}
}
struct Location: Codable {
var data: [LocationList]
}
struct LocationList: Codable {
var LocID: Int!
var LocName: String!
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "/getlocationlist")
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
guard error == nil else {
print(error!)
return
}
guard let data = data else {
print("Data is empty")
return
}
do {
let locList = try JSONDecoder().decode(Location.self, from: data)
print(locList)
} catch let error {
print(error)
}
}
task.resume()
}
The error I am getting is:
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath:
[], debugDescription: "Expected to decode Array but found a
dictionary instead.", underlyingError: nil))
Check the outlined structure of your JSON text:
{
"success": true,
"message": "got the locations!",
"data": {
...
}
}
The value for "data" is a JSON object {...}, it is not an array.
And the structure of the object:
{
"LocationList": [
...
]
}
The object has a single entry "LocationList": [...] and its value is an array [...].
You may need one more struct:
struct Location: Codable {
var data: LocationData
}
struct LocationData: Codable {
var LocationList: [LocationItem]
}
struct LocationItem: Codable {
var LocID: Int!
var LocName: String!
}
For testing...
var jsonText = """
{
"success": true,
"message": "got the locations!",
"data": {
"LocationList": [
{
"LocID": 1,
"LocName": "Downtown"
},
{
"LocID": 2,
"LocName": "Uptown"
},
{
"LocID": 3,
"LocName": "Midtown"
}
]
}
}
"""
let data = jsonText.data(using: .utf8)!
do {
let locList = try JSONDecoder().decode(Location.self, from: data)
print(locList)
} catch let error {
print(error)
}
After searching lots of thing internet, I certainly figured out this is the sweetest way to print well formatted json from any object.
let jsonString = object.toJSONString(prettyPrint: true)
print(jsonString as AnyObject)
Apple documentation about JSONEncoder ->
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
let pear = GroceryProduct(name: "Pear", points: 250, description: "A ripe pear.")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(pear)
print(String(data: data, encoding: .utf8)!)
/* Prints:
{
"name" : "Pear",
"points" : 250,
"description" : "A ripe pear."
}
*/