Dictionary extension in Swift is not recognized - swift

I am trying to extend Dictionary with the following code:
extension Dictionary where Key: ExpressibleByStringLiteral, Value: AnyObject {
var jsonString: String? {
if let dict = (self as AnyObject) as? Dictionary<String, AnyObject> {
do {
let data = try JSONSerialization.data(withJSONObject: dict, options: JSONSerialization.WritingOptions(rawValue: UInt.allZeros))
if let string = String(data: data, encoding: String.Encoding.utf8) {
return string
}
} catch {
print(error)
}
}
return nil
}
}
Where I write something like:
let x: [String: String] = ["": ""]
x.jsonString
I get this error:
Value of type '[String: String]' as no member 'jsonString'
Anything I am missing?

There is no need to constrain Dictionary Value type at all:
extension Dictionary where Key: ExpressibleByStringLiteral {
var jsonString: String? {
guard let data = try? JSONSerialization.data(withJSONObject: self)
else { return nil }
return String(data: data, encoding: .utf8)
}
}

Since String is a value type , look for it's
public struct String {
and AnyObject refers to any instance of a class only , and is equivalent to id in Objective-C , so this declaration of x
[String: String] doesn't fit with [String: AnyObject]
because Any refers to any instance of a class, struct, or enum so it'll fit perfectly

Related

Issue Decoding a Set for Tableview

I am using the following struct that conforms to Codable to represent my data within a Set, I then take this set encode it into a JSON Array that looks like the following [{"myexample":"Example5","id":3}]
Struct and Encoding:
struct Model : Codable, Hashable {
var myexample: String?
var id: Int?
}
var mySet: Set<Model> = []
mySet.insert(Model(myexample: "Example4", id: 3))
do {
let json = try JSONEncoder().encode(mySet)
print(String(data: json, encoding: .utf8)!)
} catch {
print(error)
}
How can I inversely decode this array using the following function?
Currently getting this error:
Value of type 'Set' has no member 'utf8'
func processArray(array:Set<Model> = []) {
do{
let mydata = Data(array.utf8.data)
let decoded = try JSONDecoder().decode([Model].self,from: mydata)
print(decoded)
}catch let jsonErr {
print(jsonErr)
}
}
JSON is a string format so a JSON array is also string
func processJSON(_ json: String) {
do{
let mydata = Data(json.utf8)
let decoded = try JSONDecoder().decode(Set<Model>.self,from: mydata)
print(decoded)
} catch {
print(error)
}
}

How do I convert a WKScriptMessage.body to a struct?

I set up the WKScriptMessageHandler function userContentController(WKUserContentController, didReceive: WKScriptMessage) to handle JavaScript messages sent to the native app. I know ahead of time that the message body will always come back with the same fields. How do I convert the WKScriptMessage.body, which is declared as Any to a struct?
What about safe type casting to, for example, dictionary?
let body = WKScriptMessage.body
guard let dictionary = body as? [String: String] else { return }
Or as an option, you can send body as json string and serialise it using codable.
struct SomeStruct: Codable {
let id: String
}
guard let bodyString = WKScriptMessage.body as? String,
let bodyData = bodyString.data(using: .utf8) else { fatalError() }
let bodyStruct = try? JSONDecoder().decode(SomeStruct.self, from: bodyData)
In SwiftUI message.body is String object. You can convert the body in dictionary like this:
if let bodyString = message.body as? String {
let data = Data(bodyString.utf8)
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
guard let body = json["body"] as? [String: Any] else {
return
}
//use body object
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
}
Your defined struct
struct EventType:Codable{
let status: Int!
let message: String!
}
WKScriptMessageHandler protocol method
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
do {
let jsonData = try JSONSerialization.data(withJSONObject: message.body)
let eventType = try JSONDecoder().decode(EventType.self, from: jsonData)
} catch {
print("fatalError")
}
}

Serializing JSON ReadingOptions with inconsistent output in Swift 4.2

