In order to parse the JSON, I needed to use 3 structs.
struct AppleApi: Decodable {
let feed: Feed
}
struct Feed: Decodable {
let results: [Result]
}
struct Result: Decodable {
let artistName: String
let artWorkUrl: String
enum CodingKeys : String, CodingKey {
case artistName = "artistName"
case artWorkUrl = "artworkUrl100"
}
}
But when I try to populate the array with that parsed data I got that message:
Cannot convert value of type '[Result]' to expected argument type
'AppleApi'
This is my error message:
do {
let appData = try JSONDecoder().decode(AppleApi.self, from: jsonData)
print(appData.feed.results.count)
var dataApp = appData.feed.results
print(appData)
DispatchQueue.main.async {
self.feedReseult.append(dataApp)
self.myCollectionView.reloadData()
}
} catch let err {
print("Error",err)
}
And this is my array:
var feedReseult = [AppleApi]()
I probably need to reach to 3. struct to reach the array inside JSON in order to have same type of argument type. How can I do that?
Your declaration of feedReseult should be this,
var feedReseult = [Result]()
and append the dataApp as below,
DispatchQueue.main.async {
self.feedReseult.append(contentsOf: dataApp)
self.myCollectionView.reloadData()
}
Also i feel a typo, feedResult instead of feedReseult
It looks like your struct from Json is not put together correct, you should just need one struct per one JSON load. Could you give JSON sample please?
If you want to Decode Feed which is part of AppleAPI then you should create an object that is of type AppleApi.Feed and put results into that.
Hope this helps a little
Related
I have just started with Swift so please excuse if this is just a stupid question. I am working on my first App which should load a JSON from the web, parses and displays its content to a LazyVGrid. I have started with the apple tutorial which uses a local JSON and everything worked fine.
Now I have changed to the original nested JSON and URL and I can see the output on the console, but I simply do not know how to use it in my LazyVGrid now. I did a lot of research but all good tutorials end with the successful parsing. I ended up with:
func fetchData()
{
let url = URL(string: "https://myjsonurl")!
URLSession.shared.dataTask(with: url) {(json, response, error) in
guard let json = json else {
return
}
let welcome = try! JSONDecoder().decode(Welcome.self, from: json)
print(welcome)
print(welcome.data.data.results[0].title)
}.resume()
}
So as mentioned print() gives me the whole JSON or even specific values like the title. But how can I loop this to my LazyVGrid now?(Xcode is complaining that can not reach it or it is not identifiable because ID is only all the way down in the nested array structure...)
Do I have to create a new array first because the welcome thing is not available outside the fetchData() function? How should it look like to keep the whole JSON structure? I guess all of my experiments have been far too complicated.
I would highly appreciate if someone could give me a hint or an example.
Just in case you need the structure:
// MARK: - Welcome
struct Welcome: Codable {
var data: WelcomeData
}
// MARK: - WelcomeData
struct WelcomeData: Codable {
var success: Bool
var data: DataData
}
// MARK: - DataData
struct DataData: Codable {
var results: [Result]
}
// MARK: - Result
struct Result: Codable {
var id, title, alias, introtext: String
var fulltext, publishUp: String
enum CodingKeys: String, CodingKey {
case id, title, alias, introtext, fulltext
case publishUp = "publish_up"
}
}
I have a json response. I'm bit confused on how to deserialize it. I want to extract the Id and Name from the below json response and store them as key value pairs in a dictionary.
JSON Response
("[{\"attributes\":{\"type\":\"User\",\"url\":\"/services/data/v1.0/objects/User/01234\"},\"Id\":\"01234\",\"Name\":\"User1\",\"RecordTypeId\":\"1\"},{\"attributes\":{\"type\":\"User\",\"url\":\"/services/data/v1.0/objects/User/01235\"},\"Id\":\"01235\",\"Name\":\"User2\",\"RecordTypeId\":\"2\"},{\"attributes\":{\"type\":\"User\",\"url\":\"/services/data/v1.0/objects/User/01236\"},\"Id\":\"01236\",\"Name\":\"User3\",\"RecordTypeId\":\"3\"},{\"attributes\":{\"type\":\"User\",\"url\":\"/services/data/v1.0/objects/User/01237\"},\"Id\":\"01237\",\"Name\":\"User4\",\"RecordTypeId\":\"4\"}]")
I want to extract the id and name values from above json response and store them in a dictionary like below
dict = [01234:"User1", 01235:"User2", 01236:"User3", 01237:"User4"]
The server's response seems like an array, so you could deserialize it to something like [String].
Then you define a structure that corresponds to the json inside that array, something like this:
struct ResponseElement: Codable {
struct attributes : Codable {
let type, url: String
}
let Id, Name, RecordTypeId: String
}
After that you decode it using this: JSONDecoder().decode([ResponseElement].self, ...
Finally you go over every element in your JSON to fill the dictionary, which has the following type [Int:String]
I tested this on Playgrounds, the full code is:
import Foundation
let serverResponse: [String] = [
"[{\"attributes\":{\"type\":\"User\",\"url\":\"/services/data/v1.0/objects/User/01234\"},\"Id\":\"01234\",\"Name\":\"User1\",\"RecordTypeId\":\"1\"},{\"attributes\":{\"type\":\"User\",\"url\":\"/services/data/v1.0/objects/User/01235\"},\"Id\":\"01235\",\"Name\":\"User2\",\"RecordTypeId\":\"2\"},{\"attributes\":{\"type\":\"User\",\"url\":\"/services/data/v1.0/objects/User/01236\"},\"Id\":\"01236\",\"Name\":\"User3\",\"RecordTypeId\":\"3\"},{\"attributes\":{\"type\":\"User\",\"url\":\"/services/data/v1.0/objects/User/01237\"},\"Id\":\"01237\",\"Name\":\"User4\",\"RecordTypeId\":\"4\"}]"
]
struct ResponseElement: Codable {
struct attributes : Codable {
let type, url: String
}
let Id, Name, RecordTypeId: String
}
var dict:[Int:String] = [:]
if let serverResponseAsData = serverResponse[0].data(using: .utf8),
let response = try? JSONDecoder().decode([ResponseElement].self, from: serverResponseAsData) {
response.forEach { element in
if let idAsInt = Int(element.Id) {
dict[idAsInt] = element.Name
}
}
}
print(dict)
And this prints:
[1235: "User2", 1237: "User4", 1236: "User3", 1234: "User1"]
My app has a few different view controllers that receive JSON data from my web service and parses it into table views. This one isn't working.
Here is a sample of the JSON data I am trying to parse into a TableViewController
"content_4_4":{"Sku":"W-22","Qty":"1","Desc":"Panel","Condition":""},"content_4_5":{"Sku":"W-15","Qty":"1","Desc":"Desk 44\" long","Condition":""},"content_4_6":{"Sku":"W-18","Qty":"1","Desc":"End Return Panel","Condition":""},"content_4_7":{"Sku":"W-25","Qty":"1","Desc":"End Return Panel","Condition":""},"content_4_8":{"Sku":"W-19","Qty":"1","Desc":"Header w/lights, transformer","Condition":""}
Here is the codable struct I am using to model the data.
struct Components: Codable {
var result: [Component]
}
struct Component: Codable {
var Sku: String
var Qty: String
var Desc: String
var Condition: String
}
Here is how I am trying to parse the json into components
let decoder = JSONDecoder()
if let jsonUnits = try? decoder.decode(Components.self, from: data) {
UnitComponents = jsonUnits.result
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
Nothing is showing up in my reusable cell. Since I can see the data I'm sure I'm parsing it wrong or something in the data could be breaking the parser. Perhaps the forward slashes or the presence of the titles ex. "content_4_4" is breaking the parser. Unsure. Any help appreciated.
You need
var unitComponents = [Component]()
do {
let jsonUnits = try JSONDecoder().decode([String:Component].self, from: data)
unitComponents = Array(jsonUnits.values)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
catch {
print(error)
}
struct Component: Codable {
let sku, qty, desc, condition: String
enum CodingKeys: String, CodingKey {
case sku = "Sku"
case qty = "Qty"
case desc = "Desc"
case condition = "Condition"
}
}
I am using Xcode 10.1 and Swift 4.2. When i try to convert JSON response into Codable class it gives an error that Expected to decode Array<Any> but found a string/data instead.
My Actual JSON response is Like this from API .
{
"d": "[{\"Data\":{\"mcustomer\":[{\"slno\":1000000040.0,\"fstname\":null}]},\"Status\":true}]"
}
My Model is Like this
class MainData: Codable{
var d: [SubData]
}
class SubData : Codable {
var Data : Customer
var Status : Bool?
}
class Customer : Codable {
var mcustomer : [Detail]
}
class Detail : Codable {
var slno : Double?
var fstname : String?
}
And I am Decode this Model using JSONDecoder()
let decoder = JSONDecoder()
let deco = try decoder.decode(MainData.self, from: data)
but, I am unable to Decode this Json into My Model.
Your API is wrong. You array in json shouldn't have quotation marks around it. Otherwise you're declaring that value for key "d" is string
"[...]"
[...]
Suggestions:
Variables and constants should start with small capital letter. Otherwise for example your Data property would cause confusion with Data type. For renaming it while decoding you can use CodingKeys
If you don't need to encode your model, you can just implement Decodable protocol
You can use struct instead of class for your model
The top-level JSON object is a dictionary with the key "d" and a string value, representing another JSON object (sometimes called "nested JSON"). If the server API cannot be changed then the decoding must be done in two steps:
Decode the top-level dictionary.
Decode the JSON object from the string obtained in step one.
Together with Robert's advice about naming, CodingKeys and using structs it would look like this:
struct MainData: Codable {
let d: String
}
struct SubData : Codable {
let data : Customer
let status : Bool
enum CodingKeys: String, CodingKey {
case data = "Data"
case status = "Status"
}
}
struct Customer : Codable {
let mcustomer : [Detail]
}
struct Detail : Codable {
let slno : Double
let fstname : String?
}
do {
let mainData = try JSONDecoder().decode(MainData.self, from: data)
let subData = try JSONDecoder().decode([SubData].self, from: Data(mainData.d.utf8))
print(subData)
} catch {
print(error)
}
For your solution to work, the JSON reponse has to be following format
let json = """
{
"d": [
{
"Data": {
"mcustomer": [
{
"slno": 1000000040,
"fstname": null
}
]
},
"Status": true
}
]
}
"""
But, as you can see, the JSON response you are getting is quite different than you are expecting. Either you need to ask to change the response or you need to change your model.
I'm trying to decode the following JSON Object
{
"result":[
{
"rank":12,
"user":{
"name":"bob","age":12
}
},
{
"1":[
{
"name":"bob","age":12
},
{
"name":"tim","age":13
},
{
"name":"tony","age":12
},
{
"name":"greg","age":13
}
]
}
]
}
struct userObject {
var name: String
var age: Int
}
Basically a JSON Array with two different object types
{ "rank":12, "user": {userObject} }
and a
"1" : array of [userObjects]
struct data: Decodable {
rank: Int
user: user
1: [user] <-- this is one area Im stuck
}
Thanks in advance
Just for fun:
First you need structs for the users and the representation of the first and second dictionary in the result array. The key "1" is mapped to one
struct User : Decodable {
let name : String
let age : Int
}
struct FirstDictionary : Decodable {
let rank : Int
let user : User
}
struct SecondDictionary : Decodable {
let one : [User]
private enum CodingKeys: String, CodingKey { case one = "1" }
}
Now comes the tricky part:
First get the root container.
Get the container for result as nestedUnkeyedContainer because the object is an array.
Decode the first dictionary and copy the values.
Decode the second dictionary and copy the values.
struct UserData: Decodable {
let rank : Int
let user : User
let oneUsers : [User]
private enum CodingKeys: String, CodingKey { case result }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var arrayContainer = try container.nestedUnkeyedContainer(forKey: .result)
let firstDictionary = try arrayContainer.decode(FirstDictionary.self)
rank = firstDictionary.rank
user = firstDictionary.user
let secondDictionary = try arrayContainer.decode(SecondDictionary.self)
oneUsers = secondDictionary.one
}
}
If this code is preferable over traditional manual JSONSerialization is another question.
If your JSON format is given then you are pretty much out of luck, since you will most likely have to parse your array as [Any] which is, to put it mildly, not very useful. If on the other hand you are able to modify the format of the JSON you should start from the other direction. Define your desired Swift object and encode it using JSONEncoder.encode(...) in order to quickly determine how your JSON should look like in order to make it parse in as typed a way as possible.
This approach will easily half your JSON handling code as your web service protocol will end up being structured much better. This will likely improve the structure of the overall system since it will yield a much more stable communication protocol.
Sadly enough this approach is not always possible which is when things get messy. Given your example you will be able to parse your code as
let st = """
{
"result":[
{
"rank":12,
"user":{
"name":"bob",
"age":12
}
},
{
"1":[
{
"name":"bob","age":12
},
{
"name":"tim","age":13
},
{
"name":"tony","age":12
},
{
"name":"greg","age":13
}
]
}
]
}
"""
let jsonData1 = st.data(using: .utf8)!
let arbitrary = try JSONSerialization.jsonObject(with: jsonData1, options: .mutableContainers)
This will let you access your data with a bunch of casts as in
let dict = arbitrary as! NSDictionary
print(dict["result"])
you get the idea. not very useful as you would very much like to use the Codable protocol as in
struct ArrayRes : Codable {
let result : [[String:Any]]
}
let decoder1 = JSONDecoder()
do {
let addrRes = try decoder.decode(ArrayRes.self, from: jsonData1)
print(addrRes)
} catch {
print("error on decode: \(error.localizedDescription)")
}
Unfortunately this does not work since Any is not Codable for slightly obvious reasons.
I hope you are able to change your JSON protocol since the current one will be the root cause of lot of messy code.