swift 4 - json decode object with tree structure - json

I want to decode my user json object, but I have my difficulties with decoding the superior object. The superior object is just another user that stands above the user. The structure looks like this
{
"id": 3,
"username": "a",
"email": "a#abc.com",
"userFunction": "4",
"password": "****",
"superior": {
"id": 2,
"username": "b",
"email": "b#abc.com",
"userFunction": "3",
"password": "****",
"superior": {
"id": 1,
"username": "c",
"email": "c#abc.com",
"userFunction": "1",
"password": "****",
"superior": null,
},
},
}
struct UserStructure: Decodable {
var id: Int64?
var username: String?
var email: String?
var userFunction: UserFunctionStructure?
var password: String?
var superior: UserStructure?
}
func fetchUser(username: String){
let urlString = "http://localhost:8080/rest/user/" + username
let url = URL(string: urlString)!
URLSession.shared.dataTask(with: url) { (data, response, error) -> Void in
if error != nil {
print(error!)
return
}
guard let data = data else {
return
}
do {
let user = try JSONDecoder().decode(UserStructure.self, from: data)
print(user)
} catch let err {
print(err)
}
}.resume()
}
When I set the type of superior to "UserStructure?" I get the error "Value Type 'UserStructure" cannot have a stored property that recursively contains it. I thought about creating a SuperiorStructure but then I would have the same problem a step further.

Like the compiler error message says, structs cannot have properties that contain an instance of themselves. Use class in this case:
class UserStructure: Decodable {
var id: Int64?
var username: String?
var email: String?
var userFunction: UserFunctionStructure?
var password: String?
var superior: UserStructure?
}

Related

How to decode nested Json with Swift?

