Is it possible to use CKRecord.encodeSystemFields with JSONEncoder - ios

I see in "How (and when) do I use iCloud's encodeSystemFields method on CKRecord?" how to use NSKeyedArchiver to cache the salient fields of CKRecord. Is there a way to use JSONEncoder with encodeSystemFields? My other cache data is saved as JSON so I'd like encodeSystemFields to fit with that.
Unfortunately Data is not allowed in valid JSON despite being Codable, and fails the test JSONSerialization.isValidJSONObject, so I can't just jam the output of NSKeyedArchiver into my JSON serialization.
let d = Data()
d is Codable // true
// ...encodeSystemFields...
JSONSerialization.isValidJSONObject([d]) // false
let listofd = [d]
JSONSerialization.isValidJSONObject(listofd) // false
let dictofd = ["test":d]
JSONSerialization.isValidJSONObject(dictofd) // false
It doesn't matter if d is Data or NSMutableData. I get the same results.

The solution I found was to first serialize using NSKeyedArchiver to NSMutableData, cast it to Data, and use JSONEncoder to encode it again to JSON. It creates less than 2000 bytes of JSON. which overwhelmed my tiny record but that's life.
Here is a working playground for that:
import Foundation
import CloudKit
let zoneID1 = CKRecordZoneID(zoneName: "Test.1",
ownerName: CKCurrentUserDefaultName)
let recID1 = CKRecordID(recordName: "Test1",
zoneID: zoneID1)
let rec1 = CKRecord(recordType: "Test",
recordID: recID1)
let cacheNSData = NSMutableData()
let kArchiver = NSKeyedArchiver(forWritingWith: cacheNSData)
rec1.encodeSystemFields(with: kArchiver)
kArchiver.finishEncoding()
cacheNSData.length
let cacheData = cacheNSData as Data
let jEncoder = JSONEncoder()
struct Cache: Codable {
var title: String
var blob: Data
}
let cacheItem = Cache(title: "abc", blob: cacheData)
let jData = try jEncoder.encode(cacheItem)
jData.count
String(data: jData, encoding: .utf8)

Related

Variable inside JSON string

I am working on an app to make CVs and I have a JSON file with job descriptions the user can choose from. The problem is that the aircraft the user flies can be many and I would like to add a variable I can change inside the JSON string. Something like this:
{
"data":[
{
"jobDescription": "Worldwide Passenger and freighter flights in all weather conditions, on the modern " + yourAirplane + ". I Have gained experience flying to destinations in all continents while working with different nationalities"
}
]
}
I have been googling but I couldn't find anything. I parse the string with the following code:
struct JobDescriptionData : Decodable{
var data:[JobDescritpion]?
}
class JobDescritpion: NSObject, Decodable {
static let shared = JobDescritpion()
var jobDescription : String?
func loadJobDescriptions(completion: ([JobDescritpion]) -> ()){
let path = Bundle.main.path(forResource: "jobDescription", ofType: "json")!
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
data.printJSON()
let decoder = JSONDecoder()
guard let eventResult = try? decoder.decode(JobDescriptionData.self, from: data) else { return }
var array = [JobDescritpion]()
array = eventResult.data!
completion(array)
} catch {
}
}
}
printJSON() (which is my extension to Data to print the json array) works and it returns the array but of course, the guard let fails because jobDescription is not a string.
Any Advice on how I can add a variable and then set it later in the app?
Use String Format for your JSON
example
let stringTemplate = "Worldwide Passenger and freighter flights in all weather conditions, on the modern %#. I Have gained experience flying to destinations in all continents while working with different nationalities"
let formattedString = String(format: stringTemplate , "some value")
print(formattedString)
Keep the JSON as a string:
{
"data": [{
"jobDescription": "Worldwide Passenger and freighter flights in all weather conditions, on the modern <yourAirplane>. I Have gained experience flying to destinations in all continents while working with different nationalities"
}]
}
After decoding:
"description".replacingOccurrences(of: "<yourAirplane>", with: yourAirplane)

How can I decode this json with Alamofire? [duplicate]

