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)
}
}
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 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.
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?
When I hit my configuration API with Postman I am given the following json response back. In this response the two apiVersion keys are numbers and not strings.
{
"data": {
"availability": {
"auth": true,
"ab": true,
"cd": true
},
"helloWorldConfiguration": {
"apiKey": "abcefg",
"rootUrl": "https://foo",
"apiVersion": 3
},
"fooBarConfiguration": {
"baseUrl": "https://foo",
"apiVersion": 1,
"privateApiPath": "",
"publicApiPath": "dev",
"tokenPath": ""
}
},
"errors": []
}
When I try to decode it it fails with a typeMismatch error. When I output the contents of the response, I see the following which looks fine to me.
data = {
availability = {
auth = 1;
ab = 1;
cd = 1;
};
helloWorldConfiguration = {
apiVersion = 1;
baseUrl = "https://foo";
privateApiPath = "";
publicApiPath = dev;
tokenPath = "";
};
fooBarConfiguration = {
apiKey = abcefg;
apiVersion = 3;
rootUrl = "https://foo";
};
};
errors = (
);
The error given to me indicates that data.helloWorldConfiguration.apiVersion is of type string instead of int. We can see from the original HTTP response I get from Postman that's not the case.
typeMismatch(Swift.Int, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil), CodingKeys(stringValue: "helloWorldConfiguration", intValue: nil), CodingKeys(stringValue: "apiVersion", intValue: nil)], debugDescription: "Expected to decode Int but found a string/data instead.", underlyingError: nil))
21:17:40 ERROR Unable to decode the response data into a model representation.
My model represents those properties as integers so it would appear that it receives the response and considers those numbers to be strings, which they're not.
public struct ServerConfiguration: Decodable {
let availability: AvailabilityConfiguration
let helloWorldConfiguration: HelloWorldConfiguration
let fooBarConfiguration: FooBarConfiguration
init(availability: AvailabilityConfiguration, helloWorldConfiguration: HelloWorldConfiguration, fooBarConfiguration: FloatSinkConfiguration) {
self.availability = availability
self.helloWorldConfiguration = helloWorldConfiguration
self.fooBarConfiguration = fooBarConfiguration
}
}
public struct FooBarConfiguration: Decodable {
let baseUrl: String
let apiVersion: Int
let privateApiPath: String
let publicApiPath: String
let tokenPath: String
init(baseUrl: String, apiVersion: Int, privateApiPath: String, publicApiPath: String, tokenPath: String) {
self.baseUrl = baseUrl
self.apiVersion = apiVersion
self.privateApiPath = privateApiPath
self.publicApiPath = publicApiPath
self.tokenPath = tokenPath
}
}
public struct AvailabilityConfiguration: Decodable {
let auth: Bool
let ab: Bool
let cd: Bool
init(auth: Bool, ab: Bool, cd: Bool) {
self.auth = auth
self.ab = ab
self.cd = cd
}
}
public struct HelloWorldConfiguration: Codable {
let apiKey: String
let rootUrl: String
let apiVersion: Int
init(apiKey: String, rootUrl: String, apiVersion: Int) {
self.apiKey = apiKey
self.rootUrl = rootUrl
self.apiVersion = apiVersion
}
}
As you can see my apiVersion members are both of type integer along with the json response. What am I doing wrong here? I assume what's happening is Swift is considering the numbers in the json string, regardless of how they're actually represented in the json. Is that the case?
Edit to show utf8 string of Alamofire response data
21:44:06 INFO GET: https:foo/configuration
{
"data" : {
"availability" : {
"auth" : true,
"ab" : true,
"cb" : true
},
"helloWorldConfiguration" : {
"apiKey" : "abcd",
"rootUrl" : "https://foo",
"apiVersion" : "3"
},
"fooBarConfiguration" : {
"baseUrl" : "https://foo",
"apiVersion" : "1",
"privateApiPath" : "",
"publicApiPath" : "dev",
"tokenPath" : "auth/token"
}
},
"errors" : []
}
It would seem that despite the API correctly returning apiVersion as a number, Swift is turning it into a string. Am I decoding it incorrectly?
func getRoute<TResponseData: Decodable>(route:String, completion: #escaping (TResponseData) -> Void) throws {
let headers = try! self.getHeaders(contentType: ContentType.json)
let completeUrl: String = self.getUrl(route: route, requestUrl: nil)
logger.info("GET: \(completeUrl)")
Alamofire.request(
completeUrl,
method: .get,
parameters: nil,
encoding: JSONEncoding.default,
headers: headers)
.validate()
.responseJSON { (response) -> Void in
self.logger.info("GET Response: \(String(describing:response.response?.statusCode))")
switch response.result {
case .success(_):
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom(Date.toFooBarDate)
do {
let result = try decoder.decode(TResponseData.self, from: response.data!)
completion(result)
} catch DecodingError.dataCorrupted(let error) {
self.logger.error(error.underlyingError!)
return
} catch {
print(response.result.value!)
print(error)
self.logger.error("Unable to decode the response data into a model representation.")
return
}
}
}
I checked in my playground and it seems that everything is working fine.To find the real issue i think you are required to provide the real url from where you are getting json and can be checked with alamofire
import Foundation
let json = """
{
"data": {
"availability": {
"auth": true,
"ab": true,
"cd": true
},
"helloWorldConfiguration": {
"apiKey": "abcefg",
"rootUrl": "https://foo",
"apiVersion": 3
},
"fooBarConfiguration": {
"baseUrl": "https://foo",
"apiVersion": 1,
"privateApiPath": "",
"publicApiPath": "dev",
"tokenPath": ""
}
},
"errors": []
}
"""
let data = json.data(using: .utf8)
struct Response : Codable {
let data : Data?
let errors : [String]?
}
struct Availability : Codable {
let auth : Bool?
let ab : Bool?
let cd : Bool?
}
struct Data : Codable {
let availability : Availability?
let helloWorldConfiguration : HelloWorldConfiguration?
let fooBarConfiguration : FooBarConfiguration?
}
struct FooBarConfiguration : Codable {
let baseUrl : String?
let apiVersion : Int?
let privateApiPath : String?
let publicApiPath : String?
let tokenPath : String?
}
struct HelloWorldConfiguration : Codable {
let apiKey : String?
let rootUrl : String?
let apiVersion : Int?
}
let decoder = JSONDecoder()
let response = try decoder.decode(Response.self, from: data!)
print(response)
And here is the response
Response(data: Optional(__lldb_expr_11.Data(availability: Optional(__lldb_expr_11.Availability(auth: Optional(true), ab: Optional(true), cd: Optional(true))), helloWorldConfiguration: Optional(__lldb_expr_11.HelloWorldConfiguration(apiKey: Optional("abcefg"), rootUrl: Optional("https://foo"), apiVersion: Optional(3))), fooBarConfiguration: Optional(__lldb_expr_11.FooBarConfiguration(baseUrl: Optional("https://foo"), apiVersion: Optional(1), privateApiPath: Optional(""), publicApiPath: Optional("dev"), tokenPath: Optional(""))))), errors: Optional([]))
I have an object "itensList", it has the fields "name", "createdAt" and an array of "itens".
I want to be able to build JSON that looks like this:
{
"name": "List name"
"CreatedAt": "12:12 12/12/2016"
"itens": [
{
"title": "Item title"
"CreatedAt": "12:13 12/12/2016"
"isDone": false
},
{
"title": "Another item title"
"CreatedAt": "12:14 12/12/2016"
"isDone": true
}
]
}
I have tried a few different approaches with no success.
Item Object
class Item: Object {
dynamic var name = ""
dynamic var createdAt = NSDate()
dynamic var isDone = false
}
Item List Object
class ItemList: Object {
dynamic var name = ""
dynamic var createdAt = NSDate()
let itens = List<Item>()
}
For the example, let's make an object similar to what you must have:
class Iten {
let title:String
let createdAt:String
let isDone:Bool
init(title: String, createdAt: String, isDone: Bool) {
self.title = title
self.createdAt = createdAt
self.isDone = isDone
}
}
The trick I suggest is to add a computed value that will return a dictionary:
class Iten {
let title:String
let createdAt:String
let isDone:Bool
init(title: String, createdAt: String, isDone: Bool) {
self.title = title
self.createdAt = createdAt
self.isDone = isDone
}
var toDictionary: [String:AnyObject] {
return ["title": title, "createdAt": createdAt, "isDone": isDone]
}
}
Let's use it:
let iten1Dict = Iten(title: "title1", createdAt: "date1", isDone: false).toDictionary
let iten2Dict = Iten(title: "title2", createdAt: "date2", isDone: true).toDictionary
We now make the encapsulating dictionary:
let dict: [String:AnyObject] = ["name": "List name", "createdAt": "dateX", "itens": [iten1Dict, iten2Dict]]
To finish, we encode this dictionary to JSON data then we decode it as a String:
do {
let jsonData = try NSJSONSerialization.dataWithJSONObject(dict, options: .PrettyPrinted)
if let jsonString = String(data: jsonData, encoding: NSUTF8StringEncoding) {
print(jsonString)
}
} catch let error as NSError {
print(error)
}
And voilà:
{
"createdAt" : "dateX",
"itens" : [
{
"title" : "title1",
"createdAt" : "date1",
"isDone" : false
},
{
"title" : "title2",
"createdAt" : "date2",
"isDone" : true
}
],
"name" : "List name"
}
Raphael,
This piece of code builds a JSON query. It should get you started, just keep hacking and you'll find a way! That's the fun of programming!
func JSONquery()
let request = NSMutableURLRequest(URL: NSURL(string: "https://api.dropboxapi.com/2/files/get_metadata")!)
let session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
request.addValue("application/json",forHTTPHeaderField: "Content-Type")
request.addValue("path", forHTTPHeaderField: lePath)
let cursor:NSDictionary? = ["path":lePath]
do {
let jsonData = try NSJSONSerialization.dataWithJSONObject(cursor!, options: [])
request.HTTPBody = jsonData
print("json ",jsonData)
} catch {
print("snafoo alert")
}
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
if let error = error {
completion(string: nil, error: error)
return
}
let strData = NSString(data: data!, encoding: NSUTF8StringEncoding)
//print("Body: \(strData)\n\n")
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers);
self.jsonPParser(jsonResult,field2file: "ignore")
/*for (key, value) in self.parsedJson {
print("key2 \(key) value2 \(value)")
}*/
completion(string: "", error: nil)
} catch {
completion(string: nil, error: error)
}
})
task.resume()
}
Like this:
var item = [
"title": "Item title",
"CreatedAt": "12:13 12/12/2016",
"isDone": false
]
var mainDictionary = [
"name": "List name",
"CreatedAt": "12:12 12/12/2016",
"items": [item]
]
And the just convert to json with NSJSONSerialization like this:
do {
let json = try NSJSONSerialization.dataWithJSONObject(mainDictionary, options: [])
} catch {
print(error)
}
UPDATE:
If you need to add values to array in dictionary you can do that like this:
if var items = mainDictionary["items"] as? NSMutableArray {
items.addObject(newItem)
mainDictionary["items"] = items
}