Issue parsing the json response in swift - json

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
}

Related

Getting value from JSONSerialization key Swift

I have the following JSON...
{
"id": "1000035148",
"petId": "3",
"ownerId": "1000",
"locationId": null,
"status": "Active",
“services”: [
{
"id": "5004",
“data”: 1,
“data1”: 0,
“data2": 63,
“data3": 0
}
]
}
And I'm only trying to return the following objects...
"id": "1000035148",
"petId": "3",
"ownerId": "1000",
"locationId": null,
"status": "Active"
How can I achieve this with the following code?
session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) in
if let data = data {
do {
let jsonData = try JSONSerialization.jsonObject(with: data)
if let dictionary = jsonData as? [String: Any] {
if let nestedDictionary = dictionary["status"] as? [String: Any] {
for (key, value) in nestedDictionary {
print("Key: \(key), Value: \(value)")
}
}
}
print(jsonData)
} catch {
print("Error fetching data from API: \(error.localizedDescription)")
}
}
When I try to parse using the nestedDictionary = dictionary I get an error and it skips over the line. I'm confused on how to get just the key value pairs I want from the response.
Forget JSONSerialization and use Decodable with JSONDecoder:
struct DataModel: Decodable {
let id: String
let pedId: String?
let ownerId: String?
let locationId: String?
let status: String
}
do {
let dataModel = try JSONDecoder().decode(DataModel.self, from: data)
print("Status: \(dataModel.status)")
} catch ...
If you want to use JSONSerialization, note that status is not a dictionary, it's a String:
if let dictionary = jsonData as? [String: Any] {
if let status = dictionary["status"] as? String {
print("Status: \(status)")
}
}

Decode JSON Array with no Attribute Name

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)")
}
}

Response Serialization error, how to fix it?

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)
}
}
}
}

How can I read the nested json data by using swift combine

I just started using combine swift to handle the data request and response.
The Json data returned is a nested data which I will only need one part of it.
such as:
{
"page": 1,
"data": [
{
"id": 1,
"title": "news-1",
"content": "content 1"
},
{
"id": 2,
"title": "news-2",
"content": "content 2"
},
{
"id": 3,
"title": "news-3",
"content": "content 3"
}
],
"time": 202021313,
"description" :"xxxx"
}
I will need to use the data array.
Fetch functions below:
func fetchData() throws -> URLSession.DataTaskPublisher {
let headers = [
"Content-Type": "application/json",
"cache-control": "no-cache",
]
guard let url = URL(string: endpointStr ) else {
throw APIError.invalidEndpoint
}
var request = URLRequest(url: url,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
let session = URLSession.shared
return session.dataTaskPublisher(for: request)
}
let publisher = try? fetchData()
let decoder = JSONDecoder()
let cancellable = publisher?
.receive(on: DispatchQueue.main)
.map {
$0.data
}
.decode(type: DataModel.self, decoder: decoder)
.sink(receiveCompletion: { (completion) in
switch completion {
case .failure(let error):
print("Error:")
print(error)
case .finished:
print("DONE - get Publisher")
}
}, receiveValue: { data in
print(data.title)
})
The data it returned is the complete json data, is there any elegant way to get only the array of data and convert into an array of [DataModel] and handle the data in receiveValue.
I have tried to edit map with no luck:
.map {
if let dataString = String(data: $0.data, encoding: .utf8) {
let dataDic = APIService.convertToDictionary(text: dataString)
if let DataArray = dataDic?["data"] {
return listDataDic!
}
return $0.data
}
Please clarify if i've misunderstood the question but what if you use another model to decode your [DataModel] and then map to the decoded [DataModel] array?
Here is a unit test example. Response is the new model that decodes the [DataModel] array into something you can work with.
import XCTest
import Combine
let data = """
{
"page": 1,
"data": [
{
"id": 1,
"title": "news-1",
"content": "content 1"
},
{
"id": 2,
"title": "news-2",
"content": "content 2"
},
{
"id": 3,
"title": "news-3",
"content": "content 3"
}
],
"time": 202021313,
"description": "xxxx"
}
""".data(using: .utf8)!
class Response: Codable {
var data: [DataModel]
}
class DataModel: Codable {
let id: Int
let title: String
let content: String
}
class Test: XCTestCase {
func testDecodeDataModel() {
let e = expectation(description: "finished expectation")
let decoder = JSONDecoder()
let cancellable = Just(data)
.decode(type: Response.self, decoder: decoder)
.map { $0.data }
.sink(receiveCompletion: { (completion) in
// handle completion..
}, receiveValue: { dataArray in
print(dataArray.count) // here you can work with your [DataModel] array
e.fulfill()
})
wait(for: [e], timeout: 1)
}
}

Trying to parse json for public git repos in swift but receiving "Expected to decode Dictionary<String, Any> but found an array instead."

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.