create an array from JSON in swift - arrays

I am trying to convert JSON data into an array but I do not really have any idea how to do it.
I get the data and save it in strings and I can also show it on display.
struct User_Hosting: Codable {
let company_name: String
let website: String
let street: String
let housenumber: String
let zip: String
let city: String
enum CodingKeys: String, CodingKey {
case company_name = "company_name"
case website = "website"
case street = "street"
case housenumber = "housenumber"
case zip = "zip"
case city = "city"
}
}
And here some other codes:
let url = URL(string: "myURL.com")
URLSession.shared.dataTask(with: url!, completionHandler: { [weak self] (data, response, error) in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "An error occurred")
return
}
DispatchQueue.main.async {
self?.dataSource = try! JSONDecoder().decode([User_Hosting].self, from: data)
}
}).resume()
}

Your CodingKeys match the property names, so you can get rid of the enum at all
struct UserHosting: Codable {
let companyName: String
let website: String
let street: String
let housenumber: String
let zip: String
let city: String
}
Since you have a some snake case keys in JSON, you can change the JSONDecoder.keyDecodingStrategy to convertFromSnakeCase, like so
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
Above decoder will treat keys such as company_name to be assigned to companyName property of your struct.
Finally you can decode your JSON in a do-catch block, so in case of an error we will have a message as to what went wrong.
do {
self.dataSource = try decoder.decode([UserHosting].self, from: data)
} catch {
print("JSON Decoding Error \(error)")
}

Related

just one object nested in an array, how to decode the object?

I have this json
{
"status": [
{
"state": "checked",
"errorCode": "123",
"userId": "123456"
}
]
}
this is an array of statuses but is implemented badly because can be just one so I would like to decode just the status object
struct StatusResponse: Codable {
let state: String
let error: String
let id: String
enum CodingKeys: String, CodingKey {
case state = "state"
case error = "errorCode"
case id = "userId"
}
}
I try to custom decode it
let container = try decoder.container(keyedBy: ContainerKeys.self)
var statuses = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .status)
but as expected I get "Expected to decode Dictionary<String, Any> but found an array instead." how I can have access to the first object from statuses variable and decode it into StatusResponse? or some other idea on how to procede?
I would make a struct with field status to represent the top level object. That field is an array of StatusResponse:
struct TopLevelResponse: Codable {
var status: [StatusResponse]
}
when decoding the json:
let decoded = JSONDecoder().decode(TopLevelResponse.self, from: data)
let first = decoded.status.first! // Already decoded!
Unless it's guaranteed that there will be at least one item in the array then you should handle a nil case.
I will go with this solution inspired by this answer:
fileprivate struct RawStatusResponse: Decodable {
let status: [RawStatus]
struct RawStatus: Decodable {
let state: String
let errorCode: String
let userId: String
}
}
struct StatusResponse: Codable {
let state: String
let error: String
let id: String
enum CodingKeys: String, CodingKey {
case state = "state"
case error = "errorCode"
case id = "userId"
}
public init(from decoder: Decoder) throws {
let raw = try RawStatusResponse(from: decoder)
state = raw.status.first!.state
error = raw.status.first!.errorCode
id = raw.status.first!.userId
}
}
then when decode it just decode the actual object:
let state = try JSONDecoder().decode(StatusResponse, from: json)
You could decode it as a dictionary and use flatMap to get the array
let status = try JSONDecoder().decode([String: [StatusResponse]].self, from: data).flatMap(\.value)

How to parse this JSON format in swift

