I have looked through other threads regarding trying to parse JSON data where a JSON array has no name. From what I have found you need to use a unkeyedContainer but I'm not entirely sure from the examples I have seen how this works with the data model.
Below is a snippet of data from open charge api:
[
{
"IsRecentlyVerified": false,
"ID": 136888,
"UUID": "254B0B07-E7FC-4B4B-A37C-899BCB9D7261",
"DataProviderID": 18,
"DataProvidersReference": "0a9fdbb17feb6ccb7ec405cfb85222c4",
"OperatorID": 3,
"UsageTypeID": 1,
"AddressInfo": {
"ID": 137234,
"Title": "Ballee Road Park & Share",
"AddressLine1": "Ballee Road",
"Town": "Ballymena",
"Postcode": "BT42 2HD",
"CountryID": 1,
"Latitude": 54.844648,
"Longitude": -6.273606,
"AccessComments": "Ballee Road Park and Share, Ballymena",
"RelatedURL": "http://pod-point.com",
"Distance": 3.81818421833416,
"DistanceUnit": 2
},
"Connections": [
{
"ID": 191571,
"ConnectionTypeID": 25,
"Reference": "1",
"StatusTypeID": 50,
"LevelID": 2,
"Amps": 32,
"Voltage": 400,
"PowerKW": 22,
"CurrentTypeID": 20
},
It looks to me that the first [ and { have no attribute names which I belive is creating the error in xcode: "Error!: typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))"
Here is my data model:
import Foundation
struct PublicCharger: Decodable {
let AddressInfo: [AddressInfo]
}
Here is my code:
//Find public chargers from local coordinates
func findPublicChargers(lat: Double, long: Double) {
//Use apiurl to pull all charge points that are currently in that area by adding lat and long into the api call &latitude=***&longitude=*****
let apiurl = "https://api.openchargemap.io/v3/poi/?output=json&countrycode=UK&maxresults=100&compact=true&verbose=false"
let urlString = "\(apiurl)&latitude=\(lat)&longitude=\(long)"
//print(urlString)
performRequest(urlString: urlString)
}
//Perform API Request - (London App Brewry code)
//Create the custom url
func performRequest(urlString: String) {
if let url = URL(string: urlString) {
//print("Called")
//Create a URL Session
let session = URLSession(configuration: .default)
//Give the session a task
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
//let dataString = String(data: safeData, encoding: .utf8)
//print(dataString)
self.parseJSON(data: safeData)
print("Data: \(safeData)")
}
}
//Start the task
task.resume()
}
}
func parseJSON(data: Data){
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(PublicCharger.self, from: data)
print("Data: \(decodedData.AddressInfo[0].Title)")
} catch {
print("Error!: \(error)")
}
}
struct AddressInfo: Decodable {
let Title: String
}
I have seen that in the data model you would need to include an unkeyed container element. I'm just not sure how this should be carried out in the data model. Any light on this would be much appreciated.
Try to change your PublicCharger data model to
struct PublicCharger: Decodable {
let AddressInfo: [AddressInfo]
}
And your parseJSON function to
func parseJSON(data: Data){
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode([PublicCharger].self, from: data)
if !decodedData.isEmpty {
print("Data: \(decodedData[0].AddressInfo[0].Title)")
} else {
print("Empty result!")
}
} catch {
print("Error!: \(error)")
}
}
Related
I am receiving some JSON which looks like the below :
{
"template": "search",
"item": "2",
"contents": [
{
"title": "title 1",
"subtitle": "subtitle 1",
"imageurl": "/data/dzzxw0177014_325qv.jpg?size=small",
"fullscreenimageurl": "/data/xw0177014_325qv.jpg?size=large",
"id": "0177014",
"detaillink": "/apps/v2/details/programme/177014",
"duration": "PT2H46M"
},
{
"title": "title2",
"subtitle": "subtitle 2",
"imageurl": "/data_p//11436/origin_dzdzdzdzw0046394_43fu1.jpg?size=small",
"fullscreenimageurl": "/data/11456/w0046394_43fu1.jpg?size=large",
"id": "0046394",
"detaillink": "/apps/v2/details/programme/MYW0046394",
"duration": "PT1H40M46S"
}
]
}
and I have a corresponding model:
import Foundation
// MARK: - Welcome
struct Welcome {
let template, item: String
let contents: [Content]
}
// MARK: - Content
struct Content {
let title, subtitle, imageurl, fullscreenimageurl: String
let id, detaillink, duration: String
}
I have an API manager :
import Foundation
import Combine
class APIManager {
static let shared = APIManager()
let baseURL = "https:// ....."
func fetchShows(with query: String) -> AnyPublisher<[Content], Error > {
Future<Any, Error> { promise in
self.loadJson(withQuery: query) { (result) in
promise(.success(result))
}
}
.tryMap {
try JSONSerialization.data(withJSONObject: $0, options: .prettyPrinted)
}
.decode(type: [Content].self, decoder: jsonDecoder)
.eraseToAnyPublisher()
}
var jsonDecoder: JSONDecoder {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}
func loadJson(withQuery query: String,
completion: #escaping (Result<Data, Error>) -> Void) {
let UrlString = baseURL + (query.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "")
if let url = URL(string: UrlString) {
print (UrlString)
let urlSession = URLSession(configuration: .default).dataTask(with: url) { (data, response, error) in
if let error = error {
completion(.failure(error))
}
if let data = data {
completion(.success(data))
}
}
urlSession.resume()
}
}
}
At the moment I have a crash with the error "Invalid top-level type in JSON write", I assume this is because the JSON that I am trying to decode isn't an array of Content. It's a Welcome Struct which contains an array of Content.
At what point can I say that I am only interested in the Contents "Array" and decode it ? Should this be defined in the model some how ?
Thanks
You can use .map operator to transform your data objects.
.decode(type: Welcome.self, decoder: jsonDecoder) // <- here
.map { $0.contents } // <- here
.eraseToAnyPublisher()
In addition, you have to confirm your data objects to Decodable.
Adding Decodable keyword is enough, Since all the files types are Decodable here,
struct Welcome: Decodable { //<- Here
let template, item: String
let contents: [Content]
}
struct Content: Decodable { //<- Here
let title, subtitle, imageurl, fullscreenimageurl: String
let id, detaillink, duration: String
}
I am trying to decode a json data, but it throws an error:
[] nw_protocol_get_quic_image_block_invoke dlopen libquic failed responseSerializationFailed(reason: Alamofire.AFError.ResponseSerializationFailureReason.decodingFailed(error: Swift.DecodingError.typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))))
I think, I did it in a correct way, I checked for the type mismatch also, looks correct, but could not comprehend why am i still getting the error. .
here is how my json data response looks like:
[
{
"id": 1,
"name": "BlackJet",
"type": "Silk",
"quantity": "100"
},
[
{
"id": 2,
"name": "Toto",
"type": "Leather",
"quantity": "10"
},
...,
]
here is my struct data:
import Foundation
struct Response<T: Codable>: Codable {
var data: [T]?
}
struct MyData: Codable {
var id: Int
var name: String
var type: String
var quantity: Int
}
and my serverCommunicator func:
static func getData() -> Promise<Response<MyData>> {
let decoder = JSONDecoder()
return Promise { seal in
AF.request(API.getData, method: .get, parameters: .none).responseDecodable(of: Response<MyData>.self, decoder: decoder) { response in
switch response.result {
case .success(let val):
return seal.fulfill(val)
case .failure(let err):
return seal.reject(err)
}
}
}
}
and my apiCall func inside VC didload:
func getData() {
ServerCommunicator.getData().done { response -> Void in
guard response.data != nil, let data = response.data else {
print("Data could not be obtained.. .")
return
}
self.data = data
}.catch { (err) in
print(err)
}
}
NOTE: No api headers or parametrs exist in my api//
First you need to change the type of quantity from Int to String as per the response,
struct MyData: Codable {
var id: Int
var name: String
var type: String
var quantity: String
}
Then you will need to change the signature of getData method as below,
static func getData() -> Promise<[MyData]> {
let decoder = JSONDecoder()
return Promise { seal in
AF.request(API.getData, method: .get, parameters: .none).responseDecodable(of: [MyData].self, decoder: decoder) { response in
switch response.result {
case .success(let val):
return seal.fulfill(val)
case .failure(let err):
return seal.reject(err)
}
}
}
}
I have a json response as follows:
[
{
"item_id": 3310,
"sku": "BWBCL14KWGF003-BWBCL14KWGF003",
"qty": 1,
"name": "BWBCL14KWGF003",
"price": 471,
"product_type": "simple",
"quote_id": "4246",
"product_option": {
"extension_attributes": {
"custom_options": [
{
"option_id": "23243",
"option_value": "625080"
},
{
"option_id": "23242",
"option_value": "625032"
}
]
}
}
}
]
I have the alamofire code to get this response.
AF.request("https://adamas-intl.com/rest/V1/carts/mine/items", method: .get, parameters: nil, encoding: JSONEncoding.default, headers: headers).responseJSON { response in
switch response.result {
case .success(let json):
if let res = json as? [[String: Any]]{
print("res is",res)
}
case let .failure(error):
print(error)
}
I need to fetch the item_id and other values from the response.This way of fetching,iam not able to reach inside the values.
How could i parse this json response?
I think that the best way here is to use a Decodable protocol.
struct Item: Decodable {
var itemId: Int
var sku: String
// ...
}
Then use responseDecodable(_:) method
// create a decoder to handle the `snakeCase` to `camelCase` attributes
// thanks to this `Decoder`, you are able to add a property `var itemId: Int` instead of `var item_id: Int`
let decoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}()
AF.request("https://adamas-intl.com/rest/V1/carts/mine/items")
.validate()
.responseDecodable(of: [Item].self, decoder: decoder) { (response) in
guard let items = response.value else { return }
// do what you want
}
My json looks like this:
[
{
"name": "sensei",
"owner": {
"login": "linkedin",
},
"description": "distributed realtime searchable database",
"fork": false,
},
{
"name": "linkedin-utils",
"owner": {
"login": "linkedin",
},
"description": "Base utilities shared by all linkedin open source projects",
"fork": false,
}
]
The structs I built are the following:
struct LinkedinData: Codable {
var name: String
var description: String
var owner: OwnerLogin
var fork: Bool
}
struct OwnerLogin: Codable {
var login: String
}
My code for parsing is this one:
import UIKit
class ViewController: UIViewController {
var linkedinData = [LinkedinData]()
override func viewDidLoad() {
super.viewDidLoad()
let urString : String = "https://api.github.com/orgs/linkedin/repos"
if let url = URL(string: urString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return //exit out of function
}
parseJSON(json: data!)
}
task.resume()
}
func parseJSON(json: Data) {
let decoder = JSONDecoder()
if let decodedData = try? decoder.decode(LinkedinData.self, from: json) {
linkedinData = [decodedData]
}
}
}
I tried for hours bĂșt it seems impossible to parse the json and retreive the data I am looking for (name, description, owner.login and fork) in a collection type. Could you please help?
You should decode an array of LinkedinData, instead of just one, because your JSON has an array as its root:
[ <------- this "[" indicates an array
{
"name": "sensei",
"owner": {
"login": "linkedin",
},
Therefore, you should write:
if let decodedData = try? decoder.decode([LinkedinData].self, from: json) {
linkedinData = decodedData
}
if let decodedData = try? decoder.decode(LinkedinData.self, from: json) {
linkedinData = [decodedData]
}
replace this with
if let decodedData = try? decoder.decode([LinkedinData].self, from: json) {
linkedinData = decodedData
}
as your topmost object in JSON is an Array.
I have an issue with loading JSON results within swift (php connection).
I can retrieve JSON data but it will not let me assign it to a variable.
it always assigns the results as Optional.
The JSON Data:
{
"country": [{
"id": 1,
"name": "Australia",
"code": 61
}, {
"id": 2,
"name": "New Zealand",
"code": 64
}]
}
The xCode Output:
["country": <__NSArrayI 0x60000002da20>(
{
code = 61;
id = 1;
name = Australia;
},
{
code = 64;
id = 2;
name = "New Zealand";
}
)
]
Country Name: Optional(Australia)
Country Name: Optional(New Zealand)
The .swift file:
//function did_load
override func viewDidLoad() {
super.viewDidLoad()
//created RequestURL
let requestURL = URL(string: get_codes)
//creating NSMutable
let request = NSMutableURLRequest(url: requestURL!)
//setting the method to GET
request.httpMethod = "GET"
//create a task to get results
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
if error != nil{
print("error is \(String(describing: error))")
return;
}
//lets parse the response
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String: Any]
print(json)
if let countries = json["country"] as? [[String: AnyObject]] {
for country in countries {
print("Country Name: \(String(describing: country["name"]))")
print("Country Code: \(String(describing: country["code"]))")
if let couname = country["name"] as? [AnyObject] {
print(couname)
}
if let coucode = country["code"] as? [AnyObject] {
print(coucode)
}
}
}
} catch {
print("Error Serializing JSON: \(error)")
}
}
//executing the task
task.resume()
}
You need to unwrap the optional before you try to use it via string interpolation. The safest way to do that is via optional binding:
Please use below code, which will work for you.
if let countries = json["country"] as? [[String: AnyObject]] {
for country in countries {
print("Country Name: \(country["name"] as! String)")
print("Country Code: \(country["code"] as! String)")
if let couname = country["name"] as? String {
print(couname)
}
if let coucode = country["code"] as? Int {
print(coucode)
}
}
}