This question already has answers here:
How to parse a JSON file in swift?
(18 answers)
Closed 3 years ago.
I want to print just value for key "User_number"
[
{
"User_fullname": null,
"User_sheba": null,
"User_modifiedAT": "2019-01-31T18:37:02.716Z",
"_id": "5c53404e91fc822c80e75d23",
"User_number": "9385969339",
"User_code": "45VPMND"
}
]
I suppose this is some JSON in Data format
let data = Data("""
[ { "User_fullname": null, "User_sheba": null, "User_modifiedAT": "2019-01-31T18:37:02.716Z", "_id": "5c53404e91fc822c80e75d23", "User_number": "9385969339", "User_code": "45VPMND" } ]
""".utf8)
One way is using SwiftyJSON library, but, this is something what I don't suggest since you can use Codable.
So, first you need custom struct conforming to Decodable (note that these CodingKeys are here to change key of object inside json to name of property of your struct)
struct User: Decodable {
let fullname, sheba: String? // these properties can be `nil`
let modifiedAt, id, number, code: String // note that all your properties are actually `String` or `String?`
enum CodingKeys: String, CodingKey {
case fullname = "User_fullname"
case sheba = "User_sheba"
case modifiedAt = "User_modifiedAT"
case id = "_id"
case number = "User_number"
case code = "User_code"
}
}
then decode your json using JSONDecoder
do {
let users = try JSONDecoder().decode([User].self, from: data)
} catch { print(error) }
So, now you have Data decoded as array of your custom model. So if you want to, you can just get certain User and its number property
let user = users[0]
let number = user.number
The following code takes takes in Data and saves "User_number" as an Int
if let json = try? JSONSerialization.jsonObject(with: Data!, options: []) as! NSDictionary {
let User_number= json["User_number"] as! Int
}

How to convert Base64 string to NSData?

I have an iOS application (written in Swift) that retrieves data from a wcf service in JSON format. One of the data is an image stored as a base64string. However, I was not able to convert the base64string to NSData.
My main purpose is to convert base64string all the way to blob so that I could save that in the database. On the other hand, if you know at least part of it such as from base64string to NSData would be helpful.
Following code would give you the idea of my table
let ItemsDB = Table("Items")
let idDB = Expression<String>("ID")
let nameDB = Expression<String>("Name")
let catDB = Expression<String>("Category")
let uomDB = Expression<String>("UOM")
let priceDB = Expression<Double>("Price")
let imageDB = Expression<Blob>("Image")
let actDB = Expression<Bool>("Active")
To convert from Base64 string to NSData
let nsd: NSData = NSData(base64EncodedString: Image, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)!
To Convert to Blob
nsd.datatypeValue
This works:
Swift 3, 4 & 5:
var data = Data(base64Encoded: recording_base64, options: .ignoreUnknownCharacters)
Swift 2:
var data = NSData(base64EncodedString: recording_base64, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)
There are lot of example you can find online but most of them are in Objective-c. For example, Converting between NSData and base64 strings
It is pretty straight forward for you to use
NSData(base64EncodedString: String, options: NSDataBase64DecodingOptions)

Swift. How to write bytes from NSData into another NSData?

I'm trying to concatenate two NSData objects into one NSMutableData, and than get them back. For now i'm trying to do it in such way:
Get length of first object.
Write into NSMutableData in such order: first object length, first object, second object.
Code looks like:
let firstString = "first_string";
let secondString = "secondSting";
let firstData = firstString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let secondData = secondString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let mutableData = NSMutableData()
var length = firstData.length
mutableData.appendBytes(&length, length: sizeof(Int))
mutableData.appendData(firstData)
mutableData.appendData(secondData)
Then I want to get datas back. So I suppose to read first data length and then get two datas.
var length = 0
mutableData.getBytes(&length, length: sizeof(Int))
But when I'm trying to get data I'm getting crash instead:
var data = NSData()
mutableData.getBytes(&data, range: NSMakeRange(sizeof(Int), length))
Maybe somebody know where is my problem or how to get datas?
You can extract the data using subdataWithRange():
let firstData1 = mutableData.subdataWithRange(NSMakeRange(sizeof(Int), length))
if let firstString1 = NSString(data: firstData1, encoding: NSUTF8StringEncoding) as? String {
println(firstString1)
} else {
// bad encoding
}
Your solution
var data = NSData()
mutableData.getBytes(&data, range: NSMakeRange(sizeof(Int), length))
does not work and crashes because NSData is a reference type and
data a pointer to the object. You are overwriting this pointer
and the following bytes in memory.
This works perfectly without a crash in my storyboard. I just omitted the second var before length in order to avoid redefining it.
Here is the output for each line:
"first_string"
"secondSting"
<66697273 745f7374 72696e67> // let firstData = ...
<7365636f 6e645374 696e67> // let secondData = ...
<> // let mutableData = ...
12 // var length = ...
// appending data
<0c000000 00000000>
<0c000000 00000000 66697273 745f7374 72696e67>
<0c000000 00000000 66697273 745f7374 72696e67 7365636f 6e645374 696e67>
0 // length = 0
<0c000000 00000000 66697273 745f7374 72696e67 7365636f 6e645374 696e67>
12 // length
This means you probably have an error somewhere else. You did not redefine length, right?