I have this JSON format:
{
"version":"7.0.19",
"fields": ["ID","pm","age","pm_0","pm_1","pm_2","pm_3","pm_4","pm_5","pm_6","conf","pm1","pm_10","p1","p2","p3","p4","p5","p6","Humidity","Temperature","Pressure","Elevation","Type","Label","Lat","Lon","Icon","isOwner","Flags","Voc","Ozone1","Adc","CH"],
"data":[[20,0.0,1,0.0,0.0,0.0,0.0,0.0,0.0,0.0,97,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,null,null,null,1413,0,"Oakdale",40.603077,-111.83612,0,0,0,null,null,0.01,1]],
"count":11880
}
but I cannot work out how to use a Codable protocol to parse the json response.
this would be my desired model.
struct Point: Codable {
let pm2: String?
let latitude, longitude: Double?
let temp: String?
let iD: String?
enum CodingKeys: String, CodingKey {
case pm2 = "pm", temp = "Temperature", iD = "ID", latitude = "Lat", longitude = "Lon"
}
}
Here is a URL to the json
https://webbfoot.com/dataa.json
You can use Codable to parse this:
struct Response: Decodable {
let version: String
let fields: [String]
let data: [[QuantumValue?]]
let count: Int
}
enter code here
enum QuantumValue: Decodable {
case float(Float), string(String)
init(from decoder: Decoder) throws {
if let int = try? decoder.singleValueContainer().decode(Float.self) {
self = .float(float)
return
}
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
throw QuantumError.missingValue
}
enum QuantumError:Error {
case missingValue
}
}
QuantumValue will handle both Float and String and ? will handle the null part.
This one is tricky and requires manual decoding. The principle would be to define a mapping between the fields you expect to decode and the properties of your object, then depending on the type of the property (String, Double, etc...), attempt to decode the data.
Second, since you have an array of points, you need some kind of container object to hold the array, for example:
struct Points {
var data: [Point] = []
}
First, some of your model properties don't match the type in the data, e.g. iD is a String, but the data has an Int. For simplicity, I'll redefine your model to match the data
struct Point {
var pm2: Int? = nil
var latitude: Double? = nil
var longitude: Double? = nil
var temp: Int? = nil
var iD: Int? = nil
}
Now, write the manual decoder for the parent container Points:
extension Points: Decodable {
static let mapping: [String: PartialKeyPath<Point>] = [
"pm": \Point.pm2,
"Lat": \Point.latitude,
"Lon": \Point.longitude,
"Temperature": \Point.temp,
"ID": \Point.iD
]
enum CodingKeys: CodingKey { case fields, data }
private struct Dummy: Decodable {} // see below why
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let fields = try container.decode([String].self, forKey: .fields)
var data = try container.nestedUnkeyedContainer(forKey: .data)
while !data.isAtEnd {
var row = try data.nestedUnkeyedContainer()
var point = Point()
for field in fields {
let keyPath = Points.mapping[field]
switch keyPath {
case let kp as WritableKeyPath<Point, String?>:
point[keyPath: kp] = try row.decodeIfPresent(String.self)
case let kp as WritableKeyPath<Point, Int?>:
point[keyPath: kp] = try row.decodeIfPresent(Int.self)
case let kp as WritableKeyPath<Point, Double?>:
point[keyPath: kp] = try row.decodeIfPresent(Double.self)
default:
// this is a hack to skip this value
let _ = try? row.decode(Dummy.self)
}
}
self.data.append(point)
}
}
}
Once you have that, you can decode the JSON like so:
let points = try JSONDecoder().decode(Points.self, from: jsonData)
let firstPoint = points.data[0]

Use JSONDecoder in swift 4

