Related
I am trying to create a simple Swift UI app to download some json from the web and then display it on the screen.
I have 2 calls - a general lookup call for an array of items which are displayed in a View. I then have a detailed lookup, based upon the id chosen by the user in the first View which is then displayed in a second View.
I am able to access the parsed data via 2 #Published properties in my NetworkController class which I access from my Views, using: .onAppear to trigger the calls when the user arrives at each view.
My first call is working and I am able to display a list of items and then select them and read their id, which I then use for the second call.
The second call is causing me problems, though and I am getting an error decoding.
As there is quite a bit of code I’ve created a single Playground which includes some sample Json and the functions which I use, which is producing the error.
I am doing both calls using the same procedure - e.g. a number of function - which I have applied from another app which does a similar thing. My guess is that the error is because I have misunderstood some part of how these work. Because of this, I’ve commented what I think they are doing, to give an idea of what I am trying to achieve. Because the first call is working I am thinking that my understanding is partially correct, but I must be missing something.
The element that differs between the first data call and the second one is that the first one only requires a single struct to parse all the data whereas the second call uses a nested struct. I am thinking that I may need to adjust something to accomodate this - I am using the outermost struct - but I’m not clear exactly what I need to do.
If anyone has any suggestions I would be very grateful.
######################################################
# PLAYGROUND
######################################################
import Cocoa
var itemDetailed: ItemCodable? = nil
var item: Item
// ################################################################
// EXAMPLE JSON DATA
// ################################################################
let data = """
{
"item": {
"general": {
"id": 11,
"name": "app_install",
"enabled": true,
"trigger": "CHECKIN",
"trigger_checkin": true,
"trigger_enrollment_complete": false,
"trigger_login": false,
"trigger_network_state_changed": false,
"trigger_startup": false,
"trigger_other": "",
"frequency": "Ongoing",
"retry_event": "none",
"retry_attempts": -1,
"notify_on_each_failed_retry": false,
"location_user_only": false,
"target_drive": "/",
"offline": false,
"category": {
"id": 3,
"name": "Apps"
},
"date_time_limitations": {
"activation_date": "",
"activation_date_epoch": 0,
"activation_date_utc": "",
"expiration_date": "",
"expiration_date_epoch": 0,
"expiration_date_utc": "",
"no_execute_on": {},
"no_execute_start": "",
"no_execute_end": ""
},
"network_limitations": {
"minimum_network_connection": "No Minimum",
"any_ip_address": true,
"network_segments": []
},
"override_default_settings": {
"target_drive": "default",
"distribution_point": "",
"force_afp_smb": false,
"sus": "default"
},
"network_requirements": "Any",
"site": {
"id": -1,
"name": "None"
}
},
"scope": {
"all_computers": false,
"computers": [],
"computer_groups": [
{
"id": 1,
"name": "All Managed Clients"
}
],
"buildings": [],
"departments": [],
"limit_to_users": {
"user_groups": []
},
"limitations": {
"users": [],
"user_groups": [],
"network_segments": [],
"ibeacons": []
},
"exclusions": {
"computers": [],
"computer_groups": [
{
"id": 9,
"name": "app_installed_testutil"
}
],
"buildings": [],
"departments": [],
"users": [],
"user_groups": [],
"network_segments": [],
"ibeacons": []
}
},
"self_service": {
"use_for_self_service": false,
"self_service_display_name": "",
"install_button_text": "Install",
"reinstall_button_text": "Reinstall",
"self_service_description": "",
"force_users_to_view_description": false,
"self_service_icon": {},
"feature_on_main_page": false,
"self_service_categories": [],
"notification": "Self Service",
"notification_subject": "app_install",
"notification_message": ""
},
"package_configuration": {
"packages": [
{
"id": 3,
"name": "testutil_2.0.5.psr",
"action": "Install",
"fut": false,
"feu": false
}
]
},
"scripts": [],
"printers": [
""
],
"dock_items": [],
"account_maintenance": {
"accounts": [],
"directory_bindings": [],
"management_account": {
"action": "doNotChange"
},
"open_firmware_efi_password": {
"of_mode": "none",
"of_password_sha256": "xxxxxyyyyyyyyyzzzzzzzzaaaabbbbbbccccccc"
}
},
"reboot": {
"message": "This computer will restart in 5 minutes.",
"startup_disk": "Current Startup Disk",
"specify_startup": "",
"no_user_logged_in": "Restart if a package or update requires it",
"user_logged_in": "Restart if a package or update requires it",
"minutes_until_reboot": 5,
"start_reboot_timer_immediately": false,
"file_vault_2_reboot": false
},
"maintenance": {
"recon": true,
"reset_name": false,
"install_all_cached_packages": false,
"heal": false,
"prebindings": false,
"permissions": false,
"byhost": false,
"system_cache": false,
"user_cache": false,
"verify": false
},
"files_processes": {
"search_by_path": "",
"delete_file": false,
"locate_file": "",
"update_locate_database": false,
"spotlight_search": "",
"search_for_process": "",
"kill_process": false,
"run_command": ""
},
"user_interaction": {
"message_start": "",
"allow_users_to_defer": false,
"allow_deferral_until_utc": "",
"allow_deferral_minutes": 0,
"message_finish": ""
},
"disk_encryption": {
"action": "none"
}
}
}
""".data(using: .utf8)
// ################################################################
// DATA STRUCTS
// ################################################################
struct ItemCodable: Codable{
let item: Item }
struct Item: Codable, Hashable, Identifiable {
var id = UUID()
let general: General?
enum CodingKeys: String, CodingKey {
case general = "general"
}
}
struct General: Codable, Hashable, Identifiable {
var id = UUID()
let name: String?
let enabled: Bool?
let trigger: String?
let triggerCheckin, triggerEnrollmentComplete, triggerLogin, triggerLogout: Bool?
let triggerNetworkStateChanged, triggerStartup: Bool?
let triggerOther, frequency: String?
let locationUserOnly: Bool?
let targetDrive: String?
let offline: Bool?
let networkRequirements: String?
let mac_address: String?
let ip_address: String?
let payloads: String?
enum CodingKeys: String, CodingKey {
case name = "name"
case enabled = "enabled"
case trigger = "trigger"
case triggerCheckin = "trigger_checkin"
case triggerEnrollmentComplete = "trigger_enrollment_complete"
case triggerLogin = "trigger_login"
case triggerLogout = "trigger_logout"
case triggerNetworkStateChanged = "trigger_network_state_changed"
case triggerStartup = "trigger_startup"
case triggerOther = "trigger_other"
case frequency = "frequency"
case locationUserOnly = "location_user_only"
case targetDrive = "target_drive"
case offline = "offline"
case networkRequirements = "network_requirements"
case mac_address = "mac_address"
case ip_address = "ip_address"
case payloads = "payloads"
}
}
// ################################################################
// DECODE DATA
// ################################################################
struct ItemsDetailReply: Codable {
// Struct to parse data
let itemDetailed: ItemCodable
static func decode(_ data: Data) -> Result<ItemCodable,Error> {
let decoder = JSONDecoder()
do {
let response = try decoder.decode(ItemsDetailReply.self, from: data)
print("ItemsDetailReply Decoding succeeded")
separationLine()
print("Response is:\n\(response)")
return .success(response.itemDetailed)
} catch {
separationLine()
print("Decoding error")
return .failure(error)
}
}
}
func separationLine() {
print("------------------------------------------------------------------")
}
// ################################################################
// DECODE AND REDIRECT TO MAIN QUEUE
// ################################################################
func processDetail(data: Data) {
// func that initiates the decoding using the decoding struct - then redirects the
// returned data to the main queue
let decoded = ItemsDetailReply.decode(data)
switch decoded {
case .success(let itemDetailed):
receivedItemDetail(itemDetailed: itemDetailed)
separationLine()
print("itemDetailed name is:\(String(describing: itemDetailed.item.general?.name))")
separationLine()
case .failure(let error):
separationLine()
print("Error encountered")
separationLine()
print(error)
separationLine()
}
}
// ################################################################
// SET PROPERTY VIA MAIN QUEUE
// ################################################################
func receivedItemDetail(itemDetailed: ItemCodable) {
DispatchQueue.main.async {
// self.itemDetailed = itemDetailed
}
}
// CALL FUNCTION
processDetail(data: data!)
The problem lies in the decode method:
struct ItemsDetailReply: Codable {
// Struct to parse data
let itemDetailed: ItemCodable
static func decode(_ data: Data) -> Result<ItemCodable,Error> {
let decoder = JSONDecoder()
do {
let response = try decoder.decode(ItemsDetailReply.self, from: data)
print("ItemsDetailReply Decoding succeeded")
separationLine()
print("Response is:\n\(response)")
return .success(response.itemDetailed)
} catch {
separationLine()
print("Decoding error")
return .failure(error)
}
}
}
You're trying to decode a ItemsDetailReply but the data you pass into this method represents an ItemCodable. The decoder tries to locate a top level key itemDetailed but it fails.
You can probably ditch the ItemsDetailReply struct and just decode ItemCodable like this:
let response = try decoder.decode(ItemCodable.self, from: data)
I writing because I need to search a json-key passed in a function like a string. Do you have any suggestion on how I could implement it? Once I find the key I also need to edit the value. Here there is the code I wrote until now:
JSON:
{
"JSONRoot": {
"version": 1,
"Town": {
"hour": 0,
"latitude": "",
"longitude": 0,
"latitudine": 0
},
"MeasurePoints": {
"MeasurePoint": [
{
"code": "",
"codelocation": "",
}
]
},
"Wakeup": {
"startH": 6,
"startM": 0,
"maxAttempts": 3,
"maxRetry": 10
},
"Config": {
"port": 12345,
"A": {
"writable": true,
"value": 12
},
"B": {
"writable": true,
"value": 8
},
},
"Sales": {
"Stores": {
"Store": [
{
"description": "A description",
"type": "1",
"Floors": {
"basement": true,
"number": 2
},
"Doors": {
"type": "",
"number": 7
},
"Lights": {
"number": 20
}
},
{
"description": "A description",
"type": "4",
"Floors": {
"basement": none,
"number": 1
},
"Doors": {
"type": "",
"number": 4
},
"Lights": {
"number": 8
}
}
]
}
}
}
}
Structs with codable:
// MARK: - JSONConfig
struct JsonConfig: Codable {
let jsonRoot: JSONRoot?
enum CodingKeys: String, CodingKey {
case jsonRoot = "JSONRoot"
}
}
// MARK: - JSONRoot
struct JSONRoot: Codable {
let version: Int?
let measurePoints: MeasurePoints?
let wakeup: Wakeup?
let config: Config?
let sale: Sale?
enum CodingKeys: String, CodingKey {
case version
case measurePoints = "MeasurePoints"
case wakeup = "Wakeup"
case config = "Config"
case sale = "Sale"
}
}
// MARK: - Stores
struct Stores: Codable {
let stores: [Store]?
enum CodingKeys: String, CodingKey {
case stores = "Stores"
}
}
// MARK: - Store
struct Store: Codable {
let storeDescription: String?
let type: Int?
let floors: Floors?
let doors: Doors?
let lights: Lights?
enum CodingKeys: String, CodingKey {
case storeDescription = "description"
case type
case floors = "Floors"
case doors = "Doors"
case lights = "Lights"
}
}
// MARK: - Floors
struct Floors: Codable {
let basement: Bool?
let number: Int?
}
// MARK: - Doors
struct Doors: Codable {
let type: String?
let number: Int?
}
// MARK: - Lights
struct Lights: Codable {
let number: Int?
}
// MARK: - MeasurePoints
struct MeasurePoints: Codable {
let measurePoint: [MeasurePoint]?
enum CodingKeys: String, CodingKey {
case measurePoint = "MeasurePoint"
}
}
// MARK: - MeasurePoint
struct MeasurePoint: Codable {
let code, codeLocation: String?
}
// MARK: - Config
struct Config: Codable {
let port: Int?
let a, b: K?
enum CodingKeys: String, CodingKey {
case port
case a = "A"
case b = "B"
}
}
// MARK: - K
struct K: Codable {
let writable: Bool?
let value: Int?
}
// MARK: - Wakeup
struct Wakeup: Codable {
let startH, startM, maxAttempts, maxRetry: Int?
}
Function to search for a key:
func setKeyValue(jsonKey: String, value: String) {
let decoder = JSONDecoder()
let jsonData = Data(C.jsonString.utf8)
if let jsonResult = try? decoder.decode(JsonConfig.self, from: jsonData) {
// At this point I have the jsonKey = "JSONRoot.Wakeup.maxRetry" but how I can use it to search for
// the key in the jsonResult?
}
}
Obviously I need to create a new struct to edit the json but one step at a time.
Using JSONSerialisation is probably the most straightforward way here
var value: Any?
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
let keys = "JSONRoot.Wakeup.maxRetry".split(separator: ".").map {String($0)}
var dict = jsonResult
for i in 0..<keys.count {
if let temp = dict[keys[i]] as? [String:Any] {
dict = temp
continue
}
value = dict[keys[i]]
}
}
} catch {
print(error)
}
Note that this doesn't support arrays but a solution for that is very dependent on how the search key syntax would handle an array
If my thinking is correct as you, you can try with this code.
override func viewDidLoad() {
super.viewDidLoad()
let jsonString = """
{
"JSONRoot": {
"version": 1,
"Town": {
"hour": 0,
"latitude": "",
"longitude": 0,
"latitudine": 0
},
"MeasurePoints": {
"MeasurePoint": [{
"code": "",
"codelocation": ""
}]
},
"Wakeup": {
"startH": 6,
"startM": 0,
"maxAttempts": 3,
"maxRetry": 10
},
"Config": {
"port": 12345,
"A": {
"writable": true,
"value": 12
}
},
"Sales": {
"Stores": {
"Store": [{
"description": "A description",
"type": "1",
"Floors": {
"basement": true,
"number": 2
},
"Doors": {
"type": "",
"number": 7
},
"Lights": {
"number": 20
}
},
{
"description": "A description",
"type": "4",
"Floors": {
"basement": "none",
"number": 1
},
"Doors": {
"type": "",
"number": 4
},
"Lights": {
"number": 8
}
}
]
}
}
}
}
"""
editJson(jsonString)
}
func editJson(_ jsonString: String) {
do{
let jsonData = Data(jsonString.utf8)
var jsonObject = try JSONSerialization.jsonObject(with: jsonData)
parseDict(&jsonObject)
print("jsonObject: \(String(describing: jsonObject))")
}catch let error {
print(error.localizedDescription)
}
}
func parseDict(_ jsonObject: inout Any) {
if let _ = jsonObject as? String {
return
} else if var dictionary = jsonObject as? Dictionary<String, Any> {
for (key, value) in dictionary {
var nextObject = value
parseDict(&nextObject)
if let value = getValueWith(key), let _ = dictionary.removeValue(forKey: key) {
dictionary[key] = value
} else {
dictionary[key] = nextObject
}
}
jsonObject = dictionary
}else if let array = jsonObject as? Array<Any> {
var updatedArray = array
for (index, value) in array.enumerated() {
var nextObject = value
parseDict(&nextObject)
updatedArray[index] = nextObject
}
jsonObject = updatedArray
}
}
func getValueWith(_ key: String) -> String? {
return [
"description" : "Amit (amitpstu1#gmail.com) ... so on"
][key]
}
You can refresh your memory or learn more here:
https://developer.apple.com/documentation/foundation/archives_and_serialization/using_json_with_custom_types
You would be looking at merge json from different depths section. Using encodable extension etc.
You could also look here: In Swift, can one use a string to access a struct property? If you want to roll your own search function, like a modified dfs or something.
I'm accessing the data from an API with XCode(10.2.1) and Swift(5.0) and ran into a problem I cannot seem to find the answer to. I am able to get data from all other parts of the API apart from one, which has been named as a number string "750", im not sure how to grab that data when I can't use a variable that jsonDecoder can read?
This is an example of what I know won't work but gives you an idea of what I'm trying to do.
class Images: Codable {
let "750": String
init("750": String){
self."750" = "750"
}
}
Here's a snippet from the API I'm trying to get the images from:
"id": "069f7f26",
"sku": "AP",
"title": "Pizza",
"description": "A really great pizza",
"list_price": "9.95",
"is_vatable": true,
"is_for_sale": false,
"age_restricted": false,
"box_limit": 2,
"always_on_menu": false,
"volume": null,
"zone": null,
"created_at": "2017-03-06T10:52:43+00:00",
"attributes": [
{
"id": "670f0e7c",
"title": "Allergen",
"unit": null,
"value": "Products manufactured in a nut environment"
},
{
"id": "c29e7",
"title": "Weight",
"unit": "g",
"value": "300"
}
],
"tags": [
],
"images": {
"750": {
"src": "https:\/\/some_website.co.uk\/cms\/product_image\some_image.jpg",
"url": "https:\/\/some_website.co.uk\/cms\/product_image\some_image.jpg",
"width": 750
}
}
},
I setup an example that better match your situation in order to give you an overview on how to parse and access your JSON information dynamically with a Dictionary data type:
import Foundation
let jsonData = """
{
"images": {
"750": {
"src": "https:\\/\\/some_website.co.uk/cms/product_image/some_image.jpg",
"url": "https:\\/\\/some_website.co.uk/cms/product_image/some_image.jpg",
"width": 750
}
}
}
"""
let json = jsonData.data(using: .utf8)!
public struct Results: Codable {
public var images: [String:Image] = [:]
enum CodingKeys: String, CodingKey {
case images = "images"
}
}
public struct Image: Codable {
public var src: String = ""
public var url: String = ""
public var width: Int = 0
enum CodingKeys: String, CodingKey {
case src = "src"
case url = "url"
case width = "width"
}
}
if let results = try? JSONDecoder().decode(Results.self, from: json) {
let imagesDict = results.images
for (key, value) in imagesDict {
print("Key: \(key)")
print("Value: \(value)")
}
}
If you try this snippet it will give you this output printed:
Key: 750
Value: Image(src: "https://some_website.co.uk/cms/product_image/some_image.jpg", url: "https://some_website.co.uk/cms/product_image/some_image.jpg", width: 750)
You can try out the snippet above online, if you copy paste it here and run it: http://online.swiftplayground.run/
### UPDATE (in response to comment)
In response to your comment, I found it easier to setup another example to show you how you can achieve that with your exact code sample that you shared in the comment itself.
I left everything as class and just added images in order to leave you an overview on how to achieve that.
In the end, I'd suggest to rename Products and Attributes into Product and Attribute. Also if there is no strong reason on why you choosed the model to be class, just change them to struct and as well if there is no strong reasons to keep most of the attributes of each model optional give them a default value as I did in the example above if you are always expecting some values/attributes to be there.
You can try and run this snippet as well in http://online.swiftplayground.run to try it out:
import Foundation
let jsonData = """
{
"data": [
{
"title": "titlex",
"description": "descx",
"list_price": "123,456",
"attributes": [
{
"title": "titlex",
"unit": "unitx",
"value": "valuex"
}
],
"images": {
"750": {
"src": "https:\\/\\/some_website.co.uk/cms/product_image/some_image.jpg",
"url": "https:\\/\\/some_website.co.uk/cms/product_image/some_image.jpg",
"width": 750
}
}
}
]
}
"""
let json = jsonData.data(using: .utf8)!
class AllProducts: Codable {
let data: [Products]
init(data: [Products]) {
self.data = data
}
}
class Products: Codable {
let title: String?
let description: String?
let list_price: String?
let attributes: [Attributes]?
let images: [String:Image]?
init(title: String, description: String, list_price: String, attributes: [Attributes], images: [String:Image]) {
self.title = title
self.description = description
self.list_price = list_price
self.attributes = attributes
self.images = images
}
}
class Attributes: Codable {
let title: String?
let unit: String?
let value: String?
init(title: String, unit: String, value: String) {
self.title = title
self.unit = unit
self.value = value
}
}
class Image: Codable {
let src: String?
let url: String?
let width: Int?
init(src: String, url: String, width: Int) {
self.src = src
self.url = url
self.width = width
}
}
// parsing/decoding
if let results = try? JSONDecoder().decode(AllProducts.self, from: json) {
if let imagesDict = results.data[0].images {
// there is an "images" for product at position 0 (the only one in my json example)
for (key, value) in imagesDict {
print("Key: \(key)")
print("Value src: \(value.src)")
print("Value url: \(value.url)")
print("Value width: \(value.width)")
}
}
}
Output
Key: 750
Value src: Optional("https://some_website.co.uk/cms/product_image/some_image.jpg")
Value url: Optional("https://some_website.co.uk/cms/product_image/some_image.jpg")
Value width: Optional(750)
I am trying to parse json data in decodable in swift 4. It prints nil value. i could not find what issue is this?
here is model class:
public struct TaskID: Decodable {
let embedded: Embedded?
let count: Int?
enum CodingKeys: String, CodingKey {
case count = "count"
case embedded = "_embedded"
}
}
public struct Embedded: Decodable {
let task: [Task]?
enum CodingKeys: String, CodingKey {
case task = "task"
}
}
public struct Task : Decodable {
let id : String?
let name: String?
let assignee: String?
let created: String?
let processDefinitionId: String?
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case assignee = "assignee"
case created = "created"
case processDefinitionId = "processDefinitionId"
}
}
Here is json:
{
"_links": {
"self": {
"href": "/task"
}
},
"_embedded": {
"task": [
{
"_links": {
"assignee": {
"href": "/user/demo"
},
"execution": {
"href": "/execution/1b64cf75-0616-11ea-8860-120ef5ab2c25"
},
"identityLink": {
"href": "/task/1b64f688-0616-11ea-8860-120ef5ab2c25/identity-links"
},
"processDefinition": {
"href": "/process-definition/quickEvaluation:1:129ce2b1-0616-11ea-8860-120ef5ab2c25"
},
"processInstance": {
"href": "/process-instance/1b64cf75-0616-11ea-8860-120ef5ab2c25"
},
"self": {
"href": "/task/1b64f688-0616-11ea-8860-120ef5ab2c25"
}
},
"_embedded": {
"variable": []
},
"id": "1b64f688-0616-11ea-8860-120ef5ab2c25",
"name": "Quick Evaluation",
"assignee": "demo",
"created": "2019-11-13T13:04:20.687+0000",
"due": null,
"followUp": null,
"delegationState": null,
"description": null,
"executionId": "1b64cf75-0616-11ea-8860-120ef5ab2c25",
"owner": null,
"parentTaskId": null,
"priority": 50,
"processDefinitionId": "quickEvaluation:1:129ce2b1-0616-11ea-8860-120ef5ab2c25",
"processInstanceId": "1b64cf75-0616-11ea-8860-120ef5ab2c25",
"taskDefinitionKey": "QuickEvaluation",
"caseExecutionId": null,
"caseInstanceId": null,
"caseDefinitionId": null,
"suspended": false,
"formKey": "a8apps:suryoday:gng:v0.1.0:kycUpload",
"tenantId": null
},
{
"_links": {
"assignee": {
"href": "/user/demo"
},
"execution": {
"href": "/execution/412a03b7-06ae-11ea-8860-120ef5ab2c25"
},
"identityLink": {
"href": "/task/412a2aca-06ae-11ea-8860-120ef5ab2c25/identity-links"
},
"processDefinition": {
"href": "/process-definition/quickEvaluation:1:129ce2b1-0616-11ea-8860-120ef5ab2c25"
},
"processInstance": {
"href": "/process-instance/412a03b7-06ae-11ea-8860-120ef5ab2c25"
},
"self": {
"href": "/task/412a2aca-06ae-11ea-8860-120ef5ab2c25"
}
},
"_embedded": {
"variable": [
{
"_links": {
"self": {
"href": "/process-instance/412a03b7-06ae-11ea-8860-120ef5ab2c25/variables/loanAmount"
}
},
"_embedded": null,
"name": "loanAmount",
"value": "650000",
"type": "String",
"valueInfo": {}
},
{
"_links": {
"self": {
"href": "/process-instance/412a03b7-06ae-11ea-8860-120ef5ab2c25/variables/firstName"
}
},
"_embedded": null,
"name": "firstName",
"value": "Kamesh",
"type": "String",
"valueInfo": {}
}
]
},
"id": "412a2aca-06ae-11ea-8860-120ef5ab2c25",
"name": "Quick Evaluation",
"assignee": "demo",
"created": "2019-11-14T07:13:27.558+0000",
"due": null,
"followUp": null,
"delegationState": null,
"description": null,
"executionId": "412a03b7-06ae-11ea-8860-120ef5ab2c25",
"owner": null,
"parentTaskId": null,
"priority": 50,
"processDefinitionId": "quickEvaluation:1:129ce2b1-0616-11ea-8860-120ef5ab2c25",
"processInstanceId": "412a03b7-06ae-11ea-8860-120ef5ab2c25",
"taskDefinitionKey": "QuickEvaluation",
"caseExecutionId": null,
"caseInstanceId": null,
"caseDefinitionId": null,
"suspended": false,
"formKey": "a8apps:suryoday:gng:v0.1.0:kycUpload",
"tenantId": null
}
]
},
"count": 13
}
Here is urlrequest:
// MARK: - URLRequestConvertible
func asURLRequest() throws -> URLRequest {
let url = try K.ProductionServer.baseURL.asURL()
var urlRequest = URLRequest(url: url.appendingPathComponent(path))
print(urlRequest)
// HTTP Method
urlRequest.httpMethod = method.rawValue
let authToken = UserDefaults.standard.string(forKey: "authToken")
let bearerToken: String = "Bearer " + (authToken ?? "")
print("baearer token::\(bearerToken)")
// Common Headers
urlRequest.setValue(ContentType.json.rawValue, forHTTPHeaderField: HTTPHeaderField.acceptType.rawValue)
urlRequest.setValue(ContentType.json.rawValue, forHTTPHeaderField: HTTPHeaderField.contentType.rawValue)
urlRequest.setValue(bearerToken, forHTTPHeaderField: HTTPHeaderField.authentication.rawValue)
// Parameters
if let parameters = parameters {
do {
urlRequest.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: [])
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
}
return urlRequest
}
Here is alamofire request:
import Foundation
import Alamofire
public class APIClient {
#discardableResult
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 taskID(id: String, completion:#escaping (AFResult< [TaskID]>)->Void) {
performRequest(route: APIRouter.TaskById(id: id), completion: completion)
}
}//APIClient
Initially it shows Dictionary, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary but found an array instead. but now i'm getting nil in console log. Do not know why i'm getting nil value. Is struct is correct based on my json response? I'm struggling with getting the nested data.
Any help much appreciated pls..
Your struct is correct. See playground below for proof; I don't get an error. nil is very common result when you don't have the entire data set and you assume that some optional field in the response is non optional just because you see it in your sample data (your sample is not necessarily representative). Absent an actual spec from the server, you need to figure out what field is failing to decode and you may need to get a bunch of data to figure out which fields are really optional. You can do that by either putting in error handling code in your AF project above or just by pasting your response into my playground below. Either way the decoding error should tell you which field was not present.
import PlaygroundSupport
import UIKit
import WebKit
public struct TaskID: Decodable {
let embedded: Embedded?
let count: Int?
enum CodingKeys: String, CodingKey {
case count = "count"
case embedded = "_embedded"
}
}
public struct Embedded: Decodable {
let task: [Task]?
enum CodingKeys: String, CodingKey {
case task = "task"
}
}
public struct Task : Decodable {
let id : String?
let name: String?
let assignee: String?
let created: String?
let processDefinitionId: String?
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case assignee = "assignee"
case created = "created"
case processDefinitionId = "processDefinitionId"
}
}
let data = """
{
"_links": {
"self": {
"href": "/task"
}
},
"_embedded": {
"task": [
{
"_links": {
"assignee": {
"href": "/user/demo"
},
"execution": {
"href": "/execution/1b64cf75-0616-11ea-8860-120ef5ab2c25"
},
"identityLink": {
"href": "/task/1b64f688-0616-11ea-8860-120ef5ab2c25/identity-links"
},
"processDefinition": {
"href": "/process-definition/quickEvaluation:1:129ce2b1-0616-11ea-8860-120ef5ab2c25"
},
"processInstance": {
"href": "/process-instance/1b64cf75-0616-11ea-8860-120ef5ab2c25"
},
"self": {
"href": "/task/1b64f688-0616-11ea-8860-120ef5ab2c25"
}
},
"_embedded": {
"variable": []
},
"id": "1b64f688-0616-11ea-8860-120ef5ab2c25",
"name": "Quick Evaluation",
"assignee": "demo",
"created": "2019-11-13T13:04:20.687+0000",
"due": null,
"followUp": null,
"delegationState": null,
"description": null,
"executionId": "1b64cf75-0616-11ea-8860-120ef5ab2c25",
"owner": null,
"parentTaskId": null,
"priority": 50,
"processDefinitionId": "quickEvaluation:1:129ce2b1-0616-11ea-8860-120ef5ab2c25",
"processInstanceId": "1b64cf75-0616-11ea-8860-120ef5ab2c25",
"taskDefinitionKey": "QuickEvaluation",
"caseExecutionId": null,
"caseInstanceId": null,
"caseDefinitionId": null,
"suspended": false,
"formKey": "a8apps:suryoday:gng:v0.1.0:kycUpload",
"tenantId": null
},
{
"_links": {
"assignee": {
"href": "/user/demo"
},
"execution": {
"href": "/execution/412a03b7-06ae-11ea-8860-120ef5ab2c25"
},
"identityLink": {
"href": "/task/412a2aca-06ae-11ea-8860-120ef5ab2c25/identity-links"
},
"processDefinition": {
"href": "/process-definition/quickEvaluation:1:129ce2b1-0616-11ea-8860-120ef5ab2c25"
},
"processInstance": {
"href": "/process-instance/412a03b7-06ae-11ea-8860-120ef5ab2c25"
},
"self": {
"href": "/task/412a2aca-06ae-11ea-8860-120ef5ab2c25"
}
},
"_embedded": {
"variable": [
{
"_links": {
"self": {
"href": "/process-instance/412a03b7-06ae-11ea-8860-120ef5ab2c25/variables/loanAmount"
}
},
"_embedded": null,
"name": "loanAmount",
"value": "650000",
"type": "String",
"valueInfo": {}
},
{
"_links": {
"self": {
"href": "/process-instance/412a03b7-06ae-11ea-8860-120ef5ab2c25/variables/firstName"
}
},
"_embedded": null,
"name": "firstName",
"value": "Kamesh",
"type": "String",
"valueInfo": {}
}
]
},
"id": "412a2aca-06ae-11ea-8860-120ef5ab2c25",
"name": "Quick Evaluation",
"assignee": "demo",
"created": "2019-11-14T07:13:27.558+0000",
"due": null,
"followUp": null,
"delegationState": null,
"description": null,
"executionId": "412a03b7-06ae-11ea-8860-120ef5ab2c25",
"owner": null,
"parentTaskId": null,
"priority": 50,
"processDefinitionId": "quickEvaluation:1:129ce2b1-0616-11ea-8860-120ef5ab2c25",
"processInstanceId": "412a03b7-06ae-11ea-8860-120ef5ab2c25",
"taskDefinitionKey": "QuickEvaluation",
"caseExecutionId": null,
"caseInstanceId": null,
"caseDefinitionId": null,
"suspended": false,
"formKey": "a8apps:suryoday:gng:v0.1.0:kycUpload",
"tenantId": null
}
]
},
"count": 13
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
do {
let decoded = try decoder.decode(TaskID.self, from: data)
print(decoded)
} catch ( let error ) {
print(error.localizedDescription)
}
import Foundation
// MARK: - Task
struct Task: Codable {
let links: VariableLinks?
let embedded: TaskEmbedded?
let count: Int?
enum CodingKeys: String, CodingKey {
case links = "_links"
case embedded = "_embedded"
case count = "count"
}
}
// MARK: - TaskEmbedded
struct TaskEmbedded: Codable {
let task: [TaskElement]?
enum CodingKeys: String, CodingKey {
case task = "task"
}
}
// MARK: - TaskElement
struct TaskElement: Codable {
let links: TaskLinks?
let embedded: TaskEmbeddedClass?
let id: String?
let name: String?
let assignee: String?
let created: String?
let due: String?
let followUp: String?
let delegationState: String?
let taskDescription: String?
let executionId: String?
let owner: String?
let parentTaskId: Int?
let priority: Int?
let processDefinitionId: String?
let processInstanceId: String?
let taskDefinitionKey: String?
let caseExecutionId: Int?
let caseInstanceId: Int?
let caseDefinitionId: Int?
let suspended: Bool?
let formKey: String?
let tenantId: Int?
enum CodingKeys: String, CodingKey {
case links = "_links"
case embedded = "_embedded"
case id = "id"
case name = "name"
case assignee = "assignee"
case created = "created"
case due = "due"
case followUp = "followUp"
case delegationState = "delegationState"
case taskDescription = "description"
case executionId = "executionId"
case owner = "owner"
case parentTaskId = "parentTaskId"
case priority = "priority"
case processDefinitionId = "processDefinitionId"
case processInstanceId = "processInstanceId"
case taskDefinitionKey = "taskDefinitionKey"
case caseExecutionId = "caseExecutionId"
case caseInstanceId = "caseInstanceId"
case caseDefinitionId = "caseDefinitionId"
case suspended = "suspended"
case formKey = "formKey"
case tenantId = "tenantId"
}
}
// MARK: - TaskEmbeddedClass
struct TaskEmbeddedClass: Codable {
let variable: [Variable]?
enum CodingKeys: String, CodingKey {
case variable = "variable"
}
}
// MARK: - Variable
struct Variable: Codable {
let links: VariableLinks?
let embedded: String?
let name: String?
let value: String?
let type: String?
let valueInfo: ValueInfo?
enum CodingKeys: String, CodingKey {
case links = "_links"
case embedded = "_embedded"
case name = "name"
case value = "value"
case type = "type"
case valueInfo = "valueInfo"
}
}
// MARK: - VariableLinks
struct VariableLinks: Codable {
let linksSelf: SelfClass?
enum CodingKeys: String, CodingKey {
case linksSelf = "self"
}
}
// MARK: - SelfClass
struct SelfClass: Codable {
let href: String?
enum CodingKeys: String, CodingKey {
case href = "href"
}
}
// MARK: - ValueInfo
struct ValueInfo: Codable {
}
// MARK: - TaskLinks
struct TaskLinks: Codable {
let assignee: SelfClass?
let execution: SelfClass?
let identityLink: SelfClass?
let processDefinition: SelfClass?
let processInstance: SelfClass?
let linksSelf: SelfClass?
enum CodingKeys: String, CodingKey {
case assignee = "assignee"
case execution = "execution"
case identityLink = "identityLink"
case processDefinition = "processDefinition"
case processInstance = "processInstance"
case linksSelf = "self"
}
}
Also, if you feeling lazy you can use quicktype.io, As nil is a very common response from decodable, you can you an extension for DataRequest to parse and get the values from the request just like using responseDecodable, here you can use something like this,
extension DataRequest {
fileprivate func decodableResponseSerializer<T: Decodable>() -> DataResponseSerializer<T> {
return DataResponseSerializer { _, response, data, error in
guard error == nil else { return .failure(error!) }
guard let data = data else {
return .failure(AFError.responseSerializationFailed(reason: .inputDataNil))
}
return Result { try newJSONDecoder().decode(T.self, from: data) }
}
}
#discardableResult
fileprivate func responseDecodable<T: Decodable>(queue: DispatchQueue? = nil, completionHandler: #escaping (DataResponse<T>) -> Void) -> Self {
return response(queue: queue, responseSerializer: decodableResponseSerializer(), completionHandler: completionHandler)
}
}
and then you can call it like this
Alamofire.request(url, method:.post, parameters: parameters, headers: headers)
.responseDecodable { (response: DataResponse< Task >) in completion(response.result) }
as you were getting something like this, it only comes when you have a different type of data rather than which you have specified.
I have hit a wall that I cant find the way around. I have a JSON file that I am trying to decode but there is a part of the JSON that is missing. I always get nil even though I know that there is supposed to be data there.
This is the first part of the JSON file
{
"type": "FeatureCollection",
"name": "points",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature",
"properties": { "osm_id": "755",
"name": "Majorstuen",
"other_tags": "\"bus\"=>\"yes\",\"network\"=>\"Ruter\",\"public_transport\"=>\"stop_position\"" },
"geometry": { "type": "Point", "coordinates": [ 10.7160479, 59.9296625 ] } },
{ "type": "Feature",
"properties": { "osm_id": "1188",
"name": "Arnes",
"highway": "bus_stop",
"other_tags": "\"ref:nsrq\"=>\"82139\"" },
"geometry": { "type": "Point", "coordinates": [ 16.919517, 68.3459 ] } },
{ "type": "Feature",
"properties": { "osm_id": "1194",
"name": "Skjellneset",
"highway": "bus_stop",
"other_tags": "\"ref:nsrq\"=>\"82148\"" },
"geometry": { "type": "Point", "coordinates": [ 16.938766, 68.34916 ] } },
{ "type": "Feature",
"properties": { "osm_id": "1202",
"name": "Osen",
"highway": "bus_stop",
"other_tags": "\"ref:nsrq\"=>\"82152\"" },
"geometry": { "type": "Point", "coordinates": [ 16.952982, 68.352551 ] } }
]}
Here is my Struct:
struct This:Codable {
let type: String?
let name: String?
let crs: CRS
let features: [Features]
struct CRS: Codable {
let type: String?
let properties: CRSProperties
struct CRSProperties: Codable {
let name: String?
enum CodingKeys: String, CodingKey {
case name
}
}
enum CodingKeys: String, CodingKey {
case type
case properties
}
}
struct Features: Codable {
let type: String?
let properties: FeaturesProperties
struct FeaturesProperties: Codable {
let osmId: String?
let name: String?
let highway: String?
let otherTags: String?
let geo: FeaturesPropertiesGeometry?
struct FeaturesPropertiesGeometry: Codable {
let type: String
let coordinates: [Double]
enum CodingKeys: String, CodingKey {
case type
case coordinates
}
}
enum CodingKeys: String, CodingKey {
case osmId = "osm_id"
case name
case highway
case otherTags = "other_tags"
case geo = "geometry"
}
}
enum CodingKeys: String, CodingKey {
case type
case properties
}
}
}
Then here is what I use to decode the JSON
guard let asset = NSDataAsset(name: "NN") else {
print("NN JSON not here")
return
}
do {
let deco = JSONDecoder()
this = try deco.decode(This.self, from: asset.data)
}catch {
print("Error: \(error)")
}
My problem is that I dont get the geometry data into "this: This". If I print every line then from "this" then the result for "geo" is always nil.
This is a printed line.
"Features(type: Optional("Feature"), properties: mokkingaroud.This.Features.FeaturesProperties(osmId: Optional("10587816"), name: Optional("YX Evje"), highway: nil, otherTags: Optional("\"amenity\"=>\"fuel\",\"branch\"=>\"Evje\",\"brand\"=>\"YX\",\"email\"=>\"evje#st.yx.no\",\"fuel:adblue\"=>\"yes\",\"fuel:diesel\"=>\"yes\",\"fuel:HGV_diesel\"=>\"yes\",\"fuel:octane_95\"=>\"yes\",\"fuel:taxfree_diesel\"=>\"yes\",\"hgv\"=>\"yes\",\"phone\"=>\"+47 37 92 95 60\",\"ref:yx\"=>\"561\""), geo: nil))"
Any suggestions please.
Jonas
Because coordinates is not a String I'd use:
struct This:Codable {
let name, type: String?
let features: [Features]
}
struct Features: Codable {
let type: String?
let properties: [Properties]
let geometry: [Geometry]
}
struct Properties:Codable {
let name, osm_id, other_tags: String?
}
struct Geometry:Codable {
let type: String?
let coordinates: [Double]
}