How to get JSON response from URL with json inside in swift - json

Hi my url of request is:
http://MYDOMAIN/jsonrpc?request=
{"jsonrpc": "2.0", "method": "VideoLibrary.GetMovies", "params": { "filter": {"field": "playcount", "operator": "is", "value": "0"}, "limits": { "start" : 0, "end": 75 }, "properties" : ["art", "rating", "thumbnail", "playcount", "file"], "sort": { "order": "ascending", "method": "label", "ignorearticle": true } }, "id": "libMovies"}
How can I parse result in swift?

Use Codable to parse JSON. Try with your demo input.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let someString = """
{"jsonrpc": "2.0", "method": "VideoLibrary.GetMovies", "params": { "filter": {"field": "playcount", "operator": "is", "value": "0"}, "limits": { "start" : 0, "end": 75 }, "properties" : ["art", "rating", "thumbnail", "playcount", "file"], "sort": { "order": "ascending", "method": "label", "ignorearticle": true } }, "id": "libMovies"}
"""
let data = someString.data(using: .utf8)!
do{
let jsonDataModels = try JSONDecoder().decode(JSONDataModel.self, from: data)
print(String(data: data, encoding: .utf8)!)
print("jsonDataModels: \(jsonDataModels)")
}catch {
print(error)
}
}
}
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let jSONDataModel = try? newJSONDecoder().decode(JSONDataModel.self, from: jsonData)
import Foundation
// MARK: - JSONDataModel
struct JSONDataModel: Codable {
let jsonrpc, method: String
let params: Params
let id: String
}
// MARK: - Params
struct Params: Codable {
let filter: Filter
let limits: Limits
let properties: [String]
let sort: Sort
}
// MARK: - Filter
struct Filter: Codable {
let field, filterOperator, value: String
enum CodingKeys: String, CodingKey {
case field
case filterOperator = "operator"
case value
}
}
// MARK: - Limits
struct Limits: Codable {
let start, end: Int
}
// MARK: - Sort
struct Sort: Codable {
let order, method: String
let ignorearticle: Bool
}
Another example tries with API call.
import UIKit
import Foundation
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://jsonplaceholder.typicode.com/users")!
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else { return }
do{
let jsonDataModels = try JSONDecoder().decode([JSONDataModel].self, from: data)
print(String(data: data, encoding: .utf8)!)
print("jsonDataModels: \(jsonDataModels)")
}catch{}
}
task.resume()
}
}
struct JSONDataModel: Codable {
let id: Int
let name, username, email: String
let address: Address
let phone, website: String
let company: Company
}
struct Address: Codable {
let street, suite, city, zipcode: String
let geo: Geo
}
struct Geo: Codable {
let lat, lng: String
}
struct Company: Codable {
let name, catchPhrase, bs: String
}

Related

Swift / Combine JSON decodable - decode and receive array of content only 'Invalid top-level type in JSON write'

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
}

Decode multiple layers of a nested JSON with Swift

