Simple Swift UI Json App - decoding issue - json

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)

Related

fetch api data with dataclass

I've given an API with dataclass, I want to fetch the dataclass, I tried to search up on internet but coulnd't find the answer, after that I tried to fetch it with my own, but my code doesn't seem to work. any ideas ?
"restricions": {
"restrictionsByNationality": [
{
"type": "Visit Type",
"data": {
"allowsTourists": true,
"allowsBusinessVisit": true
}
},
{
"type": "Covid Test",
"data": {
"pcrRequired": true,
"fastTestRequired": true
}
},
{
"type": "Documents Required",
"data": {
"biometricPassportRequired": true,
"locatorFormRequired": true,
"covidPassportRequired": false
}
}
]
},
if let nationality = data["restrictionsByNationality"] as? [String: Any]{
nationality = DataClass(from: nationality)
}
struct RestrictionsByNationality: Codable {
let type: String
let data: DataClass
}
// MARK: - DataClass
struct DataClass: Codable {
let allowsTourists,
allowsBusinessVisit, pcrRequired, fastTestRequired: Bool?
let biometricPassportRequired, locatorFormRequired, covidPassportRequired: Bool?
}
}

Create JSON object with Swift Xcode

I have a JSON object below that uses everything from Strings, Bools and Int's. I'm currently having a difficult time recreating the person_details section of the object and I think because it's in brackets and has multiple values, like [String: Bool], [String: String] & [String: Int] ?
I posted towards the bottom what populates on the console, but any help structuring there person_details section in the would be great.
You'll see below, in my let order, I'm structuring the data.
let testJson = """
{
"household": {
"region": "PA",
"household_size": 1,
"receiving_benefits": [
],
"energy_crisis": false,
"utility_providers": [
"peco"
],
"residence_type": "other",
"property_tax_past_due": false,
"home_needs_repairs": false,
"filed_previous_year_tax_return": false,
"heating_system_needs_repairs": false,
"at_risk_of_homelessness": false,
"received_maximum_benefit": {
"cip": false
},
"person_details": [
{
"age": 18,
"marital_status": "single",
"minimum_employment_over_extended_period": false,
"work_status": "recent_loss",
"pregnant": false,
"attending_school": false,
"disabled": false
}
],
"incomes": [
{
"gross_monthly_amount": 700,
"countable_group": "household",
"year": "current"
},
{
"gross_monthly_amount": 700,
"countable_group": "household",
"year": "previous"
}
],
"assets": [
{
"amount": 1000,
"countable_group": "household"
}
]
}
}
"""
struct Eligibility: Encodable {
let residence: String
let hhmembers: Int
let receivingBen: [String]
let unhoused: Bool
let utilityType: [String]
let residenceType: String
let propertyTax: Bool
let homeRepairs: Bool
let fileLastTax: Bool
let heatRepairs: Bool
let receivingMax: [String: Bool]
enum CodingKeys: String, CodingKey {
case residence = "region"
case hhmembers = "household_size"
case receivingBen = "receiving_benefits"
case unhoused = "at_risk_of_homelessness"
case utilityType = "utility_providers"
case residenceType = "residence_type"
case propertyTax = "property_tax_past_due"
case homeRepairs = "home_needs_repairs"
case fileLastTax = "filed_previous_year_tax_return"
case heatRepairs = "heating_system_needs_repairs"
case receivingMax = "received_maximum_benefit"
}
}
struct PersonDetails: Encodable {
let age: Int
// let marital_status: String
// let minimum_employment_over_extended_period: Bool
// let work_status: String
// let pregnant: Bool
// let attending_school: Bool
// let disabled: Bool
enum CodingKeys: String, CodingKey {
case age = "age"
// case marital_status = "marital_status"
// case minimum_employment_over_extended_period = "minimum_employment_over_extended_period"
// case work_status = "work_status"
// case pregnant = "pregnant"
// case attending_school = "attending_school"
// case disabled = "disabled"
}
}
I believe What I'm missing is inside the let order = , see below:
struct Order: Encodable {
let household: Eligibility
let person_details: PersonDetails
}
let order = Order(household: Eligibility(residence: "PA", hhmembers: 1, receivingBen: [], unhoused: false, utilityType: ["Peco"], residenceType: "other", propertyTax: false, homeRepairs: false, fileLastTax: false, heatRepairs: false, receivingMax: ["cip": false]), person_details: PersonDetails(age: 19))
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let orderJsonData = try! encoder.encode(order)
print(String(data: orderJsonData, encoding: .utf8)!)
Inside the Console shows that person_details in outside of 'household' but I would need the person_details inside of the household object as the above full JSON object shows at the top of the question (note square brackets too). Console below:
{
"household" : {
"region" : "PA",
"residence_type" : "other",
"at_risk_of_homelessness" : false,
"property_tax_past_due" : false,
"utility_providers" : [
"Peco"
],
"home_needs_repairs" : false,
"filed_previous_year_tax_return" : false,
"household_size" : 1,
"receiving_benefits" : [
],
"heating_system_needs_repairs" : false,
"received_maximum_benefit" : {
"cip" : false
}
},
"person_details" : {
"age" : 19
}
}
You've got the structure of your data hierarchy wrong converting from JSON to swift.
It should be...
struct Order: Codable {
let household: Household
}
struct Household: Codable {
let personDetails: [Person]
}
struct Person: Codable {
let age: Int
let maritalStatus: String
let minimumEmploymentOverExtendedPeriod: Bool
let workStatus: String
let pregnant: Bool
let attendingSchool: Bool
let disabled: Bool
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let order = try! decoder.decode(Order.self, from: Data(testJson.utf8))
returns
Person(age: 18, maritalStatus: "single",
minimumEmploymentOverExtendedPeriod: false, workStatus: "recent_loss",
pregnant: false, attendingSchool: false, disabled: false)]
Also worth pointing out is the use of the .keyDecodingStrategy to ease the converting from snake case. This saves defining the CodingKeys. Obviously this will only work where you're happy to keep the naming the same.

