I have some data from server, and I'm using Alamofire SwiftyJSON to convert it to [String: Any]. Then I'm saving it to plist using SwiftyPlistManager. The point is that SwiftyPlistManager crashed when saving <null>, so I need to replace all <null>or nilto "".
My Dictionary after Alamofire SwiftyJSON looks this way:
["info_editable": true,
"name": Android Q,
"is_message": true,
"images": [["id": 92,
"image": /media/product/102.png]],
"video_description": <null>,
"is_valid": true]
or it could be -
["info_editable": true,
"name": Android Q,
"is_message": true,
"images": <null>,
"video_description": <null>,
"is_valid": true]
I suppose to use Codable from raw data, but have no idea how to set initial value as empty string or [[]], then check if parced data is <null> and leave initial value as default.
Or is there any way to list nested dictionary to replace <null>to ""?
You can try
var dic = ["1":nil,"2":"33","3":"5444"]
let res = dic.mapValues { $0 == nil ? "" : $0 }
print(res) // ["1": Optional(""), "2": Optional("33"), "3": Optional("5444")]
for now, my best idea is -
1) stringify every value except array of dictionaries,
2) check if string content "null", then replace it with ""if true.
3) array of dictionaries, if stringified, will have 2 option - with [[ and ]](then checking every dictionary like above) or without - in case of "images": <null>,(so <null> should be replaced with[[]].
but I have about 7 requests with different data, should be parsed this strange way, and I hope to found more pretty decision.
Here's a protocolish solution, that goes recursively through dictionaries and arrays:
/// Allows clients to ask the receiver to remove any `NSNull` instances
protocol NullStripable {
func strippingNulls() -> Self
}
extension Array: NullStripable {
func strippingNulls() -> Self {
return compactMap {
switch $0 {
case let strippable as NullStripable:
// the forced cast here is necessary as the compiler sees
// `strippable` as NullStripable, as we casted it from `Element`
return (strippable.strippingNulls() as! Element)
case is NSNull:
return nil
default:
return $0
}
}
}
}
extension Dictionary: NullStripable {
func strippingNulls() -> Self {
return compactMapValues {
switch $0 {
case let strippable as NullStripable:
// the forced cast here is necessary as the compiler sees
// `strippable` as NullStripable, as we casted it from `Value`
return (strippable.strippingNulls() as! Value)
case is NSNull:
return nil
default:
return $0
}
}
}
}
Usage example:
let dict: [String: Any] = [
"items": ["info_editable": true,
"name": "Android Q",
"is_message": true,
"images": [["id": 92,
"image": "/media/product/102.png"]],
"video_description": NSNull(),
"is_valid": true],
"somethingElse": NSNull()
]
print(dict.strippingNulls())
Sample output:
["items": ["name": "Android Q", "info_editable": true, "images": [["image": "/media/product/102.png", "id": 92]], "is_message": true, "is_valid": true]]
Try this one. It will remove null and replace with blank string, without loosing key.
func removeNullFromResponse(response:NSDictionary) -> NSDictionary{
let blankString = "\"\""
let myMutableDict: NSMutableDictionary = NSMutableDictionary(dictionary: response)
var data = try? JSONSerialization.data(withJSONObject:myMutableDict, options: JSONSerialization.WritingOptions.prettyPrinted)
var strData = NSString.init(data: data!, encoding:String.Encoding.utf8.rawValue)
strData = strData?.replacingOccurrences(of: "<NULL>", with: blankString) as NSString?
strData = strData?.replacingOccurrences(of: "<null>", with: blankString) as NSString?
strData = strData?.replacingOccurrences(of: "<Null>", with: blankString) as NSString?
strData = strData?.replacingOccurrences(of: "NULL", with: blankString) as NSString?
strData = strData?.replacingOccurrences(of: "null", with: blankString) as NSString?
data = strData?.data(using: String.Encoding.utf8.rawValue)!
var dictionary = NSDictionary()
do
{
dictionary = try JSONSerialization.jsonObject(with: data! , options: JSONSerialization.ReadingOptions()) as! NSDictionary
} catch {
print(error)
}
return dictionary as NSDictionary
}
Related
I have a groups reference in firebase that looks like this:
I'm having trouble converting the list of members into an array of strings in my app.
I'm fetching the data like so:
//Reference to each group
let ref = Database.database().reference().child("groups").child(snapshot.key)
//Get the group data from the reference
ref.observeSingleEvent(of: .value, with: { (groupSnap) in
//Cast data as dictionary [String:Any]
if let dictionary = groupSnap.value as? [String: Any] {
//Parse each group object
if let group = Group.parse(snapshot.key, dictionary) {
groups.insert(group, at: 0)
}
//Escape with group array
complete(groups)
}
})
And currently parsing the data without the members:
static func parse(_ key: String, _ data: [String:Any]) -> Group? {
let name = data["name"] as! String
let category = data["category"] as! String
let owner = data["owner"] as! String
return Group(id: key, name: name, category: Group.Category(rawValue: category)!, ownerId: owner, members: nil)
}
How would I turn the members list into an array of strings for my group object?
// example data
let data = [
// "name": ...
// "category": ...
// "owner": ...
"members": [
"member1": true,
"member2": false,
"member3": true,
"member4": true
]
]
// grabbing the members element like you do in your parse function
let members = data["members"] as! [String: Bool]
let membersAsListOfStrings = Array(members.keys)
print(membersAsListOfStrings) // -> ["member4", "member1", "member3", "member2"]
let filteredMembersAsListOfStrings = Array(members.filter { $0.value }.keys)
print(filteredMembersAsListOfStrings) // -> ["member4", "member3", "member1"]
You're looking for the .keys attribute. I believe all dictionaries in Swift have this. This code ran for me fine in a playground.
This question already has answers here:
How to update a value in a nested dictionary given path fragment in Swift?
(2 answers)
Closed 3 years ago.
I have nested dictionary, so something like this:
var dict = ["a":["b":"c", "d":["e":"f"]], "f":["b":"g"]] // and more
Now if I want to update the value of key a.d.e, how should I do that?
It seems updateValue method only reads key as it is...It doesn't know anything about the nested key.
Edit:
While I really want to change the structure of this data to something easier to work with. I can't. This belongs to another class and I am not allow to change the structure, all I can do is to update it.
Second Edit:
After some thoughts and read the other question some one point out might be a duplicate, tried recursively updating. I feel this is seriously the worst way to do it because essentially it's creating new dictionaries with a copy of original value and assign it back. I do think it is a waste of space and tbh even recursively calling this I feel is unnecessary.
func updateKey(key:[AnyHashable],val:Bool,data:[AnyHashable:Any])->[AnyHashable:Any]{
var keyTemp = key
var tempData = data
if(keyTemp.count==1){
tempData.updateValue(val, forKey: key[0])
print(tempData)
return tempData
}else{
var firstLayerValue = data[keyTemp[0]] as? [AnyHashable:Any]
var firstKey = keyTemp.removeFirst()
var tempResult = updateKey(key: keyTemp, val: val, data: firstLayerValue!)
tempData.updateValue(tempResult, forKey: firstKey)
return tempData;
}
}
This returns a copy of what I intend to do, and I have to actually assign it back to the original copy. I really don't like this assigning back and forth thing, what if something went wrong in the middle then I might just end up losing the originally correct data.
Is there any better solution?
Another way of doing it
dict["a"]?["d"] = ["e": 123]
print(dict)
Outputs
["f": ["b": "g"], "a": ["b": "c", "d": ["e": 123]]]
Structs are the way to go. But if you really have to use dictionaries, here is a workaround:
var dict: [AnyHashable: Any] =
["a": ["b": "c", "d": ["e":"f"]],
"f": ["b": "g"]]
let newValue = true
if var firstLevel = dict["a"] as? [String : Any],
var secondLevel = firstLevel["d"] as? [String: Any] {
secondLevel["e"] = newValue
firstLevel["d"] = secondLevel
dict["a"] = firstLevel
}
print(dict) //[AnyHashable("a"): ["d": ["e": true], "b": "c"], AnyHashable("f"): ["b": "g"]]
To update a value in a dictionary with multiple levels, you can define a function like so:
func update(dictionary dict: inout [AnyHashable: Any], at keys: [AnyHashable], with value: Any) {
if keys.count < 2 {
for key in keys { dict[key] = value }
return
}
var levels: [[AnyHashable: Any]] = []
for key in keys.dropLast() {
if let lastLevel = levels.last {
if let currentLevel = lastLevel[key] as? [AnyHashable: Any] {
levels.append(currentLevel)
}
else if lastLevel[key] != nil, levels.count + 1 != keys.count {
break
} else { return }
} else {
if let firstLevel = dict[keys[0]] as? [AnyHashable : Any] {
levels.append(firstLevel )
}
else { return }
}
}
if levels[levels.indices.last!][keys.last!] != nil {
levels[levels.indices.last!][keys.last!] = value
} else { return }
for index in levels.indices.dropLast().reversed() {
levels[index][keys[index + 1]] = levels[index + 1]
}
dict[keys[0]] = levels[0]
}
And use it like so:
var dict: [AnyHashable: Any] = ["a": ["b": 1,
"c": ["d": ["e": "f"],
"g": ["h": 1.5]]],
"j": ["k": 2]]
update(dictionary: &dict,
at: ["a", "c", "d", "e"],
with: true)
dict.forEach { print($0) }
Here is the output in the console:
(key: AnyHashable("a"), value: [AnyHashable("b"): 1, AnyHashable("c"): [AnyHashable("d"): [AnyHashable("e"): true], AnyHashable("g"): ["h": 1.5]]])
(key: AnyHashable("j"), value: ["k": 2])
Objc.io has a great talk on easily mutating untyped Dictionaries, but the problem is you can't easily persist them. I think the talk may have been released before #dynamicMemberLookup was introduced.
AnyCodable looks awesome for easily encoding/decoding/persisting simple Dictionaries but you can't easily access the Dictionary members.
I was wondering if it's possible/feasible to add #dynamicMemberLookup functionality found in Swift 4.2 (eg: in this example) to AnyCodable and, if so, how? The end goal would be to access/mutate an untyped Array or Dictionary and persist them.
So, I tried doing it like this:
#dynamicMemberLookup
public struct AnyCodable: Codable {
public let value: Any
public init<T>(_ value: T?) {
self.value = value ?? ()
}
subscript(dynamicMember member: String) -> AnyCodable? {
switch self.value {
case let dictionary as [String: Any?]:
return AnyCodable(dictionary[member])
default:
return nil
}
}
}
Given the example Dictionary from AnyCodable:
let dictionary: [String: AnyEncodable] = [
"boolean": true,
"integer": 1,
"double": 3.14159265358979323846,
"string": "string",
"array": [1, 2, 3],
"nested": [
"a": "alpha",
"b": "bravo",
"c": "charlie"
]
]
If I do:
if let nested = dictionary["nested"] {
print("nested a:", nested.a)
}
it outputs: nested a: Optional(AnyCodable(Optional("alpha"))) which is almost there! But I want to be able to simply write dictionary?.nested?.a OR dictionary?.array?[1] rather than unwrap nested first with if let nested = dictionary["nested"]. And I want to be able to mutate it, eg: dictionary?.nested?.a? = "beta".
I can't figure out how to get it across the finish line though. I'd obviously need to add case let array as [Any]: etc. and maybe change the subscript to include getter/setters? But what else am I missing?
I know you're probably "not supposed to use Dictionaries that way" and create a full-blown custom-typed model and all that, but this is for a small project where going that route would be overkill. So please don't answer with "model your data differently". I want to combine these two existing methods of accessing/persisting untyped dictionaries or arrays into one.
Ok, I think I had it mostly covered.
The first problem is, that you work with a dictionary. You can add #dynamicMemberLookup only to the main definition, so you can't do it on the dictionary definition. Try this:
let dictionary: [String: AnyEncodable] = [ ... ]
let easierToUse = AnyCodable(dictionary)
So considering code below, is it what you needed? :
let dictionary: [String: AnyCodable] = [
"boolean": true,
"integer": 1,
"double": 3.14159265358979323846,
"string": "string",
"array": [1, 2, 3],
"nested": [
"a": "alpha",
"b": "bravo",
"c": "charlie",
"array": [
1,
2,
[
"a": "alpha",
"b": "bravo",
"c": "deep charlie"
]
],
]
]
let easierToUse: AnyCodable = AnyCodable(dictionary)
if let value = easierToUse.nested?.a {
print(value) // prints "alpha"
}
if let value = easierToUse.nested?.array?[2]?.c {
print(value) // prints "deep charlie"
}
if let value = easierToUse.nested?.array?[2]?.c?.value as? String {
print(value) // prints "deep charlie"
}
I had to update your classes a bit, as you forgot that it is all wrapped on every level:
// Helper to handle out of bounds on array with nil
extension Array {
subscript (safe index: Int) -> Element? {
return indices ~= index ? self[index] : nil
}
}
#dynamicMemberLookup
public struct AnyCodable: Codable {
public let value: Any
public init<T>(_ value: T) {
self.value = value
}
public init<T>(_ value: T?) {
self.value = value ?? ()
}
subscript(dynamicMember member: String) -> AnyCodable? {
switch self.value {
case let anyCodable as AnyCodable:
return anyCodable[dynamicMember: member]
case let dictionary as [String: Any?]:
return AnyCodable(dictionary[member] ?? nil)
default:
return nil
}
}
subscript(index: Int) -> AnyCodable? {
switch self.value {
case let anyCodable as AnyCodable:
return anyCodable[index]
case let array as [Any]:
return AnyCodable(array[safe: index])
default:
return nil
}
}
}
I have this JSON save in file ("list_catalog.json"):
[
{
"idcatalog": 1,
"imgbase64": "",
"urlImg": "http://172.../page01.jpg",
"urlPages": "http://172.../catalog_1_pages.json",
"dt_from": "",
"dt_to": ""
},
{
"idcatalog": 2,
"imgbase64": "",
"urlImg": "http://172.../1.jpg",
"urlPages": "http://172.../2_pages.json",
"category": [
{
"id": 1,
"lib": "lib"
}
],
"dt_to": ""
}
]
I can get this file with :
if let url = URL(string: "http://172.../list_catalogs.json") {
do {
let contents = try String(contentsOf: url)
print(contents)
} catch {
// contents could not be loaded
}
}
But I can't convert this String in dictionary.
For convert data to string I use this function :
func convertToDictionary(text: String) -> [String: Any]? {
if let data = text.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
} catch {
print(error.localizedDescription)
}
}
return nil
}
But for this situation it doesn't work
Somebody can help me ?
The problem with the code you have is that you are trying to convert an array to a dictionary (as rightly pointed out by a couple of people in the comments Vadian, Moritz at the time of writing).
So the obvious, first step solution would be instead of casting to [String: Any] cast to [[String: Any]] (an array of dictionaries) but this then still leaves you with the problem of the Any part. To work with this dictionary you need to know/remember the keys and their types and cast each value as you use it.
It is much better to use the Codable protocol which along with the decoder allows you to basically map the JSON to related code structures.
Here is an example of how you could parse this JSON using the Swift 4 codable protocol (done in a Swift Playground)
let jsonData = """
[{
"idcatalog": 1,
"imgbase64": "",
"urlImg": "http://172.../page01.jpg",
"urlPages": "http://172.../catalog_1_pages.json",
"dt_from": "",
"dt_to": ""
}, {
"idcatalog": 2,
"imgbase64": "",
"urlImg": "http://172.../1.jpg",
"urlPages": "http://172.../2_pages.json",
"category": [{
"id": 1,
"lib": "lib"
}],
"dt_to": ""
}]
""".data(using: .utf8)!
struct CatalogItem: Codable {
let idCatalog: Int
let imgBase64: String?
let urlImg: URL
let urlPages: URL
let dtFrom: String?
let dtTo: String?
let category: [Category]?
enum CodingKeys: String, CodingKey {
case idCatalog = "idcatalog"
case imgBase64 = "imgbase64"
case urlImg, urlPages, category
case dtFrom = "dt_from"
case dtTo = "dt_to"
}
}
struct Category: Codable {
let id: Int
let lib: String?
enum CodingKeys: String, CodingKey {
case id, lib
}
}
do {
let decoder = JSONDecoder()
let result = try decoder.decode([CatalogItem].self, from: jsonData)
print(result)
} catch {
print(error)
}
Console output:
[__lldb_expr_118.CatalogItem(idCatalog: 1, imgBase64: Optional(""), urlImg: http://172.../page01.jpg, urlPages: http://172.../catalog_1_pages.json, dtFrom: Optional(""), dtTo: Optional(""), category: nil), __lldb_expr_118.CatalogItem(idCatalog: 2, imgBase64: Optional(""), urlImg: http://172.../1.jpg, urlPages: http://172.../2_pages.json, dtFrom: nil, dtTo: Optional(""), category: Optional([__lldb_expr_118.Category(id: 1, lib: Optional("lib"))]))]
Now comes the good part, it's much easier to use this data now...
print(result.first?.urlImg)
Output:
Optional(http://172.../page01.jpg)
This works for the example JSON you provided but may need more tweaks based on the rest of your data set.
let json: [AnyObject] = {
"response": "get_nearby_deals",
"userID": "12345",
"demo":[{"deal_code":"iD1612061"}]
}
How to declare Dictionary in Swift? I'm new in Swift. Totally stuck.
You have declared Array using [AnyObject], just change it to [String: Any] and replace curly braces {} with square brackets [].
let json: [String: Any] = [
"response": "get_nearby_deals",
"userID": "12345",
"demo":[["deal_code":"iD1612061"]]
]
And you can retrieve value from Dictionary using subscript like this.
let userID = json["userID"] as! String
//Above will crash if `userID` key is not exist or value is not string, so you can use optional wrapping with it too.
if let userID = json["userID"] as? String {
print(userID)
}
//`demo` value is an array of [String:String] dictionary(s)
let demo = json["demo"] as! [[String:String]]
//Same here as the previous `userID`, it will crash if 'demo' key is not exist, so batter if you optionally wrapped it:
if let demo = json["demo"] as? [[String:String]] {
print(demo)
}