I have trouble decoding json data using SwiftUI, I have the following json.
{
"data": [
{
"id": "project:xxxxxx",
"project_manager": {
"employee_id": "employee:xxxxxx",
"id": "employee:xxxxxx",
"person_id": "person: xxxxxx",
"name": "Peter Post"
},
"project_status": {
"id": "projectstatus:xxxxxx",
"label": "active"
},
"created": "2019-01-08 15:39:59",
"modified": "2019-01-24 14:39:13",
"created_at": "2019-01-08 15:39:59",
"updated_at": "2019-01-24 14:39:13",
"url": "https://url.com/projects/project/view?id=000",
...
I'm decoding the json with the following code
import Foundation
struct Projects: Decodable {
let data: [Data]
}
struct Data : Decodable, Identifiable {
let id: String
let url: String
let organization: Organization?
let project_status: ProjectStatus?
}
struct Organization : Decodable, Identifiable {
let id: String?
let name: String?
}
struct ProjectStatus: Decodable, Identifiable {
let id: String?
let label: String?
}
import Foundation
import SwiftUI
import Combine
class NetworkingManager: ObservableObject {
#Published var projectList = Projects(data: [])
init() {
var request = URLRequest(url: URL(string: "https://api-url/projects")!,timeoutInterval: Double.infinity)
request.addValue("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", forHTTPHeaderField: "Authentication-Key")
request.addValue("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", forHTTPHeaderField: "Authentication-Secret")
URLSession.shared.dataTask(with: request) { (data, _, _) in
guard let data = data else { return }
let projectList = try! JSONDecoder().decode(Projects.self, from: data)
DispatchQueue.main.async {
self.projectList = projectList
print(self.projectList)
}
}.resume()
}
}
import SwiftUI
struct ContentView : View {
#ObservedObject var networkingManager = NetworkingManager()
var body: some View {
VStack {
List(networkingManager.projectList.data, id: \.id) { project in
HStack {
Text(project.id)
Text(project.url)
}
}
}
}
}
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
This results in a list of id and url string values but my question is: How can I list multiple levels of the json files. Do I have to decode each level of the json I want to use or is there a better way?
It was less complicated than I thought. I got a nil value back when I was calling project_status?.label
this was resolved when I called it like this:
project_status?.label ?? self.defaultString

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.

typeMismatch on decodable Type when the type is correct

When I hit my configuration API with Postman I am given the following json response back. In this response the two apiVersion keys are numbers and not strings.
{
"data": {
"availability": {
"auth": true,
"ab": true,
"cd": true
},
"helloWorldConfiguration": {
"apiKey": "abcefg",
"rootUrl": "https://foo",
"apiVersion": 3
},
"fooBarConfiguration": {
"baseUrl": "https://foo",
"apiVersion": 1,
"privateApiPath": "",
"publicApiPath": "dev",
"tokenPath": ""
}
},
"errors": []
}
When I try to decode it it fails with a typeMismatch error. When I output the contents of the response, I see the following which looks fine to me.
data = {
availability = {
auth = 1;
ab = 1;
cd = 1;
};
helloWorldConfiguration = {
apiVersion = 1;
baseUrl = "https://foo";
privateApiPath = "";
publicApiPath = dev;
tokenPath = "";
};
fooBarConfiguration = {
apiKey = abcefg;
apiVersion = 3;
rootUrl = "https://foo";
};
};
errors = (
);
The error given to me indicates that data.helloWorldConfiguration.apiVersion is of type string instead of int. We can see from the original HTTP response I get from Postman that's not the case.
typeMismatch(Swift.Int, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil), CodingKeys(stringValue: "helloWorldConfiguration", intValue: nil), CodingKeys(stringValue: "apiVersion", intValue: nil)], debugDescription: "Expected to decode Int but found a string/data instead.", underlyingError: nil))
21:17:40 ERROR Unable to decode the response data into a model representation.
My model represents those properties as integers so it would appear that it receives the response and considers those numbers to be strings, which they're not.
public struct ServerConfiguration: Decodable {
let availability: AvailabilityConfiguration
let helloWorldConfiguration: HelloWorldConfiguration
let fooBarConfiguration: FooBarConfiguration
init(availability: AvailabilityConfiguration, helloWorldConfiguration: HelloWorldConfiguration, fooBarConfiguration: FloatSinkConfiguration) {
self.availability = availability
self.helloWorldConfiguration = helloWorldConfiguration
self.fooBarConfiguration = fooBarConfiguration
}
}
public struct FooBarConfiguration: Decodable {
let baseUrl: String
let apiVersion: Int
let privateApiPath: String
let publicApiPath: String
let tokenPath: String
init(baseUrl: String, apiVersion: Int, privateApiPath: String, publicApiPath: String, tokenPath: String) {
self.baseUrl = baseUrl
self.apiVersion = apiVersion
self.privateApiPath = privateApiPath
self.publicApiPath = publicApiPath
self.tokenPath = tokenPath
}
}
public struct AvailabilityConfiguration: Decodable {
let auth: Bool
let ab: Bool
let cd: Bool
init(auth: Bool, ab: Bool, cd: Bool) {
self.auth = auth
self.ab = ab
self.cd = cd
}
}
public struct HelloWorldConfiguration: Codable {
let apiKey: String
let rootUrl: String
let apiVersion: Int
init(apiKey: String, rootUrl: String, apiVersion: Int) {
self.apiKey = apiKey
self.rootUrl = rootUrl
self.apiVersion = apiVersion
}
}
As you can see my apiVersion members are both of type integer along with the json response. What am I doing wrong here? I assume what's happening is Swift is considering the numbers in the json string, regardless of how they're actually represented in the json. Is that the case?
Edit to show utf8 string of Alamofire response data
21:44:06 INFO GET: https:foo/configuration
{
"data" : {
"availability" : {
"auth" : true,
"ab" : true,
"cb" : true
},
"helloWorldConfiguration" : {
"apiKey" : "abcd",
"rootUrl" : "https://foo",
"apiVersion" : "3"
},
"fooBarConfiguration" : {
"baseUrl" : "https://foo",
"apiVersion" : "1",
"privateApiPath" : "",
"publicApiPath" : "dev",
"tokenPath" : "auth/token"
}
},
"errors" : []
}
It would seem that despite the API correctly returning apiVersion as a number, Swift is turning it into a string. Am I decoding it incorrectly?
func getRoute<TResponseData: Decodable>(route:String, completion: #escaping (TResponseData) -> Void) throws {
let headers = try! self.getHeaders(contentType: ContentType.json)
let completeUrl: String = self.getUrl(route: route, requestUrl: nil)
logger.info("GET: \(completeUrl)")
Alamofire.request(
completeUrl,
method: .get,
parameters: nil,
encoding: JSONEncoding.default,
headers: headers)
.validate()
.responseJSON { (response) -> Void in
self.logger.info("GET Response: \(String(describing:response.response?.statusCode))")
switch response.result {
case .success(_):
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom(Date.toFooBarDate)
do {
let result = try decoder.decode(TResponseData.self, from: response.data!)
completion(result)
} catch DecodingError.dataCorrupted(let error) {
self.logger.error(error.underlyingError!)
return
} catch {
print(response.result.value!)
print(error)
self.logger.error("Unable to decode the response data into a model representation.")
return
}
}
}
I checked in my playground and it seems that everything is working fine.To find the real issue i think you are required to provide the real url from where you are getting json and can be checked with alamofire
import Foundation
let json = """
{
"data": {
"availability": {
"auth": true,
"ab": true,
"cd": true
},
"helloWorldConfiguration": {
"apiKey": "abcefg",
"rootUrl": "https://foo",
"apiVersion": 3
},
"fooBarConfiguration": {
"baseUrl": "https://foo",
"apiVersion": 1,
"privateApiPath": "",
"publicApiPath": "dev",
"tokenPath": ""
}
},
"errors": []
}
"""
let data = json.data(using: .utf8)
struct Response : Codable {
let data : Data?
let errors : [String]?
}
struct Availability : Codable {
let auth : Bool?
let ab : Bool?
let cd : Bool?
}
struct Data : Codable {
let availability : Availability?
let helloWorldConfiguration : HelloWorldConfiguration?
let fooBarConfiguration : FooBarConfiguration?
}
struct FooBarConfiguration : Codable {
let baseUrl : String?
let apiVersion : Int?
let privateApiPath : String?
let publicApiPath : String?
let tokenPath : String?
}
struct HelloWorldConfiguration : Codable {
let apiKey : String?
let rootUrl : String?
let apiVersion : Int?
}
let decoder = JSONDecoder()
let response = try decoder.decode(Response.self, from: data!)
print(response)
And here is the response
Response(data: Optional(__lldb_expr_11.Data(availability: Optional(__lldb_expr_11.Availability(auth: Optional(true), ab: Optional(true), cd: Optional(true))), helloWorldConfiguration: Optional(__lldb_expr_11.HelloWorldConfiguration(apiKey: Optional("abcefg"), rootUrl: Optional("https://foo"), apiVersion: Optional(3))), fooBarConfiguration: Optional(__lldb_expr_11.FooBarConfiguration(baseUrl: Optional("https://foo"), apiVersion: Optional(1), privateApiPath: Optional(""), publicApiPath: Optional("dev"), tokenPath: Optional(""))))), errors: Optional([]))

Decoding nested JSON with Swift 4

JSON =
{
"html_attributions": [],
"results": [
{
"geometry": {},
"name": "Cruise Bar, Restaurant & Events",
"vicinity": "Circular Quay W, Sydney"
},
{}
],
"status": "OK"
}
How do I retrieve name if it is nested within results?
Sam Try this i write sample code in playground using your Json. root.results will give you array of dictionary, you can easily traverse and get your desired name from it.
import UIKit
struct Root: Codable {
let results: [Results]?
private enum CodingKeys: String, CodingKey {
case results = "results"
}
}
struct Results: Codable {
let name: String?
let vicinity: String?
}
let url = Bundle.main.url(forResource: "data", withExtension: "json")
let data = NSData(contentsOf: url!)
do {
let root = try JSONDecoder().decode(Root.self, from: data as! Data)
if let name = root.results?.first?.name {
print(name)
}
} catch let error as NSError {
print(error.description)
}
Here is the json i have used.
{
"results": [{
"name": "Cruise Bar, Restaurant & Events",
"vicinity": "Circular Quay W, Sydney"
}]
}
You can do it like that:
Model:
import Foundation
struct HtmlInitial: Codable {
let results: [Result]?
let status: String
enum CodingKeys: String, CodingKey {
case results, status
}
}
struct Result: Codable {
let name, vicinity: String?
}
extension HtmlInitial {
init(data: Data) throws {
self = try JSONDecoder().decode(HtmlInitial.self, from: data)
}
}
use model Like that :
let url = Bundle.main.url(forResource: "APIResponse", withExtension: "json")!
if let data = try? Data.init(contentsOf: url) ,
let initial = try? HtmlInitial.init(data: data),
let result = initial.results?[0] {
print(result.name)
}