I am using swift 4 xcode 9.2, I got the below error when using JSONDecoder.
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [],
debugDescription: "Expected to decode Array but found a dictionary instead.",
underlyingError: nil))
class Hits: Codable {
let hits: [Hit]
init(hits: [Hit]) {
self.hits = hits
}
}
class Hit: Codable {
let recipe: String
let uri: String
let label: String
let image: String
let source: String
let url: String
let shareAs: String
let yield: String
init(recipe: String, uri: String, label: String, image: String, source: String, url: String, shareAs: String, yield: String) {
self.recipe = recipe
self.uri = uri
self.label = label
self.image = image
self.source = source
self.url = url
self.shareAs = shareAs
self.yield = yield
}
}
func downloadJSON() {
guard let downloadURL = url else {return}
URLSession.shared.dataTask(with: downloadURL) { (data, urlResponse, error) in
guard let data = data, error == nil, urlResponse != nil else { print("Something is wrong"); return }
print("download completed")
do {
let decoder = JSONDecoder()
let foods = try decoder.decode([Hits].self, from: data)
print(foods)
} catch {
print(error)
}
}.resume()
}
Here is the JSON:
https://api.edamam.com/search?q=chicken&app_id=110d8671&app_key=3f01522208d512f592625dfcd163ff5c&from=0&to=10
The error clears states that you are trying to decode an array but the actual type is a dictionary (single object).
Replace
let foods = try decoder.decode([Hits].self, from: data)
with
let foods = try decoder.decode(Hits.self, from: data)
And your classes (actually structs are sufficient) are
struct Recipe : Decodable {
let uri : URL
let label : String
let image : URL
let source : String
let url : URL
let shareAs : URL
let yield : Double
let calories, totalWeight, totalTime : Double
}
struct Hits: Decodable {
let hits: [Hit]
}
struct Hit: Decodable {
let recipe: Recipe
}
hits = try decoder.decode(Hits.self from: data)

How to get only Array part inside of a json

Json:
{"photos":{"page":1,"pages":20,"perpage":100,"total":"1941","photo":[{"id":"40270314685","owner":"24843974#N00","secret":"46f5a82bd3","server":"785","farm":1,"title":"Raleigh C Easter 2018-30","ispublic":1,"isfriend":0,"isfamily":0},{"id":"39355840240","owner":"24843974#N00","secret":"eb23dc2e68","server":"816","farm":1,"title":"Raleigh C Easter 2018-31","ispublic":1,"isfriend":0,"isfamily":0},{"id":"40270318535","owner":"24843974#N00","secret":"97c6280b2f","server":"811","farm":1,"title":"Raleigh C Easter 2018-32","ispublic":1,"isfriend":0,"isfamily":0}]},"stat":"ok"}
I only want to get "photo":[{"id":"402703146... part as an array. This is my code
struct GetPhotosOfUser:Decodable{
let id: String
let secret: String
let server: String
let farm: String
enum TopLevelCodingKeys: String, CodingKey {
case photos
}
enum UserCodingKeys: String, CodingKey {
case photo
}
enum SecondCodingKeys: String, CodingKey {
case id
case secret
case server
case farm
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: TopLevelCodingKeys.self)
let userContainer = try container.nestedContainer(keyedBy: UserCodingKeys.self, forKey: .photos)
let userContainer1 = try userContainer.nestedContainer(keyedBy: SecondCodingKeys.self, forKey: .photo)
id = try userContainer1.decode(String.self, forKey: .id)
secret = try userContainer1.decode(String.self, forKey: .secret)
server = try userContainer1.decode(String.self, forKey: .server)
farm = try userContainer1.decode(String.self, forKey: .farm)
}
}
var getphotos = [GetPhotosOfUser]()
func downloadJSON(completed: #escaping () -> ()){
let url = URL(string: "https://www.flickr.com/services/rest/?method=flickr.people.getPhotos&api_key=<api-key>&user_id=24843974#N00&format=json&nojsoncallback=1")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil{
do{
self.getphotos = try JSONDecoder().decode([GetPhotosOfUser].self, from: data!)
DispatchQueue.main.async {
completed()
print("Json Baglandi")
print(self.getphotos.count)
}
}catch{
print("JSON Error")
print(error)
}
}
}.resume()
}
But it says ""Expected to decode Array but found a dictionary instead."
Could you please help me ?
You need to decode from the top level so your structs should be like this
struct TopLevel : Decodable {
let photos: SubLevel
}
struct SubLevel: Decodable {
let photo: [GetPhotosOfUser]
}
struct GetPhotosOfUser: Decodable{
let id: String
let secret: String
let server: String
let farm: Int //Obs! I changed the type here
}
and then when decoding you need to change your code to
let result = try JSONDecoder().decode(TopLevel.self, from: data!)
self.getphotos = result.photos.photo

