Getting an error decoding JSON, Swift 4.2 - ios

I am getting an error when decoding JSON in swift 4.2
Expected to decode Array but found a dictionary instead.
My JSON Model:
public struct NewsSource: Equatable, Decodable {
public let id: String?
public let name: String?
public let sourceDescription: String?
public let url: URL?
enum CodingKeys: String, CodingKey {
case id
case name
case sourceDescription = "description"
case url
}
public init(id: String,
name: String,
sourceDescription: String,
url: URL,
category: NewsCategory,
language: NewsLanguage,
country: NewsCountry) {
self.id = id
self.name = name
self.sourceDescription = sourceDescription
self.url = url
} }
How I fetch the JSON:
func fetchJSON() {
let urlString = "https://newsapi.org/v2/sources?apiKey=myAPIKey"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, err) in
DispatchQueue.main.async {
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
print(data)
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
self.Sources = try decoder.decode([NewsSource].self, from: data)
self.tableView.reloadData()
} catch let jsonErr {
print("Failed to decode:", jsonErr)
}
}
}.resume()
}

If you look at the JSON that is being returned it looks like this:
{
"status": "ok",
"sources": [{
"id": "abc-news",
"name": "ABC News",
"description": "Your trusted source for breaking news, analysis, exclusive interviews, headlines, and videos at ABCNews.com.",
"url": "https://abcnews.go.com",
"category": "general",
"language": "en",
"country": "us"
}, {
"id": "abc-news-au",
"name": "ABC News (AU)",
"description": "Australia's most trusted source of local, national and world news. Comprehensive, independent, in-depth analysis, the latest business, sport, weather and more.",
"url": "http://www.abc.net.au/news",
"category": "general",
"language": "en",
"country": "au"
},
...
While there is an array of sources, the array is not the root. The root of the JSON is an object with a status string and and a sources array. This is why the decoder is failing.
You need to define an additional struct to handle this:
struct NewsResult {
let status: String
let sources: [NewsSource]
}
Then you decode this object:
let sourceResult = try decoder.decode(NewsResult.self, from: data)
self.sources = sourceResult.sources

This should be your structure:
struct NewsSource: Codable {
let status: String
let sources: [NewsSource]
}
public struct NewsSource: Equatable, Decodable {
public let id: String?
public let name: String?
public let sourceDescription: String?
public let url: URL?
enum CodingKeys: String, CodingKey {
case id
case name
case sourceDescription = "description"
case url
}
public init(id: String,
name: String,
sourceDescription: String,
url: URL,
category: NewsCategory,
language: NewsLanguage,
country: NewsCountry) {
self.id = id
self.name = name
self.sourceDescription = sourceDescription
self.url = url
} }
struct Source: Codable {
let id, name, description: String
let url: String
let category: Category
let language, country: String
}
enum Category: String, Codable {
case business = "business"
case entertainment = "entertainment"
case general = "general"
case health = "health"
case science = "science"
case sports = "sports"
case technology = "technology"
}
And then to decode it:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let newsSource = try? decoder.decode(NewsSource.self, from: data)
self.Sources = newsSource.sources
self.tableView.reloadData()
Hope this helps!

Related

Swift: Decode JSON response and store nested JSON as String or JSON

Given the following JSON from a network request; If you wanted to decode this into a Swift object that coforms to Codable, but you wanted to retain the nested JSON that is the value for the key configuration_payload, how could you do it?
{
"registration": {
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
}
Using the following Swift struct, I want to be able to grab the configuration_payload as a String.
public struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id, deviceType: String
public let state: State
public let error: String?
public let thingUUID: Int?
public let discoveryTimeout, installationTimeout: Int
public let configurationPayload: String?
}
As far as I can tell, the JSONDecoder in Swift, sees the value for configuration_payload as nested JSON and wants to decode it into it's own object. To add to confusion, configuration_payload is not always going to return the same JSON structure, it will vary, so I can not create a Swift struct that I can expect and simply JSON encode it again when needed. I need to be able to store the value as a String to account for variations in the JSON under the configuration_payload key.
As others have already said, you cannot just keep a part without decoding. However, decoding unknown data is trivial:
enum RawJsonValue {
case boolean(Bool)
case number(Double)
case string(String)
case array([RawJsonValue?])
case object([String: RawJsonValue])
}
extension RawJsonValue: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let boolValue = try? container.decode(Bool.self) {
self = .boolean(boolValue)
} else if let numberValue = try? container.decode(Double.self) {
self = .number(numberValue)
} else if let stringValue = try? container.decode(String.self) {
self = .string(stringValue)
} else if let arrayValue = try? container.decode([RawJsonValue?].self) {
self = .array(arrayValue)
} else {
let objectValue = try container.decode([String: RawJsonValue].self)
self = .object(objectValue)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .boolean(let boolValue):
try container.encode(boolValue)
case .number(let numberValue):
try container.encode(numberValue)
case .string(let stringValue):
try container.encode(stringValue)
case .array(let arrayValue):
try container.encode(arrayValue)
case .object(let objectValue):
try container.encode(objectValue)
}
}
}
Now we can safely decode and convert to JSON string if needed:
struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
let id, deviceType: String
let state: State
let error: String?
let thingUUID: Int?
let discoveryTimeout, installationTimeout: Int
let configurationPayload: RawJsonValue?
}
let jsonData = """
{
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let registration = try! decoder.decode(Registration.self, from: jsonData)
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let payloadString = String(data: try! encoder.encode(registration.configurationPayload), encoding: .utf8)!
print(payloadString) // {"title":"Some Title","views":9999,"url":"https:\/\/www.someurl.com\/","category":"test"}
The only problem I can see is potential loss of precision when decoding decimal numbers, which is a known problem with Foundation JSON decoder.
Also, some null values could be also removed. This could be fixed by decoding object manually by iterating keys and having a special null type.
You can achieve decoding of a JSON object to [String: Any] by using a third party library like AnyCodable.
Your Registration struct will look like this:
public struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id, deviceType: String
public let state: State
public let error: String?
public let thingUUID: Int?
public let discoveryTimeout, installationTimeout: Int
public let configurationPayload: [String: AnyCodable]?
}
and then you can convert [String: AnyCodable] type to [String: Any] or even to String:
let jsonString = """
{
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
"""
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let registration = try decoder.decode(Registration.self, from: Data(jsonString.utf8))
// to [String: Any]
let dictionary = registration.configurationPayload?.mapValues { $0.value }
// to String
if let configurationPayload = registration.configurationPayload {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let data = try encoder.encode(configurationPayload)
let string = String(decoding: data, as: UTF8.self)
print(string)
}
} catch {
print(error)
}
One (more limited than you probably want) way would be to make sure that Value part in configuration_payload JSON is a known Codable single type (String) instead of Any which can produce multiple types (String, Int, Double etc.).
I was trying to make it work with [String: Any] for the configuration_payload, the problem is Any does NOT conform to Codable.
Then I tried with [String: String] for configuration_payload and was able to make it work like following.
public struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id, deviceType: String
public let state: State
public let thingUUID: Int?
public let discoveryTimeout, installationTimeout: Int
public let configurationPayload: [String: String]? // NOT [String: Any]?
enum CodingKeys: String, CodingKey {
case id = "id"
case deviceType = "device_type"
case state = "state"
case thingUUID = "thing_uuid"
case discoveryTimeout = "discovery_timeout"
case installationTimeout = "installation_timeout"
case configurationPayload = "configuration_payload"
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(String.self, forKey: .id) ?? ""
deviceType = try values.decodeIfPresent(String.self, forKey: .deviceType) ?? ""
let stateRaw = try values.decodeIfPresent(String.self, forKey: .state) ?? ""
state = Registration.State(rawValue: stateRaw) ?? .provisioning
thingUUID = try values.decodeIfPresent(Int.self, forKey: .thingUUID)
discoveryTimeout = try values.decodeIfPresent(Int.self, forKey: .discoveryTimeout) ?? 0
installationTimeout = try values.decodeIfPresent(Int.self, forKey: .installationTimeout) ?? 0
configurationPayload = try values.decodeIfPresent([String: String].self, forKey: .configurationPayload)
}
}
Tests
let json = Data("""
{
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload": {
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": "9999"
}
}
""".utf8
)
let decoded = try JSONDecoder().decode(Registration.self, from: json)
print(decoded)
let encoded = try JSONEncoder().encode(decoded)
print(String(data: encoded, encoding: .utf8))
This is not possible with the Codable protocol, because you do not know the type before hand. You'll have to either write your own method or have a different decoding strategy.
let json = """
{
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload": {
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
""".data(using: .utf8)
do {
let decoded = try? Registration.init(jsonData: json!)
print(decoded)
}catch {
print(error)
}
public struct Registration {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id: String
public let device_type: String
public let state: State
public let error: String?
public let thing_uuid: Int?
public let discovery_timeout, installation_timeout: Int
public let configuration_payload: [String: Any]?
public init(jsonData: Data) throws {
let package = try JSONSerialization.jsonObject(with: jsonData, options: []) as! [String : Any]
id = package["id"] as! String
device_type = package["device_type"] as! String
state = State(rawValue: package["state"] as! String)!
error = package["error"] as? String
thing_uuid = package["thing_uuid"] as? Int
discovery_timeout = package["discovery_timeout"] as! Int
installation_timeout = package["installation_timeout"] as! Int
configuration_payload = package["configuration_payload"] as? [String: Any]
}
}
This is one possible way to handle the different types. You could also create a struct containing keys and loop through them, I think this illustrates the basic idea though.
Edit:
if let remaining = package["configuration_payload"] as? Data,
let data = try? JSONSerialization.data(withJSONObject: remaining, options: []) as Data,
let string = String(data: data, encoding: .utf8) {
// store your string if you want it in string formatt
print(string)
}
If you have a list of possible keys, using optionals is another way you could employ Codable. You can mix keys this way - only the ones that are available will attempt to be encoded/decoded
import UIKit
public struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id, deviceType: String
public let state: State
public let error: String?
public let thingUuid: Int?
public let discoveryTimeout, installationTimeout: Int
public var configurationPayload: ConfigurationPayload?
}
// nested json can be represented as a codable struct
public struct ConfigurationPayload: Codable {
let title: String?
let url: String?
let category: String?
let views: Int?
let nonTitle: String?
let anotherUrl: String?
let someCategory: String?
let someViews: Int?
// computed properties aren't part of the coding strategy
// TODO: avoid duplication in loop
var jsonString: String {
let mirror = Mirror(reflecting: self).children
let parameters = mirror.compactMap({$0.label})
let values = mirror.map({$0.value})
let keyValueDict = zip(parameters, values)
var returnString: String = "{\n"
for (key, value) in keyValueDict {
if let value = value as? Int {
returnString.append("\"\(key)\": \"\(value)\n")
} else if let value = value as? String {
returnString.append("\"\(key)\": \"\(value)\n")
}
}
returnString.append("}")
return returnString
}
}
// your json has a preceding key of "registration", this is the type you will decode
public struct RegistrationParent: Codable {
var registration: Registration
}
let jsonDataA =
"""
{
"registration": {
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
}
""".data(using: .utf8)!
let jsonDataB =
"""
{
"registration": {
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"non_title": "Some Title",
"another_url": "https://www.someurl.com/",
"some_category": "test",
"some_views": 9999
}
}
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
var registrationA = try decoder.decode(RegistrationParent.self, from: jsonDataA)
print(registrationA.registration.configurationPayload?.jsonString ?? "{}")
var registrationB = try decoder.decode(RegistrationParent.self, from: jsonDataB)
print(registrationB.registration.configurationPayload?.jsonString ?? "{}")
} catch {
print(error)
}
here is configurationPayload is dictionary so your Registration struct look like below
struct Registration : Codable {
let configurationPayload : ConfigurationPayload?
let deviceType : String?
let discoveryTimeout : Int?
let id : String?
let installationTimeout : Int?
let state : String?
let thingUuid : Int?
enum CodingKeys: String, CodingKey {
case configurationPayload = "configuration_payload"
case deviceType = "device_type"
case discoveryTimeout = "discovery_timeout"
case id = "id"
case installationTimeout = "installation_timeout"
case state = "state"
case thingUuid = "thing_uuid"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
configurationPayload = ConfigurationPayload(from: decoder)
deviceType = try values.decodeIfPresent(String.self, forKey: .deviceType)
discoveryTimeout = try values.decodeIfPresent(Int.self, forKey: .discoveryTimeout)
id = try values.decodeIfPresent(String.self, forKey: .id)
installationTimeout = try values.decodeIfPresent(Int.self, forKey: .installationTimeout)
state = try values.decodeIfPresent(String.self, forKey: .state)
thingUuid = try values.decodeIfPresent(Int.self, forKey: .thingUuid)
}
}
and your ConfigurationPayload look like this
struct ConfigurationPayload : Codable {
let category : String?
let title : String?
let url : String?
let views : Int?
enum CodingKeys: String, CodingKey {
case category = "category"
case title = "title"
case url = "url"
case views = "views"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
category = try values.decodeIfPresent(String.self, forKey: .category)
title = try values.decodeIfPresent(String.self, forKey: .title)
url = try values.decodeIfPresent(String.self, forKey: .url)
views = try values.decodeIfPresent(Int.self, forKey: .views)
}
}

Swift 5: Decoding Nested JSON

I'm having a little trouble decoding some JSON data into a struct. I've tried below methods and it doesn't work:
JSON:
{
"submission_date": "2020-02-28T14:21:46.000+08:00",
"status": "pending",
"requestor": {
"name": "Adam"
},
"claim_items": [
{
"date": "2020-02-20",
"description": "TV",
"currency": "MYR",
"amount": "103.0",
"amount_in_ringgit": "10.0"
},
{
"date": "2020-02-20",
"description": "Netflix",
"currency": "MYR",
"amount": "12.0",
"amount_in_ringgit": "10.0"
}
]
}
Struct Method 1:
struct ClaimDetail: Decodable {
let submission_date: String
let status: String
let requestor: Requestor
let claim_items: [ClaimItem]
}
struct Requestor: Decodable {
let name: String
init(json: [String:Any]) {
name = json["name"] as? String ?? ""
}
}
struct ClaimItem: Decodable {
let date: String
let description: String
let currency: String
let amount: String
let amount_in_ringgit: String
init(json: [String:Any]) {
date = json["date"] as? String ?? ""
description = json["description"] as? String ?? ""
currency = json["currency"] as? String ?? ""
amount = json["amount"] as? String ?? ""
amount_in_ringgit = json["amount_in_ringgit"] as? String ?? ""
}
}
Struct Method 2:
struct ClaimDetail: Decodable {
let submission_date: String
let status: String
let requestor: Requestor
let claim_items: [ClaimItem]
struct Requestor: Decodable {
let name: String
init(json: [String:Any]) {
name = json["name"] as? String ?? ""
}
}
struct ClaimItem: Decodable {
let date: String
let description: String
let currency: String
let amount: String
let amount_in_ringgit: String
init(json: [String:Any]) {
date = json["date"] as? String ?? ""
description = json["description"] as? String ?? ""
currency = json["currency"] as? String ?? ""
amount = json["amount"] as? String ?? ""
amount_in_ringgit = json["amount_in_ringgit"] as? String ?? ""
}
}
}
Struct Method 3 (via https://app.quicktype.io/):
// MARK: - ClaimDetail
struct ClaimDetail: Codable {
let submissionDate, status: String
let requestor: Requestor
let claimItems: [ClaimItem]
enum CodingKeys: String, CodingKey {
case submissionDate = "submission_date"
case status, requestor
case claimItems = "claim_items"
}
}
// MARK: - ClaimItem
struct ClaimItem: Codable {
let date, claimItemDescription, currency, amount: String
let amountInRinggit: String
enum CodingKeys: String, CodingKey {
case date
case claimItemDescription = "description"
case currency, amount
case amountInRinggit = "amount_in_ringgit"
}
}
// MARK: - Requestor
struct Requestor: Codable {
let name: String
}
URL Session
URLSession.shared.dataTask(with: requestAPI) { [weak self] (data, response, error) in
if let data = data {
do {
let json = try JSONDecoder().decode(ClaimDetail.self, from: data)
print (json)
} catch let error {
print("Localized Error: \(error.localizedDescription)")
print("Error: \(error)")
}
}
}.resume()
All returns below error:
Localized Error: The data couldn’t be read because it isn’t in the correct format.
Error: 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.})))
Solution:
I used struct method #1 and it wasn't the issue. The issue was with how I decoded the data in URLSession. For some reason, this works:
URLSession.shared.dataTask(with: requestAPI) { [weak self] (data, response, error) in
if let data = data {
do {
let dataString = String(data: data, encoding: .utf8)
let jsondata = dataString?.data(using: .utf8)
let result = try JSONDecoder().decode(ClaimDetail.self, from: jsondata!)
print(result)
} catch let error {
print("Localized Error: \(error.localizedDescription)")
print("Error: \(error)")
}
}
}.resume()
Screenshot:
I don't really understand but I guess I had to convert the data into a string, then decode that instead?
Thank you all for helping out.