Can't figure out why I get a fatal error: unexpectedly found nil while unwrapping an Optional value

I keep getting this error :
fatal error: unexpectedly found nil while unwrapping an Optional value
and cannot figure out how to debug it!
Here's my code :
func readCSV() -> Array<String> {
// Creates a new array of strings
var csvArray : Array<String> = Array<String>()
if let url: NSURL = NSURL(string : "URLFROMCSV" ) {
// Creates an Input Stream that will load the datas from our URL
let data :NSData! = NSData(contentsOfURL: url)!
let stream : NSInputStream! = NSInputStream(data: data)
// Opens the receiving stream
stream.open()
// Sets a buffer with a given size size
let bufferSize = 1024
var buffer = Array <UInt8>(count: bufferSize, repeatedValue: 0)
// String variable initialization
var csvFullString : String = ""
// While the stream receives datas, parses datas, convert them into strings and then concatenate them into one big string
while (stream.hasBytesAvailable) {
let readSize = stream.read(&buffer, maxLength: bufferSize)
let csvRaw = NSString (bytes: &buffer, length: readSize, encoding: NSUTF8StringEncoding)
let csvString = csvRaw as String!
csvFullString = csvFullString + csvString
}
// Fills the array with each strings. Separation between strings is made when a Θ character is parsed
csvArray = csvFullString.componentsSeparatedByString("Θ")
// Delete each null string
for(var i = 0 ; i < csvArray.count; i++) {
if(csvArray[i] == "") {
csvArray.removeAtIndex(i)
}
}
}
return csvArray
}
After searching on the web, I'm pretty sure it has something to do with unwrapping elements but the fact is when I debug it, i don't get any nil value anywhere.
PS: Would like to upload a screen but can't because i don't have 10 reputation, so bad!
Thanks in advance!
EDIT : Line let data :NSData! = NSData(contentsOfURL: url)! got the error.
Terry
You're probably creating the error in one of these two lines (though it may show up later):
let data :NSData! = NSData(contentsOfURL: url)!
let stream : NSInputStream! = NSInputStream(data: data)
You're assigning an optional value to an implicitlyUnwrappedOptional type and then using it without checking if you have a valid value.
This is why if let exists. It's a little funny that you've started to indent as if you're using if let but aren't.
Try this instead:
if let url = NSURL(string : "http://gorillaapplications.com/etablissements.csv" ) {
// Creates an Input Stream that will load the datas from our URL
if let data = NSData(contentsOfURL: url) {
let stream = NSInputStream(data: data)
stream.open()
// rest of your code here
}
else {
println("Didn't get a data object")
}
}
else {
println("Didn't get a URL object")
}
You really need to grasp Optionals for Swift. I'd recommend reading my Optionals chapter in this iBook: https://itunes.apple.com/us/book/swift-optionals-generics-for/id943445214?mt=11&uo=4&at=11lMGu
Update:
Since you added a bit more in your comments above, you're saying you get the error on this line: let data: NSData! = NSData(contentsOfURL: url)!. This is because of the ! at the end, which tells Swift you're sure that this function will return a valid value, so just use it, without checking if it's nil first. In your case, the function is returning nil and so your app crashes. Using the sample code I've provided above, you'll see that you'll no longer get a crash, but your code will execute the "Didn't get a data object" line. You'll need to correctly handle the case where you can't load data from that URL.

Resources