why does swift 4 codable throw an error when parsing [duplicate] - ios

This question already has answers here:
Using codable with value that is sometimes an Int and other times a String
(5 answers)
Closed 4 years ago.
This is my first time using the swift 4 codable protocol in an app.
I'm given a json response like this -
{
"result": [
{
"id": 1,
"heading": "Chapter 1",
"headingTextColor": "#3e9690",
"title": "Introduction: inovation for crisis",
"coverImage": "https://www.country067.com/wp-content/uploads/sites/28/2017/07/logoImage_4.jpg",
"descriptionUrl": "This is an option attribute. If not null this chapter will probably has no sections",
"sections": [
{
"id": 1,
"title": "Background",
"url": "http://api.example.com/chapter/1/section/1",
"projects": "null"
},
{
"id": 2,
"title": "Projects",
"url": null,
"projects": [
{
"id": 1,
"title": "Support to refugees",
"url": "http://api.example.com/chapter/1/project/1",
"coverImage": "https://example/wp-content/uploads/sites/28/2017/07/logoImage_4.jpg"
},
{
"id": 2,
"title": "title text",
"url": "http://api.example.com/chapter/1/project/2",
"coverImage": "https://example.com/wp-content/uploads/sites/28/2017/07/logoImage_4.jpg"
}
]
}
]
}
]
}
Using the decodable protocol, i created my models to map the response .
struct ChapterResult: Decodable {
let result: [Chapter]
}
struct Chapter: Decodable {
let id: Int
let heading: String
let headingTextColor: String
let title: String
let coverImage: String
let descriptionUrl: String
let sections: [Section]
}
struct Section: Decodable {
let id: Int
let title: String
let url: String?
let projects: [Project]?
}
struct Project: Decodable {
let id: Int
let title: String
let url: String
let coverImage: String
}
When calling the json decoder I do the following
let decoder = JSONDecoder()
let response = try decoder.decode(ChapterResult.self, from: data)
completion(.success(response.result))
which then results in the following error
failure(Optional(Error Domain=NSCocoaErrorDomain Code=4864 "Expected
to decode Array but found a string/data instead."
UserInfo={NSCodingPath=(
"CodingKeys(stringValue: \"result\", intValue: nil)",
"_JSONKey(stringValue: \"Index 0\", intValue: 0)",
"CodingKeys(stringValue: \"sections\", intValue: nil)",
"_JSONKey(stringValue: \"Index 0\", intValue: 0)",
"CodingKeys(stringValue: \"projects\", intValue: nil)"

It says result[0].sections[0].projects has a TypeMissMatch error.
So this "projects": "null" should be an array but it's string.

Read the error:
Expected to decode Array but found a string/data instead
In your JSON file projects have String or Array:
"projects": "null"
"projects": [
{
"id": 1
To fix the issue "null" should be null. If you can't edit JSON file you should write custom initializer.

see, Accroding to your struct
struct Section: Decodable {
let id: Int
let title: String
let url: String?
let projects: [Project]?
}
projects is a array of type project.
{
"id": 1,
"title": "Background",
"url": "http://api.example.com/chapter/1/section/1",
"projects": "null"
}
But at 0th index of sections, the value of projects key is "null" which is a string, so that's why you are getting an error.
Now read carefully your error it contains "Expected to decode Array but found a string/data instead."

Related

Parse valid objects from JSON array in Swift

I have a codable struct like this
struct User: Codable {
let id: String
let registrationId: String
let firstName: String?
let lastName: String?
}
Now, the response from the server contains an array like this
[
{
"id": "1",
"registrationId": "r1",
"firstName": "Jon",
"lastName": "Doe"
},
{
"id": "2",
"registrationId": null,
"firstName": null,
"lastName": null
},
{
"id": "3",
"registrationId": null,
"firstName": null,
"lastName": null
},
{
"id": "4",
"registrationId": "r4",
"firstName": "Jon",
"lastName": "Snow"
}
]
I want to parse this as [User] but only those who have a valid(not null) registrationId. I know how to parse JSON in swift. But the problem here is because of the two invalid data in the middle the whole response will run into decoding error. But I want to parse it as an array of [User] containing valid ones(in this case first and last object).
Any hints or help is much appreciated.
1- Make registrationId an optional
let registrationId: String?
2-
let res = try JSONDecoder().decode([User].self,from:data)
let filtered = res.filter { $0.registrationId != nil }
after all, this data must come from a database or an array. By making the id parameter as the primary key, the registrationId parameter as a foreign key, and if you are working on the registrationId parameter, you can make a productive sequence or if it is on the array, you can link the method that generates the sequence for that registrationId.
Now I know how to achieve this.
struct User: Codable {
let id: String
let registrationId: String
let firstName: String?
let lastName: String?
}
struct WrappedDecodableArray<Element: Decodable>: Decodable {
let elements: [Element]
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var elements = [Element]()
while !container.isAtEnd {
if let element = try? container.decode(Element.self) {
elements.append(element)
} else {
// move the container currentIndex forward
_ = try container.decode(Block.self)
}
}
self.elements = elements
}
private struct Block: Decodable {}
}
func testUserParsing() {
let jsonStr = """
[
{
"id": "1",
"registrationId": "r1",
"firstName": "Jon",
"lastName": "Doe"
},
{
"id": "2",
"registrationId": null,
"firstName": null,
"lastName": null
},
{
"id": "3",
"registrationId": null,
"firstName": null,
"lastName": null
},
{
"id": "4",
"registrationId": "r4",
"firstName": "Jon",
"lastName": "Snow"
}
]
"""
let jsonData = jsonStr.data(using: .utf8)!
let wrappedArray = try! JSONDecoder().decode(WrappedDecodableArray<User>.self, from: jsonData)
print(wrappedArray.elements)
}
It would have been more elegant if we could override the init(from decoder: Decoder) for Array under certain conditions like extension Array where Element == User. But looks like this is not possible. Initializer inside the extension can not override the original one and hence never get called.
So for now looks like wrapping with a struct is the only solution.

The data couldn’t be read because it isn’t in the correct format (swift)

I want to take weather informations with JSON but there is an error : The data couldn’t be read because it isn’t in the correct format.
Error 'It looks like your post is mostly code; please add some more details.' in stackoverflow. Although I briefly describe my problem, it still expects an explanation from me :/
override func viewDidLoad() {
super.viewDidLoad()
let url = "https://api.openweathermap.org/data/2.5/weather?q=bursa,tr&appid=00f63a1cff271776651468c0204c422c"
getData(from: url)
}
private func getData (from url : String){
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data , response , error in
guard let data = data , error == nil else {
print ("birşeyler ters gitti")
return
}
var main : Response?
do {
main = try JSONDecoder().decode(Response.self , from: data)
} catch{
print ("ERROR IS HERE!!! \(error.localizedDescription)")
}
guard let json = main else {
return
}
print (json.weather)
})
task.resume()
}}
struct Response : Codable {
let weather : myResult
let status : String
}
struct myResult : Codable {
let main : String
let description : String
let icon : String
}
API Response is like that :
{"coord": { "lon": 139,"lat": 35},
"weather": [
{
"id": 800,
"main": "Clear",
"description": "clear sky",
"icon": "01n"
}
],
"base": "stations",
"main": {
"temp": 281.52,
"feels_like": 278.99,
"temp_min": 280.15,
"temp_max": 283.71,
"pressure": 1016,
"humidity": 93
},
"wind": {
"speed": 0.47,
"deg": 107.538
},
"clouds": {
"all": 2
},
"dt": 1560350192,
"sys": {
"type": 3,
"id": 2019346,
"message": 0.0065,
"country": "JP",
"sunrise": 1560281377,
"sunset": 1560333478
},
"timezone": 32400,
"id": 1851632,
"name": "Shuzenji",
"cod": 200
}
First, error.localizedDescription is meant to display an information for the user. It's not useful for debugging. If you replace it with error:
} catch {
print ("ERROR IS HERE!!! \(error)") // <- remove .localizedDescription
}
you will get more details:
ERROR IS HERE!!! typeMismatch(Swift.Dictionary<Swift.String, Any>,
Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue:
"weather", intValue: nil)], debugDescription: "Expected to decode
Dictionary<String, Any> but found an array instead.", underlyingError:
nil))
To solve this you need to declare weather as an array:
let weather: [myResult]
I'd also recommend replacing myResult with Weather (or at least capitalised MyResult) as it will be more readable:
struct Weather: Codable {
let main: String
let description: String
let icon: String
}
Also, in the JSON response you provided there is no status field so you may need to remove it from the Response class (or make it optional).
If you'd like to add more fields to the response, declare them according to your JSON structure. Eg. if you want to add humidity and temperature you can do:
struct Response: Codable {
...
let main: Main
}
struct Main: Codable {
let temp: Double
let humidity: Double
}
To have a more readable code you can use CodingKeys - then your variable names can be independent from JSON variables.
struct Main: Codable {
enum CodingKeys: String, CodingKey {
case temperature = "temp"
case humidity
}
let temperature: Double
let humidity: Double
}
Summing up, your Response may look like this:
struct Response: Codable {
let weather: [Weather]
let main: Main
// alternatively declare `status` optional
// let status: String?
}
struct Weather: Codable {
let main: String
let description: String
let icon: String
}
struct Main: Codable {
enum CodingKeys: String, CodingKey {
case temperature = "temp"
case humidity
}
let temperature: Double
let humidity: Double
}