Error trying to parse JSON using URLSession in swift

I'm trying to parse a test JSON from a http adress, but I get an error saying that
"No value associated with key CodingKeys(stringValue: \"name\", intValue: nil)
The JSON looks like this. It has been validated, so it should work ok:
{
"work": [
{
"name": "Jocke",
"job": "Developer",
"date": "1985-12-30T00:00:00+0000",
"best_book": {
"id": 33245,
"title": "DiscWorld",
"author": {
"id": 345,
"name": "Terry Prattchet"
}
}
},
{
"name": "Bob",
"job": "Construction worker",
"date": "2010-01-30T00:00:00+0000",
"best_book": {
"id": 375802,
"title": "Ender's Game (Ender's Saga, #1)",
"author": {
"id": 589,
"name": "Orson Scott Card"
}
}
}
]
}
The code looks like this:
struct People: Codable {
let name: String
let job: String
enum OuterKey: String, CodingKey {
case work = "work"
}
enum codingKeys: String, CodingKey {
case name = "name"
case job = "job"
}
init(decoder: Decoder) throws {
let outerContainer = try decoder.container(keyedBy: OuterKey.self)
let innerContainer = try outerContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .work)
self.name = try innerContainer.decode(String.self, forKey: .name)
self.job = try innerContainer.decode(String.self, forKey: .job)
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
guard let url = URL(string: "https://api.myjson.com/bins/fe2eo") else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
let decodedJson = try! jsonDecoder.decode([People].self, from: data)
}
}.resume()
}
}
I'm just trying to grasp the first two keys as of now, just to see if it works. But it doesn't even get past name.
Your api returns
[{"firstName":"Jocke","job":"developer"},{"firstName":"Anna","job":"construction"},{"firstName":"Peter","job":"pilot"}]
do {
let jsonDecoder = JSONDecoder()
let decodedJson = try jsonDecoder.decode([People].self, from: data)
}
catch {
print(error)
}
struct People: Codable {
let firstName, job: String
}
just try this
struct Work:Codeable {
let work:[People]
}
struct People: Codable {
let name: String
let job: String
}
do {
let jsonDecoder = JSONDecoder()
let decodedJson = try jsonDecoder.decode(Work.self, from: data)
}
catch {
print(error)
}
if you have same name as json keys you don't need to use codingkeys