typeMismatch Error While parsing JSON data in Swift 4.2

Type mismatch Error - expected to decode Array but found dictionary.
I am using local json file embedded in the code. Json file contains the information about packages. I need to pick values and show them accordingly into tableview controller. Please identify what am I doing wrong in codable model or in a code. JSON Parsing not properly handled.
File Format:
{
"packages": [
{
"name": "Platinum Maksi 6 GB",
"desc": "Zengin içerikli Platinum Maksi Paketi ile Turkcell Uygulamalarının keyfini sürün!",
"subscriptionType": "monthly",
"didUseBefore": true,
"benefits": [
"TV+",
"Fizy",
"BiP",
"lifebox",
"Platinum",
"Dergilik"
],
"price": 109.90,
"tariff": {
"data": "6144",
"talk": "2000",
"sms": "100"
},
"availableUntil": "1558131150"
}
]
}
Models:
Base Model
struct Base : Codable {
let packages : [Package]?
enum CodingKeys: String, CodingKey {
case packages = "packages"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
packages = try values.decodeIfPresent([Package].self, forKey: .packages)
}
2) Package Model:
struct Package : Codable {
let availableUntil : String?
let benefits : String?
let desc : String?
let didUseBefore : Bool?
let name : String?
let price : Int?
let subscriptionType : String?
let tariff : Tariff?
enum CodingKeys: String, CodingKey {
case availableUntil = "availableUntil"
case benefits = "benefits"
case desc = "desc"
case didUseBefore = "didUseBefore"
case name = "name"
case price = "price"
case subscriptionType = "subscriptionType"
case tariff = "tariff"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
availableUntil = try values.decodeIfPresent(String.self, forKey: .availableUntil)
benefits = try values.decodeIfPresent(String.self, forKey: .benefits)
desc = try values.decodeIfPresent(String.self, forKey: .desc)
didUseBefore = try values.decodeIfPresent(Bool.self, forKey: .didUseBefore)
name = try values.decodeIfPresent(String.self, forKey: .name)
price = try values.decodeIfPresent(Int.self, forKey: .price)
subscriptionType = try values.decodeIfPresent(String.self, forKey: .subscriptionType)
tariff = try Tariff(from: decoder)
}
}
Tariff Model:
struct Tariff : Codable {
let data : String?
let sms : String?
let talk : String?
enum CodingKeys: String, CodingKey {
case data = "data"
case sms = "sms"
case talk = "talk"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
data = try values.decodeIfPresent(String.self, forKey: .data)
sms = try values.decodeIfPresent(String.self, forKey: .sms)
talk = try values.decodeIfPresent(String.self, forKey: .talk)
}
}
My code:
var pack = [Package]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = Bundle.main.url(forResource: "packageList", withExtension: "json")!
do {
let data = try Data(contentsOf: url)
pack = try JSONDecoder().decode([Package].self, from: data)
print(pack)
} catch {
print(error)
}
You are decoding the wrong object. You have to decode always the root object of the JSON which is Base.
Get the Package array with base.packages
let base = try JSONDecoder().decode(Base.self, from: data)
pack = base.packages
Now you will get two other type mismatch errors, change the type of benefits to [String] and price to Double.
You can reduce your structs dramatically: Remove all CodingKeys and all initializers and declare all struct members as non-optional (remove the question marks).
Edit:
To decode availableUntil as Date declare it as Date
let availableUntil: Date
and add a custom date decoding strategy because the value is a string (It would be easier if the value was Int)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom{ decoder -> Date in
let container = try decoder.singleValueContainer()
let dateStr = try container.decode(String.self)
guard let interval = TimeInterval(dateStr) else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Date string cannot be converted to TimeInterval") }
return Date(timeIntervalSince1970: interval)
}
let data = try Data(contentsOf: url)
let base = try decoder.decode(Base.self, from: data)
pack = base.packages

Resources