How to configure model class for nested JSON response with Alamofire and Decodable in iOS

I'm using Alamofire and Decodable for a Google Books API request for performing a search.
I've created a Decodable model which I will add below and it worked up until I try to get any other field below "title" in "volumeInfo" (I've been commenting out properties one by one to check where it fails). The error I get after uncommenting "authors", "publisher" or anything other than "title" inside "volumeInfo" is:
responseSerializationFailed(reason: Alamofire.AFError.ResponseSerializationFailureReason.decodingFailed(error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "publisher", intValue: nil)
An example response is:
{
"kind": "books#volume",
"id": "Ett09eLWE5oC",
"etag": "WIwTdsmpnhs",
"selfLink": "https://www.googleapis.com/books/v1/volumes/Ett09eLWE5oC",
"volumeInfo": {
"title": "The Picture of Dorian Gray",
"authors": [
"Oscar Wilde"
],
"publisher": "Wordsworth Editions",
"publishedDate": "1992",
"description": "The handsome appearance of dissolute young Dorian Gray remains unchanged while the features in his portrait become distorted as his degeneration progresses",
"industryIdentifiers": [
{
"type": "ISBN_10",
"identifier": "1853260150"
},
{
"type": "ISBN_13",
"identifier": "9781853260155"
}
],
"readingModes": {
"text": false,
"image": true
},
"pageCount": 312,
"printType": "BOOK",
"categories": [
"Fiction"
],
"averageRating": 4.0,
"ratingsCount": 6,
"maturityRating": "NOT_MATURE",
"allowAnonLogging": false,
"contentVersion": "1.1.2.0.preview.1",
"panelizationSummary": {
"containsEpubBubbles": false,
"containsImageBubbles": false
},
"imageLinks": {
"smallThumbnail": "http://books.google.com/books/content?id=Ett09eLWE5oC&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api",
"thumbnail": "http://books.google.com/books/content?id=Ett09eLWE5oC&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api"
},
"language": "en",
"previewLink": "http://books.google.ro/books?id=Ett09eLWE5oC&pg=PA130&dq=9781853260155&hl=&cd=1&source=gbs_api",
"infoLink": "http://books.google.ro/books?id=Ett09eLWE5oC&dq=9781853260155&hl=&source=gbs_api",
"canonicalVolumeLink": "https://books.google.com/books/about/The_Picture_of_Dorian_Gray.html?hl=&id=Ett09eLWE5oC"
}
}
My model:
struct Response: Decodable {
struct VolumeInfo: Decodable {
var title: String
//var authors: [String]
var publisher: String
//var identifiers: [Identifier]
//var image: ImageLink
enum CodingKeys: String, CodingKey {
case title
//case authors
case publisher
//case identifiers = "industryIdentifiers"
//case image = "imageLinks"
}
}
struct Identifier: Decodable {
var type: String
var identifier: String
enum CodingKeys: String, CodingKey {
case type
case identifier
}
}
struct ImageLink: Decodable {
var url: URL
enum CodingKeys: String, CodingKey {
case url = "thumbnail"
}
}
var id: String
var volumeInfo: VolumeInfo
}

get position in a api swift Codable and Model data

i am getting data from an api like this
[
{
"internData": {
"id": "abc123",
"name": "Doctor"
},
"author": "Will smith",
"description": "Is an actor",
"url": "https://www",
},
{
"internData": {
"id": "qwe900",
"name": "Constructor"
},
"author": "Edd Bett",
"description": "Is an Constructor",
"url": "https://www3",
}
]
I have my model like this
struct PersonData: Codable {
let author: String?
let description: String?
let url: String?
}
But I dont know how to define the "internData", I tried with another Model "InterData" and define id and name like the PersonData, but i get an error, i tried also with [String:Any] but i get an error for the Codable protocol
I am using
let resP = try JSONSerialization.jsonObject(with: data, options: .init()) as? [String: AnyObject]
print("resP", )
in my script of Service/Network
Thanks if somebody knows
You can't use [String:Any] type in case of Codable. you need to create an another model of InternData, which is used by PersonData.
Code:
JSON Data :
let jsonData =
"""
[
{
"internData": {
"id": "abc123",
"name": "Doctor"
},
"author": "Will smith",
"description": "Is an actor",
"url": "https://www",
},
{
"internData": {
"id": "qwe900",
"name": "Constructor"
},
"author": "Edd Bett",
"description": "Is an Constructor",
"url": "https://www3",
}
]
"""
// Models
struct PersonData: Codable {
let author: String?
let description: String?
let url: String?
let internData : InternData?
}
// New model
struct InternData : Codable {
let id : String?
let name : String?
}
// Parsing
do {
let parseRes = try JSONDecoder().decode([PersonData].self, from: Data(jsonData.utf8))
print(parseRes)
}
catch {
print(error)
}

How to get json response using alamofire in iOS Swift?

I have been trying to get json response from url using alamofire. Created model, apirouter and api client class.
its shows error
failure(Alamofire.AFError.responseSerializationFailed(reason:
Alamofire.AFError.ResponseSerializationFailureReason.decodingFailed(error:
Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "variables", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"variables\", intValue: nil) (\"variables\").", underlyingError: nil)))))
Here is my postman json response:
[
{
"id": "00602c70-fc8a-11e9-ad1d-2abe2670111d",
"resourceType": "Task",
"name": "My Tasks",
"owner": null,
"query": {
"assigneeExpression": "${currentUser()}",
"taskVariables": [],
"processVariables": [],
"caseInstanceVariables": [],
"orQueries": []
},
"properties": {
"variables": [
{
"name": "loanAmount",
"label": "Loan Amount"
},
{
"name": "firstName",
"label": "First Name"
}
],
"color": "#555555",
"showUndefinedVariable": false,
"description": "Tasks assigned to me",
"refresh": false,
"priority": -10
}
}
]
My trying get values for id, name and properties -> variables -> name and label from json response.
Here is model class:
import Foundation
public struct Filter: Codable {
let id: String
let name: String
let properties: [variables]
}
public struct variables: Codable {
let name: String
let label: String
}
Here is code for alamofire :
private static func performRequest<T:Decodable>(route:APIRouter, decoder: JSONDecoder = JSONDecoder(), completion:#escaping (AFResult<T>)->Void) -> DataRequest {
return AF.request(route)
.responseDecodable (decoder: decoder){ (response: AFDataResponse<T>) in
completion(response.result)
print("framework response::",response.result)
}
}
public static func getFilter(completion:#escaping (AFResult<[Filter]>)->Void) {
let jsonDecoder = JSONDecoder()
performRequest(route: APIRouter.getFilter, decoder: jsonDecoder, completion: completion)
}
Any help much appreciates pls...
your model class should be like the below.
import Foundation
public struct Filter: Codable {
let id: String
let name: String
let properties: Properties
}
public struct Properties: Codable {
let variables: [variables]
let color: String
let showUndefinedVariable: Bool
let description: String
let refresh: Bool
let priority: Int
}
public struct variables: Codable {
let name: String
let label: String
}
Did you try this one? This is what it should be
public struct Filter: Codable {
let id: String
let name: String
let properties: Property
}
public struct Property: Codable {
let variables: [Variable]
}
public struct Variable: Codable {
let name: String
let label: String
}
The error message you're getting is pretty clear: No value associated with key CodingKeys(stringValue: \"variables\"
You're attempting to decode the JSON into the Filter struct, but the JSON has no variables property. You could fix this by introducing a new Properties struct that will wrap the variables property, like this:
struct Filter: Codable {
let id: String
let name: String
let properties: Properties
}
struct Properties: Codable {
let variables: Variables
}
struct Variables: Codable {
let name: String
let label: String
}
Also, as you'll see in this snippet, it's convention to make the write the type names in CamelCase, so struct Variables instead of struct variables.

Resources