Parsing json data using Codable

I am new to using Codable for parsing data from JSON and I am having trouble with the format of my JSON. I am not able to parse the correct fields into my Employee object. This is my first time using codable and dealing with a complex URL. This is how my JSON url is structured: https://ibb.co/WgDNMNT
{
"students": [
{
"uuid": "0djkdjjf734783749c",
"full_name": "Joe Morris",
"phone_number": "44445399",
"email_address": "jm99#jfgj.com",
"biography": "student of arts"
},
{
"uuid": "0djkdjjf734783749c",
"full_name": "Joe Morris",
"phone_number": "44445399",
"email_address": "jm99#jfgj.com",
"biography": "student of arts"
}
]
}
Here is my code:
struct Students: Codable {
var uuid: String?
var fullName: String?
var phoneNumber: String?
var emailAddress: String?
var biography: String?
}
//Custom Keys
enum CodingKeys: String, CodingKey{
case uuid
case fullname = "full_name"
case phoneNumber = "phone_number"
case emailAddress = "email_address"
case biography = "biography"
}
func parseData(){
guard let url = URL(string: "xxxxxxxxxx") else {return}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let dataResponse = data,
error == nil else {
print(error?.localizedDescription ?? "Error")
return }
do{
let decoder = JSONDecoder()
let model = try decoder.decode([Students].self, from: dataResponse)
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
}
Replace
let model = try decoder.decode([Students].self, from: dataResponse)
With
let model = try decoder.decode([String:[Students]].self, from: dataResponse)
print(model["students"])

Not able to parse api response

I have an api response that looks like so…I get the response from the api properly..
{
"status": "success",
"data": {
"meta": {
"url": "htt..://www.abc.com",
"title": “ASD - Log In or Sign Up",
"description": "Create an account or log in….”,
"display_url": "htt..://www.abc.com/",
"video_url": "",
"image": "htt..://www.asd.com/images/asds_325x325.png",
"img_wxh": "325x325"
}
}
}
The model class with which I parse this data is given like so..
struct MetaData: Codable {
let status: String?
let data: DataClass?
}
struct DataClass: Codable {
let meta: Meta
}
struct Meta: Codable {
let url: String
let title, description: String
let displayURL: String
let videoURL: String
let image: String
let imgWxh: String
enum CodingKeys: String, CodingKey {
case url = "url"
case title = "title"
case description = "description"
case displayURL = "display_url"
case videoURL = "video_url"
case image = "image"
case imgWxh = "img_wxh"
}
}
The api call that is being made is gives as below...
WebServiceClient.shared.getMeta(withParameters: parameters) { [weak self] (isSuccess, result) in
guard let `self` = self else { return }
if isSuccess, result != nil {
if let jsonData = try? JSONSerialization.data(withJSONObject: result as Any, options: []) {
do {
let metaData = try JSONDecoder().decode(MetaData.self, from: jsonData)
self.metaDataImageView.sd_setImage(with: URL(string: metaData.data?.meta.image ?? ""), completed: nil)
self.urlLabel.text = metaData.data?.meta.url
self.titleLabel.text = metaData.data?.meta.title
self.urlDescriptionLabel.text = metaData.data?.meta.description
} catch {
print("error \(error)")
}
}
}
But I get all data as nil...what could be the reason..?
I get nothing in metaData...
Here is the code I tried to parse your data
struct MetaData: Codable {
let status: String?
let data: DataClass?
}
struct DataClass: Codable {
let meta: Meta
}
struct Meta: Codable {
let url: String
let title, description: String
let displayURL: String
let videoURL: String
let image: String
let imgWxh: String
enum CodingKeys: String, CodingKey {
case url = "url"
case title = "title"
case description = "description"
case displayURL = "display_url"
case videoURL = "video_url"
case image = "image"
case imgWxh = "img_wxh"
}
}
let jsonString = """
{
"status": "success",
"data": {
"meta": {
"url": "htt..://www.abc.com",
"title": "ASD - Log In or Sign Up ",
"description": "Create an account or log in….",
"display_url": "htt..://www.abc.com/",
"video_url": "",
"image": "htt..://www.asd.com/images/asds_325x325.png",
"img_wxh": "325x325"
}
}
}
"""
let jsonData = jsonString.data(using: .utf8)
do {
let parsedData = try JSONDecoder().decode(MetaData.self, from: jsonData!)
print(parsedData)
} catch {
print(error.localizedDescription)
}
And it works.
Also your json have some issue so make sure you validate your json format.
you can use jsonlint.com for validating json.

Resources