I have been trying to decode this Json data but I'm not able to do it completly :
This is my sample json data :
{
"id": 10644,
"name": "CP2500",
"numberOfConnectors": 2,
"connectors": [
{
"id": 59985,
"name": "CP2500 - 1",
"maxchspeed": 22.08,
"connector": 1,
"description": "AVAILABLE"
},
{
"id": 59986,
"name": "CP2500 - 2",
"maxchspeed": 22.08,
"connector": 2,
"description": "AVAILABLE"
}
]
}
this is my struct :
`
struct Root: Codable {
var id: Int
var name: String
var numberOfConnectors: Int
var connectors: [Connector]
}
struct Connector: Codable {
var id: Int
var name: String
var maxchspeed: Double
var connector: Int
var connectorDescription: String
enum CodingKeys: String, CodingKey {
case id, name, maxchspeed, connector
case connectorDescription = "description"
}
}
I want to parse the element within the [Connector] array but I'm just getting the elements of the Root level :
let jsonData = array.data(using: .utf8)!
let root = try JSONDecoder().decode(Root.self, from: jsonData)
print("\(root.id)")
Any idea how to do this ?
do {
let root = try JSONDecoder().decode(Root.self, from: jsonData)
print("root id : \(root.id)")
root.connectors.forEach {
print("name : \($0.name),"," connector id : \($0.id),","status : \($0.description)");
}
} catch {
print(error.localizedDescription)
}

Simple Swift UI Json App - decoding issue

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)

function for json parsing returns decode error

I am new to Swift and currently I am learning about JSON parsing. I found some example json file and i wanted to decode into object, but I am getting error which I cant find in my code.
JSON FILE
{
"apiVersion": "v1",
"context": "success",
"httpCode": 200,
"method": "GET",
"baseUrl": "http://expecting.devtvornica.org",
"uri": "api/v1/baby-names/get?country=hr",
"queryParameters": {
"country": "hr",
"order_by": "name",
"sort_order": "ASC"
},
"data": {
"favorites": [
{
"id": 962,
"name": "Adam",
"country": "hr",
"gender": "male",
"is_favorite": false
},
{
"id": 930,
"name": "Adrian",
"country": "hr",
"gender": "male",
"is_favorite": false
},
{
"id": 974,
"name": "BAdriano",
"country": "hr",
"gender": "male",
"is_favorite": true
},
{
"id": 959,
"name": "CAlen",
"country": "hr",
"gender": "male",
"is_favorite": false
},
{
"id": 985,
"name": "CAna",
"country": "hr",
"gender": "female",
"is_favorite": true
},
]
}
}
RESPONSE CLASS
import Foundation
public class Response <T: Codable>: Codable{
public let apiVersion: String?
public let context: String?
public let httpCode: Int?
public let method: String?
public let baseUrl: String?
public let uri: String?
public let message: String?
public let queryParameters: [QueryParameters]?
public let data:T?
public init(data: T, apiVersion: String, context: String, httpCode: Int, method: String, baseUrl: String, uri: String, message: String, queryParameters: [QueryParameters]){
self.apiVersion = apiVersion
self.context = context
self.httpCode = httpCode
self.method = method
self.baseUrl = baseUrl
self.uri = uri
self.message = message
self.queryParameters = queryParameters
self.data = data
}
}
STRUCT FILES
import Foundation
public struct BabyFavorite: Codable {
public let favorites: [BabyName]
}
import Foundation
public struct BabyName: Codable {
public let id: Int
public let name: String
public let country: String
public let gender: String
public let isFavorite: Bool
}
VIEWCONTROLLER
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
getBabyNames()
}
}
extension ViewController {
private func getBabyNames(){
if let localData = self.readLocalField(forName: "get_baby_names_country_success"),
let responseData: Response <BabyFavorite> = parse(jsonData: localData),
let data = responseData.data {
handleBabyNames(data: data)
}
}
private func handleBabyNames(data: BabyFavorite){
data.favorites.forEach { (babyname) in
print(babyname)
}
}
}
extension ViewController {
static let jsonDecoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}()
private func readLocalField(forName name: String) -> Data? {
do {
if let bundlePath = Bundle.main.path(forResource: name, ofType: "json"),
let jsonData = try String(contentsOfFile: bundlePath).data(using: .utf8){
return jsonData
}
}catch{
print(error)
}
return nil
}
private func parse<T: Codable>(jsonData: Data) -> T?{
let object: T?
do {
object = try ViewController.jsonDecoder.decode(T.self, from: jsonData)
} catch {
print("decode error")
object = nil
}
return object
}
}
My parse function is always catching error.
I found by breakpoint that line
let data = responseData.data {
handleBabyNames(data: data)
doesnt execute, but I dont know how to actually fix this.
queryParameters is a object and u are decoding it as array
public let queryParameters: QueryParameters?

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 IOS Failed to access json array data using Codable

How to access JSON using Codable. this is my sample json.
{
"status": "success",
"message": "Data received successfully",
"course": {
"id": 1,
"description": "something",
"name": "ielts",
"attachments": [
{
"id": 809,
"attachment": "https:--",
"file_name": "syllabus.pdf",
"description": "course",
},
{
"id": 809,
"attachment": "https:--",
"file_name": "syllabus.pdf",
"description": "course",
}]
"upcased_name": "IELTS"
}
}
This is my code.
struct ResponseObject: Codable {
let course: [Course]
}
struct Course: Codable {
let id: Int
let name: String
let description: String
let attachments: [Attachments]
}
struct Attachments: Codable {
let id: Int
let attachment: String
let file_name: String
let description: String
let about: String
}
var course: [Course] = []
This is my api call.
func fetchUserData() {
let headers: HTTPHeaders = [
"Authorization": "Token token="+UserDefaults.standard.string(forKey: "auth_token")!,
"Accept": "application/json"
]
let params = ["course_id" : "1"] as [String : AnyObject]
self.showSpinner("Loading...", "Please wait!!")
DispatchQueue.global(qos: .background).async {
AF.request(SMAConstants.courses_get_course_details , parameters: params, headers:headers ).responseDecodable(of: ResponseObject.self, decoder: self.decoder) { response in
DispatchQueue.main.async {
self.hideSpinner()
guard let value = response.value else {
print(response.error ?? "Unknown error")
return
}
self.course = value.course
}
}
}
}
}
I am getting following error.
responseSerializationFailed(reason:
Alamofire.AFError.ResponseSerializationFailureReason.decodingFailed(error:
Swift.DecodingError.typeMismatch(Swift.Array,
Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue:
"course", intValue: nil)], debugDescription: "Expected to decode
Array but found a dictionary instead.", underlyingError: nil))))
Your model object does not match the JSON structure. Try this instead:
struct ResponseObject: Codable {
let status, message: String
let course: Course
}
struct Course: Codable {
let id: Int
let courseDescription, name: String
let attachments: [Attachment]
let upcasedName: String
enum CodingKeys: String, CodingKey {
case id
case courseDescription = "description"
case name, attachments
case upcasedName = "upcased_name"
}
}
struct Attachment: Codable {
let id: Int
let attachment, fileName, attachmentDescription: String
enum CodingKeys: String, CodingKey {
case id, attachment
case fileName = "file_name"
case attachmentDescription = "description"
}
}
and to download and parse this with plain Swift and Foundation, use code like this:
let url = URL(string: SMAConstants.courses_get_course_details)!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print(error)
return
}
if let data = data {
do {
let response = try JSONDecoder().decode(ResponseObject.self, from: data)
// access your data here
} catch {
print(error)
}
}
}
task.resume()