I'm working on a pet project where I am serializing JSON using the JSONSerialization class and jsonObject(with:options:). The object is unusable until cast into a Dictionary [String: Any] or an Array [Any]. This is where the inconsistency occurs. The following is a method from one of my classes. The input is tested and valid.
private static func parse(data: Data) -> [JSONDictionary]? {
do {
let options = JSONSerialization.ReadingOptions() // rawValue = UInt 0
let otherOptions: JSONSerialization.ReadingOptions = [] // rawValue = UInt 0
let jsonAny = try JSONSerialization.jsonObject(with: data, options: otherOptions)
if let array = jsonAny as? [String: Any] {
print(array)
}
} catch {
return nil
}
return nil
}
Both of the ReadingOption objects are valid and produce valid output that can be properly cast, and print(array) is called.
However, when I use the following, invalid output is returned and can not be cast properly. Note options in the jsonObject call has an equivalent value to otherOptions in the above example.
private static func parse(data: Data) -> [JSONDictionary]? {
do {
let jsonAny = try JSONSerialization.jsonObject(with: data, options: [])
if let array = jsonAny as? [String: Any] {
print(array) // never called
}
} catch {
return nil
}
return nil
}
I thought because they have equivalent values that I could use them in place of each other. But that is not the case. Is this a bug, or am I using this incorrectly?
Edit: here is the dataset being used https://www.govtrack.us/api/v2/role?current=true&role_type=senator
The reading options are irrelevant. In Swift ReadingOptions are only useful if the expected result is not array or dictionary.
If the expected type is array or dictionary omit the options parameter.
The inconsistency is that your return type is an array ([JSONDictionary]) but the actual type is a dictionary.
private static func parse(data: Data) -> JSONDictionary? {
do {
let jsonAny = try JSONSerialization.jsonObject(with: data)
if let jsonDictionary = jsonAny as? JSONDictionary {
return jsonDictionary
}
} catch {
print(error)
return nil
}
return nil
}
It's recommended to hand over an error of a throwing method
enum SerializationError : Error {
case typeMismatch
}
private static func parse(data: Data) throws -> JSONDictionary {
let jsonAny = try JSONSerialization.jsonObject(with: data)
guard let jsonDictionary = jsonAny as? JSONDictionary else { throw SerializationError.typeMismatch }
return jsonDictionary
}

Codable to CKRecord

I have several codable structs and I'd like to create a universal protocol to code them to CKRecord for CloudKit and decode back.
I have an extension for Encodable to create a dictionary:
extension Encodable {
var dictionary: [String: Any] {
return (try? JSONSerialization.jsonObject(with: JSONEncoder().encode(self), options: .allowFragments)) as? [String: Any] ?? [:]
}
}
Then in a protocol extension, I create the record as a property and I try to create a CKAsset if the type is Data.
var ckEncoded: CKRecord? {
// Convert self.id to CKRecord.name (CKRecordID)
guard let idString = self.id?.uuidString else { return nil }
let record = CKRecord(recordType: Self.entityType.rawValue,
recordID: CKRecordID(recordName: idString))
self.dictionary.forEach {
if let data = $0.value as? Data {
if let asset: CKAsset = try? ckAsset(from: data, id: idString) { record[$0.key] = asset }
} else {
record[$0.key] = $0.value as? CKRecordValue
}
}
return record
}
To decode:
func decode(_ ckRecord: CKRecord) throws {
let keyIntersection = Set(self.dtoEncoded.dictionary.keys).intersection(ckRecord.allKeys())
var dictionary: [String: Any?] = [:]
keyIntersection.forEach {
if let asset = ckRecord[$0] as? CKAsset {
dictionary[$0] = try? self.data(from: asset)
} else {
dictionary[$0] = ckRecord[$0]
}
}
guard let data = try? JSONSerialization.data(withJSONObject: dictionary) else { throw Errors.LocalData.isCorrupted }
guard let dto = try? JSONDecoder().decode(self.DTO, from: data) else { throw Errors.LocalData.isCorrupted }
do { try decode(dto) }
catch { throw error }
}
Everything works forth and back except the Data type. It can't be recognized from the dictionary. So, I can't convert it to CKAsset. Thank you in advance.
I have also found there is no clean support for this by Apple so far.
My solution has been to manually encode/decode: On my Codable subclass I added two methods:
/// Returns CKRecord
func ckRecord() -> CKRecord {
let record = CKRecord(recordType: "MyClassType")
record["title"] = title as CKRecordValue
record["color"] = color as CKRecordValue
return record
}
init(withRecord record: CKRecord) {
title = record["title"] as? String ?? ""
color = record["color"] as? String ?? kDefaultColor
}
Another solution for more complex cases is use some 3rd party lib, one I came across was: https://github.com/insidegui/CloudKitCodable
So I had this problem as well, and wasn't happy with any of the solutions. Then I found this, its somewhat helpful, doesn't handle partial decodes very well though https://github.com/ggirotto/NestedCloudkitCodable

Convert JSON String to Swift Dictionary

I'm trying to convert a json string to a dictionary.
I came across this answer but it only works for arrays with one value.
Here is an example of a string I'm trying to turn into a swift dictionary
[
{"value1":"Reporting for duty"},
{"value2":"Post received"},
{"value3":"frogg222"},
{"value4":"Still reporting"}
]
Just to be clear, the following code does not work and will return nil with the above string.
func convertStringToDictionary(text: String) -> [String:AnyObject]? {
if let data = text.data(using: String.Encoding.utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [String:AnyObject]
} catch let error as NSError {
print(error)
}
}
return nil
}