Successfully write JSON to Realm DB using swiftyJSON in Swift

I am trying to write to a Realm DB from JSON file (using swiftyJSON) get the following error:
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'RLMException', reason: 'Invalid value '{
"vehicle" : true,
"viewable" : true,
"id" : 0,
"weapon" : false,
"genres" : "[Fantasy, General]",
"name" : "Car"
}' to initialize object of type 'Item': missing key 'id''
My JSON file is structured as follows:
[
{
"id": 0,
"name": "Car",
"genres": "[Fantasy, General]",
"viewable": true,
"weapon": false,
"vehicle": true
},
{
"id": 1,
"name": "Truck",
"genres": "[General]",
"viewable": true,
"weapon": false,
"vehicle": true
},
]
My Realm DB Class is:
class Item: Object {
#objc dynamic var id: Int = 0
#objc dynamic var name = ""
let genres = List<String>()
#objc dynamic var visable: Bool = false
#objc dynamic var weapon: Bool = false
#objc dynamic var vehicle: Bool = false
override static func primaryKey() -> String? {
return "id"
}
override static func indexedProperties() -> [String] {
return ["genre", "visable"]
}
}
and the code to write the JSON to the RealmDB is as follows:
func jsonAdd() {
if let path = Bundle.main.path(forResource: "Data", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let json = JSON(data)
for (index,subJson):(String, JSON) in json {
do {
try realm.write {
realm.create(Item.self, value: subJson, update: .modified)
}
} catch let error as NSError {
print("Unable to write")
print(error)
}
}
} catch {
print("Error")
}
}
}
I think the issue is a JSON mapping one but any help will be greatly appreciated to write the JSON to the Realm DB including the genres as a List.
NB: I've managed to write to the Realm DB using the following code (based off the Realm documentation) before the 'Write' function but cannot seem to get it to work for my JSON structure.
let newdata = "{\"id\": 0, \"name\": \"Car\", \"genres\": [\"Fantasy\",\"General\"], \"viewable\": true, \"weapon\": false, \"vehicle\": true}".data(using: .utf8)!
let json = try! JSONSerialization.jsonObject(with: newdata, options: [])
Thank you!
Solved through the following approach:
Slight change to my JSON structure as follows:
[
{
"id": 0,
"name": "Car",
"genres": ["Fantasy", "General"],
"viewable": true,
"weapon": false,
"vehicle": true
},
{
"id": 1,
"name": "Truck",
"genres": ["Fantasy", "General"],
"viewable": true,
"weapon": false,
"vehicle": true
}
]
Created a new Genres Object to better handle the list
class Genres: Object {
#objc dynamic var id = 0
#objc dynamic var name = ""
override static func primaryKey() -> String? {
return "id"
}
}
class Item: Object {
#objc dynamic var id = 0
#objc dynamic var name = ""
var genres = List<Genres>()
#objc dynamic var viewable: Bool = false
#objc dynamic var weapon: Bool = false
#objc dynamic var vehicle: Bool = false
override static func primaryKey() -> String? {
return "id"
}
override static func indexedProperties() -> [String] {
return ["genres", "viewable"]
}
}
3)Crucially in the jsonADD function updated the code to create a new Item Object then mapped the JSON values to that object before attempting to write to the Realm DB:
for (key,subJson):(String, JSON) in jsonObjects {
let thisItem = Item()
thisItem.id = subJson["id"].intValue
thisItem.name = subJson["name"].stringValue
thisItem.visable = subJson["viewable"].boolValue
thisItem.weapon = subJson["weapon"].boolValue
thisItem.vehicle = subJson["vehicle"].boolValue
let genreArray = subJson["genres"].arrayValue
for genre in genreArray {
let string = genre.stringValue
let predicate = NSPredicate(format: "name = %#", string)
if let foundGenre = realm.objects(Genres.self).filter(predicate).first {
thisItem.genres.append(foundGenre)
}
}
do {
try realm.write {
realm.create(Item.self, value: thisItem, update: .modified)
}
} catch let error as NSError {
print("Unable to write")
print(error)
}
}

Swift: search for json key and edit it

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.

Accessing nested object from JSON when it is a dynamic number represented as string

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)