Swift - JSONSerialization invalid JSON - swift

I'm accessing health records via HealthKit, the issue is when I inspecting the FHIR data, it isn't valid JSON data when checking using isValidJSONObject. I'm not too familiar with JSONSerialization, this is my first real use for it.
let jsonObject = try JSONSerialization.jsonObject(with: fhirRecord.data, options: [])
print(jsonObject)
{
lotNumber = 11111;
occurrenceDateTime = "2021-01-01”;
patient = {
reference = "resource:0";
};
performer = (
{
actor = {
display = “Some place here“;
};
}
);
resourceType = Immunization;
status = completed;
vaccineCode = {
coding = (
{
code = 1;
system = “URL_HERE”;
},
{
code = 28581000087106;
system = “URL_HERE”;
}
);
};
}

JSONSerialization has nothing to do with Codable, and generally should be avoided in Swift. It's only in Swift because it's bridged from ObjC, and has significant limitations even in ObjC.
isValidJSONObject doesn't tell you that JSON data is valid. It tells you that an ObjC object could be converted to JSON by JSONSerialization (again, completely unrelated to Codable).
Get rid of the JSONSerialization. Plug your JSON into https://app.quicktype.io to generate a Codable model for it, and use JSONDecoder to decode it. JSONSerialization will only give a [String: Any] which is extremely hard to work with in Swift (and not great in ObjC). JSONDecoder will give you a proper struct.

As #RobNapier pointed out I needed FHIRModels, from Apple. I'm able to get data more easily than using a messy Codable data approach manually.
import ModelsR4
let resource = try decoder.decode(Immunization.self, from: data)
print output is proper
28581000087106
28581000087106

Related

Using NSKeyedUnarchiver to decode object to a new model class in Swift using Coddle

I am working with an old Objective-C codebase that utilizes NSCoding, NSSecureCoding, and NSKeyedUnarchiver/NSKeyedArchiver to store a model in User Defaults. We are migrating to a new User Defaults layer and I am wondering if it possible to decode this object without having the underlying class. For instance, the current object being stored is UserModel. Is it possible for me to create a new class, NewUserModel, with the same properties then decode this object from User Defaults?
I have tried the following, see comments for results:
guard let userData: Data = UserDefaults.default.object(forKey: "user-data") else {
return nil
}
// This returns the object, but it is Any as we do not have the model class for this object
guard let restoredObject = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(userData) as? Any else {
return nil
}
let unarchiver: NSKeyedUnarchiver = NSKeyedUnarchiver(forReadingFrom: userData)
if let decoded = unarchiver.decodeTopLevelDecodable(NewUserModel.self, forKey: NSKeyedArchiveRootObjectKey) {
// this fails and does not decode the object even though the properties are identical
print(decoded)
}
// trying to access the properties individually also fails
if let userToken: String = unarchiver.decodeObject(forKey: "userToken") {
// fails
print(userToken)
}
// attempting to decode using JSONDecoder also fails as the data is not valid JSON
do {
let jsonDecoder = JSONDecoder()
let user = try jsonDecoder.decode(NewUserModel.self, from: userData)
print(user)
} catch {
print(erro)
}
Basically I have the underlying data yet I need to decode this data manually to get the relevant information without having access to the exact class that was used to archive the data.
Is it possible for me to create a new class, NewUserModel, with the same properties then decode this object from User Defaults?
Yes.
See the -setClass:forClassName: method, which is described thus:
Sets a translation mapping on this unarchiver to decode objects encoded with a given class name as instances of a given class instead.
So you can use that method to tell your unarchiver to instantiate NewUserModel whenever it sees the class name UserModel in the archive.
There's also a class method +setClass:forClassName: that does the same thing for all unarchivers, which might be handy if you have to read the same data in multiple places.

List all window names in Swift

I’m learning Swift. How do I fix the following code to list the window names?
import CoreGraphics
let windows = CGWindowListCopyWindowInfo(CGWindowListOption.optionAll, kCGNullWindowID)
for i in 0..<CFArrayGetCount(windows) {
if let window = CFArrayGetValueAtIndex(windows, i) {
print(CFDictionaryGetValue(window, kCGWindowName))
}
}
The error:
main.swift:6:32: error: cannot convert value of type 'UnsafeRawPointer' to expected argument type 'CFDictionary?'
print(CFDictionaryGetValue(window, kCGWindowName))
^~~~~~
as! CFDictionary
It becomes easier if you avoid using the Core Foundation types and methods, and bridge the values to native Swift types as early as possible.
Here, CGWindowListCopyWindowInfo() returns an optional CFArray of CFDictionaries, and that can be bridged to the corresponding Swift type [[String : Any]]. Then you can access its values with the usual Swift methods (array enumeration and dictionary subscripting):
if let windowInfo = CGWindowListCopyWindowInfo(.optionAll, kCGNullWindowID) as? [[ String : Any]] {
for windowDict in windowInfo {
if let windowName = windowDict[kCGWindowName as String] as? String {
print(windowName)
}
}
}
You can use unsafeBitCast(_:to:) to convert the opaque raw pointer to a CFDictionary. Note that you'll also need to convert the second parameter, to a raw pointer:
CFDictionaryGetValue(unsafeBitCast(window, to: CFDictionary.self), unsafeBitCast(kCGWindowName, to: UnsafeRawPointer.self))
unsafeBitCast(_:to:) tells the compiler to treat that variable as another type, however it's not very safe (thus the unsafe prefix), recommending to read the documentation for more details, especially the following note:
Warning
Calling this function breaks the guarantees of the Swift type system; use with extreme care.
In your particular case there should not be any problems using the function, since you're working with the appropriate types, as declared in the documentation of the Foundation functions you're calling.
Complete, workable code could look something like this:
import CoreGraphics
let windows = CGWindowListCopyWindowInfo(CGWindowListOption.optionAll, kCGNullWindowID)
for i in 0..<CFArrayGetCount(windows) {
let windowDict = unsafeBitCast(CFArrayGetValueAtIndex(windows, i), to: CFDictionary.self)
let rawWindowNameKey = unsafeBitCast(kCGWindowName, to: UnsafeRawPointer.self)
let rawWindowName = CFDictionaryGetValue(windowDict, rawWindowNameKey)
let windowName = unsafeBitCast(rawWindowName, to: CFString?.self) as String?
print(windowName ?? "")
}
Update
You can bring the CoreFoundation array sooner to the Swift world by casting right from the start:
let windows = CGWindowListCopyWindowInfo(CGWindowListOption.optionAll, kCGNullWindowID) as? [[AnyHashable: Any]]
windows?.forEach { window in
print(window[kCGWindowName])
}
The code is much readable, however it might pose performance problems, as the cast to [[AnyHashable: Any]]` can be expensive for large array consisting of large dictionaries.

Can Codable APIs be used to decode data encoded with NSKeyedArchiver.archivedData?

I'm converting a codebase from using NSCoding to using Codable. I have run into an issue when trying to restore data encoded with NSCoding. I have an object that was encoded with the code:
let encodedUser = NSKeyedArchiver.archivedData(withRootObject: user)
let userDefaults = UserDefaults.standard
userDefaults.set(encodedUser, forKey: userKey)
userDefaults.synchronize()
It was previously being decoded with the code:
if let encodedUser = UserDefaults.standard.data(forKey: userKey) {
if let user = NSKeyedUnarchiver.unarchiveObject(with: encodedUser) as? User {
\\ do stuff
}
}
After updating the User object to be Codable, I attempted to update the archive/unarchive code to use PropertyListDecoder because I saw that archivedData(withRootObject:) returns data formatted as NSPropertyListBinaryFormat_v1_0. When running this code:
if let encodedUser = UserDefaults.standard.data(forKey: userKey) {
let user = try! PropertyListDecoder().decode(User.self, from: encodedUser)
\\ do stuff
}
I get a keyNotFound error for the first key I'm looking for, and breakpointing in my init(from: Decoder), I can see that the container has no keys. I also tried to use PropertyListSerialization.propertyList(from:options:format) to see if I could pass that result into PropertyListDecoder, but that function gave me an NSCFDictionary, which is structured in a really strange way but does appear to contain all the data I'm looking for.
So is it possible to decode an object using Codable APIs if it was encoded with NSKeyedArchiver.archivedData(withRootObject:)? How can I achieve this?
I decided that the answer is almost certainly no, you cannot use Codable to decode what NSCoding encoded. I wanted to get rid of all the NSCoding cruft in my codebase, and settled on a migration path where I have both protocols implemented in the critical data models to convert stored data from NSCoding to Codable formats, and I will remove NSCoding in the future when enough of my users have updated.

How to use swift function that returns a value - in objective C?

I am using IBM Watson APIs - Alchemy Data news
The problem is, I am using swift - objective C bridging and in between I am stuck with the function that returns a value. How do I use that value in my objective C code?
Here is my swift class
#objc class alchemyNews : NSObject {
func getNewsList() -> NewsResponse {
let apiKey = "api-key"
let alchemyDataNews = AlchemyDataNews(apiKey: apiKey)
let start = "now-1d" // yesterday
let end = "now" // today
let query = [
"q.enriched.url.title": "O[IBM^Apple]",
"return": "enriched.url.title,enriched.url.entities.entity.text,enriched.url.entities.entity.type"
]
let failure = { (error: NSError) in print(error) }
alchemyDataNews.getNews(start, end: end, query: query, failure: failure) { news in
print(news)
}
let response : NewsResponse = alchemyDataNews.getNews(start, end: end) { news in
return news
}
return response
}
}
I want to have alchemyDataNews.getNews print value to be display. So I am calling this function in my Objective C class in this way.
#property (strong, nonatomic) AlchemyDataNews *getnews;
-(void)loadNews
{
self.getnews = [[AlchemyDataNews alloc]init];
[self.getnews getNewsList];
}
But what to do now? This will just call the function and not give me the response so that I can put it in array and display in tableview.
I think the issue is getNewsList returns an instance of NewsResponse, you should store it in a variable and then use it.
self.getnews = [[AlchemyDataNews alloc]init];
NewsResponse *newResponse = [self.getnews getNewsList];
// now you can use 'newResponse'
// ...
Hope that helped.
The NewsResponse type (and its primary property, NewsResult) is a struct. Unfortunately, Swift's struct types do not bridge to Objective-C. To use the Swift-to-Objective-C bridging, you will need to write Swift code to further process the NewsResponse type.
For example, you might choose the information from each NewsResponse that you're interested in and store those values in a Swift Dictionary (which bridges to an Objective-C NSDictionary). Alternatively, you could expose the table view to Swift and write Swift code to populate it.
Hope that helps!

How to access CFDictionary in Swift 3?

I need to read and write some data from CFDictionary instances (to read and update EXIF data in photos). For the life of me, I cannot figure out how to do this in Swift 3. The signature for the call I want is:
func CFDictionaryGetValue(CFDictionary!, UnsafeRawPointer!)
How the heck do I convert my key (a string) to an UnsafeRawPointer so I can pass it to this call?
If you don't have to deal with other Core Foundation functions expecting an CFDictionary, you can simplify it by converting to Swift native Dictionary:
if let dict = cfDict as? [String: AnyObject] {
print(dict["key"])
}
Be careful converting a CFDictionary to a Swift native dictionary. The bridging is actually quite expensive as I just found out in my own code (yay for profiling!), so if it's being called quite a lot (as it was for me) this can become a big issue.
Remember that CFDictionary is toll-free bridged with NSDictionary. So, the fastest thing you can do looks more like this:
let cfDictionary: CFDictionary = <code that returns CFDictionary>
if let someValue = (cfDictionary as NSDictionary)["some key"] as? TargetType {
// do stuff with someValue
}
What about something like:
var key = "myKey"
let value = withUnsafePointer(to: &key){ upKey in
return CFDictionaryGetValue(myCFDictionary, upKey)
}
You can write something like this:
let key = "some key" as NSString
if let rawResult = CFDictionaryGetValue(cfDictionary, Unmanaged.passUnretained(key).toOpaque()) {
let result = Unmanaged<AnyObject>.fromOpaque(rawResult).takeUnretainedValue()
print(result)
}
But I guess you would not like to write such thing at any time you retrieve some data from that CFDictionary. You better convert it to Swift Dictionary as suggested in Code Different's answer.